`.
+
+@verbatim
+```blade
+
+ First item
+ Second item
+ Third item
+
+```
+@endverbatim
+
+## Children
+
+Accepts any EDGE elements as children. Children are arranged vertically from top to bottom.
+
+## Supported Tailwind classes
+
+Column inherits the full class set documented at [Layout & Styling](layout#supported-tailwind-classes). The classes
+that shape how a column behaves specifically:
+
+| Class | Effect on a column |
+|---|---|
+| `gap-N` | **Vertical** spacing between children |
+| `items-*` | **Horizontal** (cross-axis) alignment of children: `items-start`, `items-center`, `items-end`, `items-stretch` |
+| `justify-*` | **Vertical** (main-axis) distribution: `justify-start`, `justify-center`, `justify-end`, `justify-between`, `justify-around`, `justify-evenly` |
+| `flex-1` | Fills remaining space in the parent flex container |
+| `safe-area`, `safe-area-top`, `safe-area-bottom` | Respect device safe-area insets (typical at page root) |
+
+Everything else from the shared list applies the same as on any element (`w-*`, `h-*`, `p-*`, `m-*`, `bg-*`,
+`rounded-*`, `shadow-*`, `dark:*`, `ios:*` / `android:*`, `glass:*`, alpha suffix `/N`, arbitrary `prefix-[value]`).
+
+## Examples
+
+### Full-screen layout with safe area
+
+@verbatim
+```blade
+
+ My App
+
+
+
+```
+@endverbatim
+
+### Centered content
+
+@verbatim
+```blade
+
+
+ Loading...
+
+```
+@endverbatim
+
+### Surface-styled layout
+
+@verbatim
+```blade
+
+ Section Title
+ Surface description goes here.
+
+
+
+
+
+```
+@endverbatim
+
+### Space-between distribution
+
+@verbatim
+```blade
+
+ Top
+ Middle
+ Bottom
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Native\Mobile\Edge\Elements\Column;
+use Native\Mobile\Edge\Elements\Text;
+
+Column::make(
+ Text::make('First'),
+ Text::make('Second'),
+)->fill()->padding(16)->gap(12);
+```
+
+- `make(Element ...$children)` - Create a column with children. Layout / style fluent methods are inherited from
+ the base `Element` class — see [Layout & Styling](layout)
diff --git a/resources/views/docs/mobile/4/edge-components/divider.md b/resources/views/docs/mobile/4/edge-components/divider.md
new file mode 100644
index 00000000..bac16a24
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/divider.md
@@ -0,0 +1,97 @@
+---
+title: Divider
+order: 340
+---
+
+## Overview
+
+A thin horizontal line separator. Renders as a 1pt rule. Color resolves from the `border-*` class if set, otherwise
+the platform separator color (`UIColor.separator` on iOS, Material `outlineVariant` on Android).
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+`
` is an alias of `
` exposed for use inside [side navigation](side-nav).
+
+
+
+## Supported Tailwind classes
+
+The classes that affect how a divider renders:
+
+| Class | Effect |
+|---|---|
+| `border-{palette}-{shade}`, `border-[#hex]`, `border-theme-{token}` | Line color |
+| `opacity-*`, `opacity-[0.5]` | Line opacity |
+| `m-*`, `mx-*`, `my-*`, `mt-*` / `mr-*` / `mb-*` / `ml-*` | Spacing around the divider |
+| `dark:border-*` | Dark-mode color override |
+| `ios:border-*`, `android:border-*` | Platform-specific color |
+
+## Examples
+
+### Basic separator
+
+@verbatim
+```blade
+
+ Section One
+ Some content here.
+
+ Section Two
+ More content here.
+
+```
+@endverbatim
+
+### Themed divider with margin
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### In a list
+
+@verbatim
+```blade
+
+ @foreach($items as $item)
+
+ {{ $item->name }}
+
+ @unless($loop->last)
+
+ @endunless
+ @endforeach
+
+```
+@endverbatim
+
+### Thicker rule (use a column)
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Native\Mobile\Edge\Elements\Divider;
+
+Divider::make()->borderColor('#E2E8F0');
+```
+
+- `make()` - Create a divider
diff --git a/resources/views/docs/mobile/4/edge-components/icon.md b/resources/views/docs/mobile/4/edge-components/icon.md
new file mode 100644
index 00000000..a0767cbf
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/icon.md
@@ -0,0 +1,90 @@
+---
+title: Icon
+order: 330
+---
+
+## Overview
+
+Displays a platform-native icon. On iOS, icons render as SF Symbols. On Android, icons render as Material Icons.
+A smart mapping system translates common icon names across platforms automatically.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Props
+
+All [shared layout and style attributes](layout) are supported, plus:
+
+- `name` - Icon name (required, string). See the [Icons](icons) reference for available names
+- `size` - Icon size in dp (optional, float, default: `24`)
+- `color` - Icon color as hex string (optional, default: platform default)
+
+
+
+## Examples
+
+### Basic icons
+
+@verbatim
+```blade
+
+
+
+
+
+
+```
+@endverbatim
+
+### Colored icon with label
+
+@verbatim
+```blade
+
+
+ Verified
+
+```
+@endverbatim
+
+### Large icon
+
+@verbatim
+```blade
+
+
+ No messages
+
+```
+@endverbatim
+
+### Platform-specific icon
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\Icon;
+
+Icon::make('home')->size(24)->color('#1E293B');
+```
+
+- `make(string $name = '')` - Create an icon
+- `size(float $size)` - Icon size in dp
+- `color(string $hex)` - Icon color
diff --git a/resources/views/docs/mobile/4/edge-components/icons.md b/resources/views/docs/mobile/4/edge-components/icons.md
new file mode 100644
index 00000000..bfbe1cb3
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/icons.md
@@ -0,0 +1,288 @@
+---
+title: Icons
+order: 9999
+---
+
+## Overview
+
+NativePHP EDGE components use a smart icon mapping system that automatically converts icon names to platform-specific
+icons. On iOS, icons render as [SF Symbols](https://developer.apple.com/sf-symbols/), while Android uses
+[Material Icons](https://fonts.google.com/icons?icon.set=Material+Icons).
+
+You don't need to worry about the differences! Just use a single, consistent icon name in your components, and the EDGE
+handles the platform translation automatically.
+
+## How It Works
+
+The icon system uses a four-tier resolution strategy:
+
+1. **Direct Platform Icons** - On iOS, if the name contains a `.` it's used as a direct SF Symbol path (e.g., `car.side.fill`). On Android, any Material Icon ligature name works directly (e.g., `shopping_cart`).
+2. **Manual Mapping** - Explicit mappings for common icons and aliases (e.g., `home`, `settings`, `user`)
+3. **Smart Fallback** - Attempts to auto-convert unmapped icon names to platform equivalents
+4. **Default Fallback** - Uses a circle icon if no match is found
+
+This approach means you can use intuitive icon names for common cases, leverage direct platform icons for advanced use
+cases, and get consistent results across iOS and Android.
+
+## Platform Differences
+
+### iOS (SF Symbols)
+
+On iOS, icons render as SF Symbols. Manual mappings convert common icon names to their SF Symbol equivalents.
+For example:
+
+- `home` → `house.fill`
+- `settings` → `gearshape.fill`
+- `check` → `checkmark.circle.fill`
+
+If an icon name isn't manually mapped, the system attempts to find a matching SF Symbol by trying variations like
+`.fill`, `.circle.fill`, and `.square.fill`.
+
+### Android (Material Icons)
+
+On Android, icons render using a lightweight font-based approach that supports the entire Material Icons library. You
+can use any Material Icon by its ligature name directly (e.g., `shopping_cart`, `qr_code_2`).
+
+Manual mappings provide convenient aliases for common icon names. For example:
+
+- `home` → `home`
+- `settings` → `settings`
+- `check` → `check`
+- `cart` → `shopping_cart`
+
+## Direct Platform Icons
+
+For advanced use cases, you can use platform-specific icon names directly.
+
+### iOS SF Symbols
+
+On iOS, include a `.` in the icon name to use an SF Symbol path directly:
+
+@verbatim
+```blade
+
+
+
+```
+@endverbatim
+
+### Android Material Icons
+
+On Android, use any Material Icon ligature name (with underscores):
+
+@verbatim
+```blade
+
+
+
+```
+@endverbatim
+
+## Platform-Specific Icons
+
+When you need different icons on each platform, use the `System` facade:
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+This is useful when the mapped icon doesn't match your needs or you want to use platform-specific variants.
+
+## Basic Usage
+
+Use the `icon` attribute in any EDGE component that supports icons, simply passing the name of the icon you wish to use:
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Icon Reference
+
+All icons listed here are manually mapped and guaranteed to work consistently across iOS and Android.
+
+### Navigation
+
+| Icon | Description |
+|------|-------------|
+| `dashboard` | Grid-style dashboard view |
+| `home` | House/home screen |
+| `menu` | Three-line hamburger menu |
+| `settings` | Gear/settings |
+| `account`, `profile`, `user` | User account or profile |
+| `person` | Single person |
+| `people`, `connections`, `contacts` | Multiple people |
+| `group`, `groups` | Group of people |
+
+### Business & Commerce
+
+| Icon | Description |
+|------|-------------|
+| `orders`, `receipt` | Receipt or order |
+| `cart`, `shopping` | Shopping cart |
+| `shop`, `store` | Store or storefront |
+| `products`, `inventory` | Products or inventory |
+
+### Charts & Data
+
+| Icon | Description |
+|------|-------------|
+| `chart`, `barchart` | Bar chart |
+| `analytics` | Analytics/analysis |
+| `summary`, `report`, `assessment` | Summary or report |
+
+### Time & Scheduling
+
+| Icon | Description |
+|------|-------------|
+| `clock`, `schedule`, `time` | Clock or time |
+| `calendar` | Calendar |
+| `history` | History or recent |
+
+### Actions
+
+| Icon | Description |
+|------|-------------|
+| `add`, `plus` | Add or create new |
+| `edit` | Edit or modify |
+| `delete` | Delete or remove |
+| `save` | Save |
+| `search` | Search |
+| `filter` | Filter |
+| `refresh` | Refresh or reload |
+| `share` | Share |
+| `download` | Download |
+| `upload` | Upload |
+
+### Communication
+
+| Icon | Description |
+|------|-------------|
+| `notifications` | Notifications or alerts |
+| `message` | Message or SMS |
+| `email`, `mail` | Email |
+| `chat` | Chat or conversation |
+| `phone` | Phone or call |
+
+### Navigation Arrows
+
+| Icon | Description |
+|------|-------------|
+| `back` | Back or previous |
+| `forward` | Forward or next |
+| `up` | Up arrow |
+| `down` | Down arrow |
+
+### Status
+
+| Icon | Description |
+|------|-------------|
+| `check`, `done` | Check or complete |
+| `close` | Close or dismiss |
+| `warning` | Warning |
+| `error` | Error |
+| `info` | Information |
+
+### Authentication
+
+| Icon | Description |
+|------|-------------|
+| `login` | Login |
+| `logout`, `exit` | Logout or exit |
+| `lock` | Locked |
+| `unlock` | Unlocked |
+
+### Content
+
+| Icon | Description |
+|------|-------------|
+| `favorite`, `heart` | Favorite or like |
+| `star` | Star or rating |
+| `bookmark` | Bookmark |
+| `image`, `photo` | Image or photo |
+| `image-plus` | Add photo |
+| `video` | Video |
+| `folder` | Folder |
+| `folder-lock` | Locked folder |
+| `file`, `description` | Document or file |
+| `book-open` | Book |
+| `newspaper`, `news`, `article` | News or article |
+
+### Device & Hardware
+
+| Icon | Description |
+|------|-------------|
+| `camera` | Camera |
+| `qr`, `qrcode`, `qr-code` | QR code scanner |
+| `device-phone-mobile`, `smartphone` | Mobile phone |
+| `vibrate` | Vibration |
+| `bell` | Bell or notification |
+| `finger-print`, `fingerprint` | Fingerprint or biometric |
+| `light-bulb`, `lightbulb`, `flashlight` | Light bulb or flashlight |
+| `map`, `location` | Map or location |
+| `globe-alt`, `globe`, `web` | Globe or web |
+| `bolt`, `flash` | Lightning bolt or flash |
+
+### Audio & Volume
+
+| Icon | Description |
+|------|-------------|
+| `speaker`, `speaker-wave` | Speaker with sound |
+| `volume-up` | Volume up |
+| `volume-down` | Volume down |
+| `volume-mute`, `mute` | Muted |
+| `volume-off` | Volume off |
+| `music`, `audio`, `music-note` | Music or audio |
+| `microphone`, `mic` | Microphone |
+
+### Miscellaneous
+
+| Icon | Description |
+|------|-------------|
+| `help` | Help or question |
+| `about`, `information-circle` | Information or about |
+| `more` | More options |
+| `list` | List view |
+| `visibility` | Visible |
+| `visibility_off` | Hidden |
+
+## Best Practices
+
+Icons have meaning and most users will associate the visual cues of icons and the underlying behavior or section of an
+application across apps. So try to maintain consistent use of icons to help guide users through your app.
+
+- **Stay consistent** - Use the same icon name throughout your app for the same action
+- **Test on both platforms** - If you use auto-converted icons, verify they appear correctly on iOS and Android
+
+## Finding Icons
+
+### Android Material Icons
+
+Browse the complete Material Icons library at [Google Fonts Icons](https://fonts.google.com/icons). Use the icon name
+exactly as shown (with underscores, e.g., `shopping_cart`, `qr_code_2`).
+
+### iOS SF Symbols
+
+Browse SF Symbols using this [community Figma file](https://www.figma.com/community/file/1549047589273604548). While not
+comprehensive, it's a great starting point for discovering available symbols.
+
+For the complete library, download the [SF Symbols app](https://developer.apple.com/sf-symbols/) for macOS.
+
+
diff --git a/resources/views/docs/mobile/4/edge-components/image.md b/resources/views/docs/mobile/4/edge-components/image.md
new file mode 100644
index 00000000..5b249974
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/image.md
@@ -0,0 +1,98 @@
+---
+title: Image
+order: 320
+---
+
+## Overview
+
+Displays an image from a URL. Loaded asynchronously by the native platform — `AsyncImage` on iOS, Coil on Android.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Props
+
+All [shared layout and style attributes](layout) are supported, plus:
+
+- `src` - Image URL (required, string)
+- `fit` - Content fit mode (optional, int, default: `1`):
+ - `0` / `1` — fit (scale to fit within bounds, preserving aspect ratio)
+ - `2` / `3` — fill (scale to fill bounds, cropping excess)
+- `tint-color` - Apply a color tint as hex string (optional)
+
+
+
+## Examples
+
+### Basic image
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Rounded avatar
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Tinted icon image
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Image in a card
+
+@verbatim
+```blade
+
+
+
+ Article Title
+ A brief description of the article.
+
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Native\Mobile\Edge\Elements\Image;
+
+Image::make('https://example.com/photo.jpg')
+ ->fit(2)
+ ->tintColor('#7C3AED');
+```
+
+- `make(string $src = '')` - Create an image with a source URL
+- `fit(int $mode)` - `0`/`1` = fit, `2`/`3` = fill
+- `tintColor(string $hex)` - Apply a color tint
diff --git a/resources/views/docs/mobile/4/edge-components/introduction.md b/resources/views/docs/mobile/4/edge-components/introduction.md
new file mode 100644
index 00000000..a3be6cf4
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/introduction.md
@@ -0,0 +1,156 @@
+---
+title: Introduction
+order: 1
+---
+
+## What is EDGE?
+
+EDGE (Element Definition and Generation Engine) is NativePHP for Mobile's component system that transforms Blade
+template syntax into platform-native UI elements that look beautiful whichever device your users are using.
+
+
+
+Instead of rendering in the web view, EDGE components are compiled into truly native elements and live apart from the
+web view's lifecycle. This means they are persistent and offer truly native performance.
+
+There's no custom rendering engine and complex ahead-of-time compilation process, just a lightweight transformation
+step that happens at runtime. You end up with pure, fast and flexible native components — all configured by PHP!
+
+## Available Components
+
+EDGE provides a full suite of native UI components for building your app, from layout containers and typography to
+interactive forms and navigation chrome.
+
+### Layout
+
+- **[Layout & Styling](layout)** - Shared sizing, spacing, flex, style, and event attributes available on all elements
+- **[Screen](screen)** - Themed page-level container
+- **[Column](column)** - Vertical flex container
+- **[Row](row)** - Horizontal flex container
+- **[Scroll View](scroll-view)** - Scrollable container with virtualization
+- **[Stack](stack)** - Overlay container (ZStack) for layering elements
+- **[Spacer](spacer)** - Flexible space element
+- **[Pressable](pressable)** - Touch-sensitive container wrapper
+- **[Card](card)** - Content surface with filled / outlined / elevated variants
+- **[Web View](web-view)** - Embed web content as a native element and surround it with native UI
+
+### Content
+
+- **[Text](text)** - Text display with font sizing, weight, color, and alignment
+- **[Image](image)** - Image display with fit modes and tinting
+- **[Icon](icon)** - Platform-native icons (SF Symbols on iOS, Material Icons on Android)
+- **[Divider](divider)** - Horizontal line separator
+- **[Activity Indicator](activity-indicator)** - Loading spinner
+- **[Progress Bar](progress-bar)** - Linear progress indicator
+- **[Badge](badge)** - Count or label pill marker
+
+### Forms
+
+- **[Button](button)** - Tappable button with variants, sizes, and disabled / loading state
+- **[Button Group](button-group)** - Segmented single-choice selector
+- **[Text Input](text-input)** - Outlined and filled text input variants
+- **[Toggle](toggle)** - On/off switch control
+- **[Checkbox](checkbox)** - Tick/untick control with optional inline label
+- **[Radio Group](radio-group)** - Single-choice radio selector
+- **[Select](select)** - Dropdown picker
+- **[Slider](slider)** - Continuous (or stepped) value selector
+- **[Chip](chip)** - Compact selectable tag
+
+### Navigation
+
+- **[Bottom Navigation](bottom-nav)** - The always-accessible bottom navigation bar
+- **[Top Bar](top-bar)** - A title bar with action buttons
+- **[Side Navigation](side-nav)** - A slide-out navigation drawer
+- **[Tab Row](tab-row)** - Horizontal tab strip for in-screen sectioning
+
+### Lists & data
+
+- **[List](list)** - Virtualized list with pull-to-refresh, end-reached, and swipe actions
+- **[List Item](list)** - Material3 row with leading + trailing slot system
+- **[Carousel](carousel)** - Horizontal paging carousel
+
+### Overlays
+
+- **[Bottom Sheet](bottom-sheet)** - Modal bottom sheet for contextual actions and forms
+- **[Modal](modal)** - Full-screen modal overlay
+
+### Drawing
+
+- **[Canvas](canvas)** - Drawing surface for shape primitives
+- **[Shapes](shapes)** - Rect, circle, and line elements
+
+## How It Works
+
+@verbatim
+```blade
+
+
+
+```
+@endverbatim
+
+You simply define your components in Blade and EDGE processes these during each request, passing instructions to the
+native side. The native UI rendering pipeline takes over to generate your defined components and builds the interface
+just the way your users would expect, enabling your app to use the latest and greatest parts of each platform,
+such as Liquid Glass on iOS.
+
+Under the hood, the Blade components are compiled down to a simple JSON configuration which we pass to the native side.
+The native code already contains the generic components compiled-in. These are then rendered as needed based on the
+JSON configuration.
+
+
+
+## Why Blade?
+
+Blade is an expressive and straightforward templating language that is very familiar to most Laravel users, and also
+super accessible to anyone who's used to writing HTML. All of our components are Blade components, which allows us to
+use Blade's battle-tested processing engine to rapidly compile the necessary transformation just in time.
+
+## Where to define your native components
+
+They can be defined in any Blade file, but for them to be processed, that Blade file will need to be rendered. We
+recommend putting your components in a Blade component that is likely to be rendered on every request, such as your
+main layout, e.g. `layouts/app.blade.php` or one of its child views/components.
+
+## Props Validation
+
+EDGE components enforce required props validation to prevent misconfiguration. If you're missing required props, you'll
+see a clear error message that tells you exactly what's missing and how to fix it.
+
+For example, if you forget the `label` prop on a bottom navigation item:
+
+```
+EDGE Component
is missing required properties: 'label'.
+Add these attributes to your component: label="..."
+```
+
+The error message will list all missing required props and show you exactly which attributes you need to add. This
+validation happens at render time, making it easy to catch configuration issues during development.
+
+Each component's documentation page indicates which props are required vs optional.
+
+## Using Inertia?
+
+Each link in an EDGE component will do a full post back to PHP, which may not be what you want if you are using Inertia. To transform these requests into Inertia ``, add `router` to your `window` object:
+
+```typescript
+import { router } from '@inertiajs/vue3';
+
+declare global {
+ interface Window {
+ router: typeof router;
+ }
+}
+
+window.router = router;
+```
diff --git a/resources/views/docs/mobile/4/edge-components/layout.md b/resources/views/docs/mobile/4/edge-components/layout.md
new file mode 100644
index 00000000..520e64c0
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/layout.md
@@ -0,0 +1,328 @@
+---
+title: Layout & Styling
+order: 150
+---
+
+## Overview
+
+Every EDGE element shares a common set of layout, styling, and event attributes. These are inherited from the base
+`Element` class and can be applied to any native component -- containers, text, buttons, images, and more.
+
+This page documents the shared attribute system that powers the layout engine across all EDGE elements.
+
+## Sizing
+
+Control element dimensions with width, height, and fill attributes.
+
+@verbatim
+```blade
+{{-- Fixed dimensions (in dp) --}}
+
+ ...
+
+
+{{-- Fill available space --}}
+
+ ...
+
+
+{{-- Fill both axes --}}
+
+ ...
+
+
+{{-- Percentage width --}}
+
+ ...
+
+```
+@endverbatim
+
+- `width` - Width in dp (float) or percentage string (e.g. `"50%"`)
+- `height` - Height in dp (float) or percentage string
+- `fill` - Fill both width and height of parent (boolean)
+- `fill-width` - Fill parent width (boolean)
+- `fill-height` - Fill parent height (boolean)
+- `min-width` - Minimum width in dp (float)
+- `max-width` - Maximum width in dp (float)
+- `min-height` - Minimum height in dp (float)
+- `max-height` - Maximum height in dp (float)
+- `aspect-ratio` - Width-to-height ratio (float, e.g. `1.0` for square)
+
+## Spacing
+
+Padding and margin follow CSS shorthand conventions. Pass a single value for uniform spacing, or an array for
+per-side control.
+
+@verbatim
+```blade
+{{-- Uniform padding --}}
+
+ ...
+
+
+{{-- Vertical | Horizontal --}}
+
+ ...
+
+
+{{-- Top | Right | Bottom | Left --}}
+
+ ...
+
+
+{{-- Uniform margin --}}
+
+ ...
+
+
+{{-- Gap between children --}}
+
+ ...
+
+```
+@endverbatim
+
+- `padding` - Inner spacing. Single value (float) or array of 2-4 values
+- `margin` - Outer spacing. Single value (float) or array of 2-4 values
+- `gap` - Space between children in dp (float)
+
+## Flex Layout
+
+The layout engine uses a Flexbox-based system. Containers (column, row) arrange children along a main axis, and flex
+properties control how children grow, shrink, and align.
+
+@verbatim
+```blade
+{{-- Grow to fill remaining space --}}
+
+ ...
+
+
+{{-- Prevent shrinking --}}
+
+ ...
+
+```
+@endverbatim
+
+- `flex-grow` - How much this element grows relative to siblings (float, default: `0`)
+- `flex-shrink` - How much this element shrinks when space is limited (float)
+- `flex-basis` - Initial size before flex distribution (float or string)
+
+
+
+## Alignment
+
+Alignment values are integers that map to standard flex alignment:
+
+| Value | Meaning |
+|-------|---------|
+| `0` | start |
+| `1` | center |
+| `2` | end |
+| `3` | stretch |
+| `4` | baseline |
+
+@verbatim
+```blade
+{{-- Center children on both axes --}}
+
+ ...
+
+
+{{-- Cross-axis alignment (horizontal in a column) --}}
+
+ Centered text
+
+
+{{-- Main-axis distribution --}}
+
+ Left
+ Right
+
+```
+@endverbatim
+
+- `align-items` - Cross-axis alignment for children (int, 0-4)
+- `justify-content` - Main-axis distribution (int, 0=start, 1=center, 2=end, 3=space-between, 4=space-around, 5=space-evenly)
+- `align-self` - Override parent's `align-items` for this element (int, 0-4)
+- `center` - Shorthand: sets both `align-items` and `justify-content` to center (boolean)
+
+
+
+## Style
+
+Visual styling attributes that apply to any element.
+
+@verbatim
+```blade
+
+ ...
+
+```
+@endverbatim
+
+- `bg` - Background color as hex string (e.g. `"#FF0000"`, `"#80FF000080"` for alpha)
+- `border-radius` - Corner rounding in dp (float)
+- `border-width` - Border width in dp (float). Must be used together with `border-color`
+- `border-color` - Border color as hex string. Must be used together with `border-width`
+- `opacity` - Element opacity from 0.0 to 1.0 (float)
+- `elevation` - Shadow depth (float). Maps to platform shadow/elevation
+
+## Events
+
+Any element can respond to press and long-press gestures. Use `@press` and `@longPress` directives to bind methods on
+the route's PHP component class.
+
+@verbatim
+```blade
+
+ Tap or long press me
+
+```
+@endverbatim
+
+- `@press` - PHP method to call on tap
+- `@longPress` - PHP method to call on long press
+
+## Safe Area
+
+Respect the device's safe area insets (notch, home indicator, status bar) by adding the `safe-area` attribute. This is
+typically applied to your outermost column.
+
+@verbatim
+```blade
+
+ {{-- Content will not overlap the notch or home indicator --}}
+
+```
+@endverbatim
+
+- `safe-area` - Inset content on both top and bottom edges (boolean)
+- `safe-area-top` - Inset only the top edge (status bar / notch)
+- `safe-area-bottom` - Inset only the bottom edge (home indicator)
+
+See [Safe Area](../the-basics/safe-area) for the full picture, including how the framework's [layout](../the-basics/layouts)
+chrome already handles safe-area insets for you.
+
+## Visibility
+
+Hide elements without removing them from the tree.
+
+@verbatim
+```blade
+
+ {{-- This element is not displayed --}}
+
+```
+@endverbatim
+
+- `hidden` - Hide this element (boolean)
+
+## Dark Mode
+
+Override styles for dark mode using the `dark:` prefix with Tailwind classes, or pass a `dark` attribute array.
+
+@verbatim
+```blade
+{{-- Tailwind dark mode --}}
+
+
+ Adapts to dark mode
+
+
+```
+@endverbatim
+
+Dark mode overrides currently support `bg`, `color`, `border-color`, `opacity`, and `font-size`.
+
+## Tailwind Classes
+
+EDGE includes a built-in Tailwind CSS parser that converts familiar utility classes into native layout attributes. Use
+the `class` attribute on any element.
+
+@verbatim
+```blade
+
+
+ Styled with Tailwind
+
+
+```
+@endverbatim
+
+### Supported Tailwind classes
+
+The parser recognizes the classes listed below.
+
+| Category | Classes |
+|----------|---------|
+| Width | `w-full`, `w-N`, fractional (`w-1/2`, `w-1/3`, `w-2/3`, `w-1/4`, `w-3/4`, `w-1/5`…), arbitrary `w-[N]` |
+| Height | `h-full`, `h-N`, arbitrary `h-[N]` |
+| Padding | `p-N`, `px-N`, `py-N`, `pt-N`, `pr-N`, `pb-N`, `pl-N`, arbitrary `p-[N]` etc. |
+| Margin | `m-N`, `mx-N`, `my-N`, `mt-N`, `mr-N`, `mb-N`, `ml-N`, arbitrary `m-[N]` etc. |
+| Gap | `gap-N`, `gap-[N]` (uniform — no `gap-x-*` or `gap-y-*`) |
+| Position | `absolute`, `relative`, `top-N`, `right-N`, `bottom-N`, `left-N`, arbitrary `top-[N]` etc. |
+| Flex | `flex-1`, `flex-grow`, `flex-grow-0`, `flex-shrink`, `flex-shrink-0` |
+| Items (cross-axis) | `items-start`, `items-center`, `items-end`, `items-stretch` |
+| Justify (main-axis) | `justify-start`, `justify-center`, `justify-end`, `justify-between`, `justify-around`, `justify-evenly` |
+| Self | `self-start`, `self-center`, `self-end`, `self-stretch` |
+| Background | `bg-{palette}-{shade}` (e.g. `bg-red-500`), `bg-white`, `bg-black`, `bg-transparent`, `bg-[#hex]`, `bg-theme-{token}` |
+| Text color | `text-{palette}-{shade}`, `text-white`, `text-black`, `text-transparent`, `text-[#hex]`, `text-theme-{token}` |
+| Border color | `border-{palette}-{shade}`, `border-white`, `border-black`, `border-transparent`, `border-[#hex]`, `border-theme-{token}` |
+| Border width | `border` (1dp), `border-2`, `border-4`, `border-8` |
+| Rounded | `rounded` (4dp), `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-2xl`, `rounded-3xl`, `rounded-full`, `rounded-[N]` |
+| Shadow | `shadow`, `shadow-sm`, `shadow-md`, `shadow-lg`, `shadow-xl`, `shadow-2xl`, `shadow-inner`, `shadow-none` |
+| Opacity | `opacity-{0..100}`, arbitrary `opacity-[0.5]` |
+| 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` |
+| 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`) |
+
+**Variants** — prepend any class:
+
+| Prefix | Effect |
+|----------|---------|
+| `dark:` | Applies in dark mode (e.g. `dark:bg-zinc-900`) |
+| `ios:` | Applies on iOS only — drops silently on Android |
+| `android:` | Applies on Android only — drops silently on iOS |
+
+Variants compose freely: `ios:dark:bg-zinc-800`, `dark:ios:bg-zinc-800` — both work.
+
+**Alpha suffix** — append `/N` to any color class for opacity (Tailwind v3+ syntax):
+
+```
+bg-purple-500/40 bg-[#FF0000]/60 text-white/80
+border-theme-outline/50
+```
+
+**Arbitrary values** — `prefix-[value]` for the prefixes shown above: `w`, `h`, `p`/`px`/`py`/`pt`/`pr`/`pb`/`pl`,
+`m`/`mx`/`my`/`mt`/`mr`/`mb`/`ml`, `gap`, `bg`, `text`, `border`, `rounded`, `opacity`, `top`, `right`, `bottom`, `left`.
+
+
+
diff --git a/resources/views/docs/mobile/4/edge-components/list.md b/resources/views/docs/mobile/4/edge-components/list.md
new file mode 100644
index 00000000..dfa23462
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/list.md
@@ -0,0 +1,202 @@
+---
+title: List
+order: 280
+---
+
+## Overview
+
+A virtualized list container. On iOS, renders as a SwiftUI `List` with native pull-to-refresh and trailing
+swipe-to-delete. On Android, renders as a `LazyColumn` / `LazyRow`.
+
+Pair with [``](#list-item) for Material3 list rows, or use any EDGE element as a child.
+
+@verbatim
+```blade
+
+ @foreach($contacts as $contact)
+ id }})"
+ />
+ @endforeach
+
+```
+@endverbatim
+
+## Props
+
+- `horizontal` - Lay out children horizontally instead of vertically (optional, boolean, default: `false`)
+- `shows-indicators` - Show scroll indicators (optional, boolean, default: `false`) [iOS]
+- `separator` - Render dividers between rows (optional, boolean, default: `false`) [iOS]
+- `on-refresh` - Livewire method called on pull-to-refresh (optional, string) [iOS]
+- `on-end-reached` - Livewire method called when the user nears the end of the list (optional, string)
+
+## Children
+
+Accepts any EDGE elements as children. `` is the canonical child for Material3-style rows.
+
+## List Item
+
+A pre-styled Material3 row with a headline, optional supporting + overline text, and configurable leading + trailing
+content slots.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Text props
+
+- `headline` - Primary text (required, string)
+- `supporting` - Secondary text rendered below the headline (optional, string)
+- `overline` - Small caption rendered above the headline (optional, string)
+
+### Leading slot (mutually exclusive)
+
+- `leadingIcon` - Icon name rendered as a leading icon
+- `leadingAvatar` - URL of a circular avatar image
+- `leadingMonogram` - 1-2 character monogram (combine with `leadingMonogramColor`)
+- `leadingMonogramColor` - Hex color for monogram background
+- `leadingImage` - URL of a square image with a small radius
+- `leadingCheckbox` - Boolean value for a leading checkbox
+- `leadingRadio` - Boolean value for a leading radio button
+
+### Trailing slot (mutually exclusive)
+
+- `trailingIcon` - Icon name rendered as a trailing icon
+- `trailingText` - Trailing text label
+- `trailingCheckbox` - Boolean value for a trailing checkbox
+- `trailingSwitch` - Boolean value for a trailing switch [Android]
+- `trailingIconButton` - Icon name for a tappable trailing button
+
+### Color overrides
+
+- `headlineColor`, `supportingColor`, `overlineColor` - Hex colors for the text styles
+- `containerColor` - Row background color
+- `leadingIconColor`, `trailingIconColor`, `trailingTextColor` - Colors for the slot content
+
+### State
+
+- `disabled` - Disable the row (optional, boolean, default: `false`)
+- `tonalElevation` - Tonal elevation in dp [Android]
+- `shadowElevation` - Shadow elevation in dp [Android]
+
+### Events
+
+- `@press` / `@longPress` - Standard press handlers on the row
+- `on-swipe-delete` - Livewire method invoked when the user swipes the row to delete [iOS]
+
+## Examples
+
+### Settings menu
+
+@verbatim
+```blade
+
+
+
+
+
+
+```
+@endverbatim
+
+### Swipe-to-delete with pull-to-refresh
+
+@verbatim
+```blade
+
+ @foreach($tasks as $task)
+ id }})"
+ />
+ @endforeach
+
+```
+@endverbatim
+
+### Infinite scroll
+
+@verbatim
+```blade
+
+ @foreach($posts as $post)
+
+ @endforeach
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\NativeList;
+use Nativephp\NativeUi\Elements\ListItem;
+
+NativeList::make(
+ ListItem::make('Profile')->leadingIcon('person')->trailingIcon('forward'),
+ ListItem::make('Settings')->leadingIcon('settings')->trailingIcon('forward'),
+)
+ ->separator()
+ ->onRefresh('refresh')
+ ->onEndReached('loadMore');
+```
+
+### `NativeList` methods
+
+- `make(Element ...$children)` - Create a list with children
+- `horizontal(bool $value = true)` - Horizontal layout
+- `showsIndicators(bool $value = true)` - Show scroll indicators
+- `separator(bool $value = true)` - Render dividers between rows
+- `onRefresh(string $method)` - Pull-to-refresh handler
+- `onEndReached(string $method)` - End-reached handler
+
+### `ListItem` methods
+
+Text:
+
+- `make(string $headline = '')`, `supporting(string $text)`, `overline(string $text)`
+
+Leading slot:
+
+- `leadingIcon(string $icon)`
+- `leadingAvatar(string $url)`
+- `leadingMonogram(string $initials, ?string $color = null)`
+- `leadingImage(string $url)`
+- `leadingCheckbox(bool $checked = false)`
+- `leadingRadio(bool $selected = false)`
+
+Trailing slot:
+
+- `trailingIcon(string $icon)`
+- `trailingText(string $text)`
+- `trailingCheckbox(bool $checked = false)`
+- `trailingSwitch(bool $checked = false)`
+- `trailingIconButton(string $icon)`
+
+Styling:
+
+- `headlineColor`, `supportingColor`, `overlineColor`, `containerColor`,
+ `leadingIconColor`, `trailingIconColor`, `trailingTextColor` (all `(string $color)`)
+- `tonalElevation(float $dp)`, `shadowElevation(float $dp)`
+
+Callbacks:
+
+- `onLeadingChange(string $method)`, `onTrailingChange(string $method)`,
+ `onTrailingPress(string $method)`, `onSwipeDelete(string $method)`
+- `disabled(bool $disabled = true)`
diff --git a/resources/views/docs/mobile/4/edge-components/modal.md b/resources/views/docs/mobile/4/edge-components/modal.md
new file mode 100644
index 00000000..9a30eaea
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/modal.md
@@ -0,0 +1,90 @@
+---
+title: Modal
+order: 710
+---
+
+## Overview
+
+A full-screen modal overlay. Visibility is driven by the `visible` prop. Use a [bottom sheet](bottom-sheet) for
+contextual actions; reach for `` when you want the entire screen covered (e.g. an onboarding flow,
+image preview, or detail view).
+
+Per Model 3, backdrop and surface colors come from `theme.background`. The close icon uses `theme.onSurfaceVariant`.
+
+@verbatim
+```blade
+
+
+ Details
+ {{ $item->description }}
+
+
+```
+@endverbatim
+
+## Props
+
+- `visible` - Whether the modal is shown (required, boolean)
+- `dismissible` - Render a close icon and allow swipe-to-dismiss (optional, boolean, default: `true`)
+- `a11y-label` - Accessibility label (optional)
+
+## Events
+
+- `@dismiss` - Livewire method called when the user dismisses the modal (close button tap or swipe). Always handle
+ this to keep your `visible` state in sync
+
+## Children
+
+Accepts any EDGE elements as children. The children are rendered inside the modal's content area below the
+auto-supplied close button (when `dismissible`).
+
+
+
+## Examples
+
+### Image preview
+
+@verbatim
+```blade
+
+
+
+
+
+```
+@endverbatim
+
+### Non-dismissible loading modal
+
+@verbatim
+```blade
+
+
+
+ Processing...
+
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\Modal;
+
+Modal::make()
+ ->visible($showDetails)
+ ->dismissible(true)
+ ->onDismiss('closeDetails');
+```
+
+- `make()` - Create a modal
+- `visible(bool $value = true)` - Toggle visibility
+- `dismissible(bool $value = true)` - Allow user dismissal
+- `a11yLabel(string $value)` - Accessibility label
+- `onDismiss(string $method)` - Livewire method invoked on dismissal
diff --git a/resources/views/docs/mobile/4/edge-components/pressable.md b/resources/views/docs/mobile/4/edge-components/pressable.md
new file mode 100644
index 00000000..2d118b48
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/pressable.md
@@ -0,0 +1,100 @@
+---
+title: Pressable
+order: 250
+---
+
+## Overview
+
+A touch-sensitive container that wraps its children in a tappable area. Pressable is structurally identical to a column
+but is specifically designed for handling tap and long-press gestures on a group of elements.
+
+While any element can handle `@press` and `@longPress` events, `` makes the intent explicit and
+provides a clear tap target that wraps multiple children.
+
+@verbatim
+```blade
+id }})" class="w-full p-4 rounded-xl" bg="#FFFFFF">
+ {{ $item->name }}
+ {{ $item->description }}
+
+```
+@endverbatim
+
+## Props
+
+All [shared layout and style attributes](layout) are supported. There are no pressable-specific props beyond the
+standard event handlers.
+
+## Events
+
+- `@press` - Livewire method to call on tap
+- `@longPress` - Livewire method to call on long press
+
+## Children
+
+Accepts any EDGE elements as children. Children are arranged vertically (like a column).
+
+## Examples
+
+### Tappable list item
+
+@verbatim
+```blade
+@foreach($items as $item)
+ id }})"
+ class="w-full px-4 py-3"
+ >
+
+
+
+ {{ $item->name }}
+ {{ $item->subtitle }}
+
+
+
+
+ @unless($loop->last)
+
+ @endunless
+@endforeach
+```
+@endverbatim
+
+### Card with tap and long press
+
+@verbatim
+```blade
+id }})"
+ @longPress="showOptions({{ $post->id }})"
+ class="w-full p-4 rounded-2xl gap-2"
+ bg="#FFFFFF"
+ :elevation="2"
+>
+ {{ $post->title }}
+ {{ $post->excerpt }}
+
+```
+@endverbatim
+
+### Navigation with @navigate
+
+@verbatim
+```blade
+id }}" class="w-full p-4">
+ {{ $item->name }}
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Native\Mobile\Edge\Elements\Pressable;
+
+Pressable::make($child1, $child2)->onPress('handleTap');
+```
+
+- `make(Element ...$children)` - Create a pressable with children. Inherits the standard layout / style API from
+ the base `Element` class
diff --git a/resources/views/docs/mobile/4/edge-components/progress-bar.md b/resources/views/docs/mobile/4/edge-components/progress-bar.md
new file mode 100644
index 00000000..249fcf23
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/progress-bar.md
@@ -0,0 +1,83 @@
+---
+title: Progress Bar
+order: 355
+---
+
+## Overview
+
+A linear progress indicator. When `value` is supplied, renders as determinate progress in `[0.0, 1.0]`. Without a
+value (or with `indeterminate`), renders an animated wave.
+
+For a circular spinner use [``](activity-indicator) instead.
+
+Per Model 3, the progress fill uses `theme.primary` and the track uses `theme.surfaceVariant`. The optional `color`
+prop is an escape hatch for non-theme containers.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Props
+
+- `value` - Current progress in `[0.0, 1.0]` (optional, float). Setting `value` implies `indeterminate=false`
+- `indeterminate` - Force indeterminate mode (optional, boolean, default: `false` when `value` is set, otherwise `true`)
+- `color` - Override the fill color as hex string (optional)
+- `track-color` - Override the track color as hex string (optional)
+- `a11y-label` - Accessibility label (optional)
+
+
+
+## Examples
+
+### Determinate
+
+@verbatim
+```blade
+
+
+ Uploading
+ {{ round($progress * 100) }}%
+
+
+
+```
+@endverbatim
+
+### Indeterminate
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### With color override
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\ProgressBar;
+
+ProgressBar::make()->value(0.65);
+
+ProgressBar::make()->indeterminate();
+```
+
+- `make()` - Create a progress bar
+- `value(float $val)` - Determinate progress in `[0.0, 1.0]`
+- `indeterminate(bool $value = true)` - Force indeterminate mode
+- `color(string $hex)` - Override the fill tint
+- `trackColor(string $hex)` - Override the track color
+- `a11yLabel(string $value)` - Accessibility label
diff --git a/resources/views/docs/mobile/4/edge-components/radio-group.md b/resources/views/docs/mobile/4/edge-components/radio-group.md
new file mode 100644
index 00000000..56246941
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/radio-group.md
@@ -0,0 +1,113 @@
+---
+title: Radio Group
+order: 530
+---
+
+## Overview
+
+A single-choice container holding `` children. The group owns the selection; each child declares its
+own `value` and label.
+
+Per Model 3, all colors come from theme tokens.
+
+@verbatim
+```blade
+
+
+
+
+
+```
+@endverbatim
+
+## Props (Group)
+
+- `value` - Currently selected `value` string (optional). Use `native:model` for two-way binding
+- `label` - Label text rendered above the group (optional, string)
+- `disabled` - Disable the entire group (optional, boolean, default: `false`)
+- `a11y-label` - Accessibility label (optional)
+- `a11y-hint` - Accessibility hint (optional)
+
+## Events
+
+- `@change` - Livewire method called when the selection changes. Receives the new value as a parameter
+
+## Two-way Binding
+
+`native:model` binds the group's selected value to a Livewire string property:
+
+@verbatim
+```blade
+
+
+
+
+```
+@endverbatim
+
+## Children
+
+`` declares a single option:
+
+- `value` - The option's value (required, string). Must be unique within the group
+- `label` - Inline label (optional, string)
+- `disabled` - Disable just this option (optional, boolean, default: `false`)
+
+## Examples
+
+### Plan picker
+
+@verbatim
+```blade
+
+
+
+
+
+
+```
+@endverbatim
+
+### Manual handler
+
+@verbatim
+```blade
+
+
+
+
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\RadioGroup;
+use Nativephp\NativeUi\Elements\Radio;
+
+RadioGroup::make(
+ Radio::make('free')->label('Free'),
+ Radio::make('pro')->label('Pro'),
+ Radio::make('team')->label('Team'),
+)
+ ->value($plan)
+ ->label('Choose a plan')
+ ->onChange('setPlan');
+```
+
+### `RadioGroup` methods
+
+- `make(Element ...$children)` - Create a group with radio children
+- `value(string $selectedValue)` - Currently selected value
+- `label(string $text)` - Group label
+- `disabled(bool $value = true)` - Disable the group
+- `a11yLabel(string $value)`, `a11yHint(string $value)` - Accessibility
+- `syncMode(string $mode)` - Set by `native:model` modifiers
+- `onChange(string $method)` - Livewire method invoked on selection change
+
+### `Radio` methods
+
+- `make(string $value = '')` - Create a radio with a value
+- `label(string $label)` - Inline label
+- `disabled(bool $value = true)` - Disable the option
diff --git a/resources/views/docs/mobile/4/edge-components/row.md b/resources/views/docs/mobile/4/edge-components/row.md
new file mode 100644
index 00000000..fe59ce2d
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/row.md
@@ -0,0 +1,92 @@
+---
+title: Row
+order: 210
+---
+
+## Overview
+
+A horizontal flex container that arranges its children from left to right. Use rows for side-by-side layouts, toolbars,
+and inline groupings.
+
+@verbatim
+```blade
+
+
+ 4.8 Rating
+
+```
+@endverbatim
+
+## Children
+
+Accepts any EDGE elements as children. Children are arranged horizontally from left to right.
+
+## Supported Tailwind classes
+
+Row inherits the full class set documented at [Layout & Styling](layout#supported-tailwind-classes). The classes that
+shape how a row behaves specifically:
+
+| Class | Effect on a row |
+|---|---|
+| `gap-N` | **Horizontal** spacing between children |
+| `items-*` | **Vertical** (cross-axis) alignment of children: `items-start`, `items-center`, `items-end`, `items-stretch` |
+| `justify-*` | **Horizontal** (main-axis) distribution: `justify-start`, `justify-center`, `justify-end`, `justify-between`, `justify-around`, `justify-evenly` |
+| `flex-1` | Fills remaining space in the parent flex container |
+
+Everything else from the shared list applies the same as on any element (`w-*`, `h-*`, `p-*`, `m-*`, `bg-*`,
+`rounded-*`, `shadow-*`, `dark:*`, `ios:*` / `android:*`, `glass:*`, alpha suffix `/N`, arbitrary `prefix-[value]`).
+
+## Examples
+
+### Toolbar with spacer
+
+@verbatim
+```blade
+
+ Title
+
+
+
+```
+@endverbatim
+
+### Evenly spaced items
+
+@verbatim
+```blade
+
+ One
+ Two
+ Three
+
+```
+@endverbatim
+
+### Inline label and value
+
+@verbatim
+```blade
+
+ Status
+
+
+ Active
+
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Native\Mobile\Edge\Elements\Row;
+use Native\Mobile\Edge\Elements\Text;
+
+Row::make(
+ Text::make('Left'),
+ Text::make('Right'),
+)->gap(8)->alignItems(1);
+```
+
+- `make(Element ...$children)` - Create a row with children. Layout / style fluent methods are inherited from the
+ base `Element` class — see [Layout & Styling](layout)
diff --git a/resources/views/docs/mobile/4/edge-components/screen.md b/resources/views/docs/mobile/4/edge-components/screen.md
new file mode 100644
index 00000000..c26473af
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/screen.md
@@ -0,0 +1,53 @@
+---
+title: Screen
+order: 100
+---
+
+## Overview
+
+A themed page-level container. Applies `theme.background` as the full-bleed backdrop and `theme.onBackground` as
+the default content color for descendants. Adapts automatically to the system's light/dark mode.
+
+Use it as the root of a page so background and default content color follow your theme everywhere — no need to
+add `bg-...` classes to every screen.
+
+@verbatim
+```blade
+
+
+
+ Welcome
+ Your dashboard.
+
+
+
+```
+@endverbatim
+
+## Props
+
+`` is intentionally props-less — it's a contextual backdrop, not a styled element. Layout
+attributes (`width`, `margin`, etc.) are still accepted in case you need to adjust position inside a parent, but
+typical usage is bare.
+
+## Children
+
+Accepts any EDGE elements as children. Most commonly wraps a single [``](scroll-view) or
+[``](column).
+
+
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\Screen;
+
+Screen::make();
+```
+
+- `make()` - Create a screen container
diff --git a/resources/views/docs/mobile/4/edge-components/scroll-view.md b/resources/views/docs/mobile/4/edge-components/scroll-view.md
new file mode 100644
index 00000000..cf31123b
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/scroll-view.md
@@ -0,0 +1,105 @@
+---
+title: Scroll View
+order: 220
+---
+
+## Overview
+
+A scrollable container for content that exceeds the available screen space. By default it scrolls vertically, but can
+be configured for horizontal scrolling. On the native side, this uses `LazyColumn`/`LazyRow` on Android and
+`ScrollView` on iOS for efficient rendering.
+
+@verbatim
+```blade
+
+
+ @foreach($items as $item)
+ {{ $item->name }}
+ @endforeach
+
+
+```
+@endverbatim
+
+## Props
+
+All [shared layout and style attributes](layout) are supported, plus:
+
+- `horizontal` - Scroll horizontally instead of vertically (optional, boolean, default: `false`)
+- `shows-indicators` - Show scroll indicators (optional, boolean, default: `true`) [iOS]
+
+
+
+## Children
+
+Accepts any EDGE elements as children. Typically wraps a single `` or `` that contains the
+scrollable content.
+
+## Examples
+
+### Vertical list
+
+@verbatim
+```blade
+
+
+ @foreach($posts as $post)
+
+ {{ $post->title }}
+ {{ $post->excerpt }}
+
+ @endforeach
+
+
+```
+@endverbatim
+
+### Horizontal carousel
+
+@verbatim
+```blade
+
+
+ @foreach($categories as $category)
+
+ {{ $category->name }}
+
+ @endforeach
+
+
+```
+@endverbatim
+
+### Full-page scrollable layout
+
+@verbatim
+```blade
+
+
+ Welcome
+
+ Scroll down to see more content.
+
+ {{-- Long content here --}}
+
+
+```
+@endverbatim
+
+
diff --git a/resources/views/docs/mobile/4/edge-components/select.md b/resources/views/docs/mobile/4/edge-components/select.md
new file mode 100644
index 00000000..ecc08abc
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/select.md
@@ -0,0 +1,96 @@
+---
+title: Select
+order: 540
+---
+
+## Overview
+
+A single-choice dropdown picker over a flat list of strings. On iOS, renders as a SwiftUI `Menu` (popover); on
+Android, as an M3 `ExposedDropdownMenuBox` with an outlined trigger.
+
+Per Model 3, colors and borders come from theme tokens.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Props
+
+- `options` - Array of option strings (required, array)
+- `value` - Currently selected option string (optional). Use `native:model` for two-way binding
+- `label` - Label rendered above the trigger (optional, string)
+- `placeholder` - Text shown when nothing is selected (optional, string)
+- `disabled` - Disable the picker (optional, boolean, default: `false`)
+- `a11y-label` - Accessibility label (optional)
+- `a11y-hint` - Accessibility hint (optional)
+
+## Events
+
+- `@change` - Livewire method called when the selection changes. Receives the new option string
+
+## Two-way Binding
+
+`native:model` binds the selected option to a Livewire string property:
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Examples
+
+### Country picker
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Manual handler
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\Select;
+
+Select::make()
+ ->label('Country')
+ ->placeholder('Select country')
+ ->options(['Australia', 'Canada', 'United States'])
+ ->value($country)
+ ->onChange('setCountry');
+```
+
+- `make()` - Create a select
+- `options(array $options)` - Option strings
+- `value(string $val)` - Current selection
+- `label(string $text)` - Label text
+- `placeholder(string $text)` - Empty-state text
+- `disabled(bool $value = true)` - Disable the picker
+- `a11yLabel(string $value)`, `a11yHint(string $value)` - Accessibility
+- `syncMode(string $mode)` - Set by `native:model` modifiers
+- `onChange(string $method)` - Livewire method invoked on change
diff --git a/resources/views/docs/mobile/4/edge-components/shapes.md b/resources/views/docs/mobile/4/edge-components/shapes.md
new file mode 100644
index 00000000..fc850494
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/shapes.md
@@ -0,0 +1,117 @@
+---
+title: Shapes
+order: 600
+---
+
+## Overview
+
+Shape primitives for drawing simple geometric forms. Three primitives are available:
+
+- `` — rectangle
+- `` — circle
+- `` — horizontal rule
+
+These are typically placed inside a [``](canvas) or used standalone for decorative accents.
+
+## Rect
+
+A rectangle filled with `bg`. All [shared layout and style attributes](layout) apply, so border, radius, opacity,
+and elevation behave as on any other element.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Props
+
+- `left` - X position offset in dp (optional, float). Used inside an absolutely-positioned parent
+- `top` - Y position offset in dp (optional, float)
+
+`` may optionally wrap children if you need to layer content on top of the fill.
+
+## Circle
+
+A circle filled with `bg`. Defaults to `border-radius: 9999` so any square-ish frame appears as a circle. For a
+perfect circle use equal `width` and `height`.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Props
+
+- `left` - X position offset in dp (optional, float)
+- `top` - Y position offset in dp (optional, float)
+
+`` is a self-closing element. It does not accept children.
+
+## Line
+
+A 1pt horizontal rule across the available width.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Props
+
+All [shared layout and style attributes](layout) are supported. The most useful:
+
+- `border-color` - Line color as hex string (optional, default: platform separator color)
+- `border-width` - Stroke thickness in dp (optional, float, default: `1`)
+
+
+
+## Examples
+
+### Status dot
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Colored badge background
+
+@verbatim
+```blade
+
+ New
+
+```
+@endverbatim
+
+### Decorative separator
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Native\Mobile\Edge\Elements\Rect;
+use Native\Mobile\Edge\Elements\Circle;
+use Native\Mobile\Edge\Elements\Line;
+
+Rect::make();
+Circle::make();
+Line::make();
+```
+
+All three expose `make()` and inherit the standard layout / style fluent API.
diff --git a/resources/views/docs/mobile/4/edge-components/side-nav.md b/resources/views/docs/mobile/4/edge-components/side-nav.md
new file mode 100644
index 00000000..778cc8ec
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/side-nav.md
@@ -0,0 +1,110 @@
+---
+title: Side Navigation
+order: 400
+---
+
+## Overview
+
+
+
+
+
+
+
+
+
+A slide-out navigation drawer with support for groups, headers, and dividers.
+
+@verbatim
+```blade
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+@endverbatim
+
+## Props
+
+- `gestures-enabled` - Swipe to open (default: `false`) [Android]
+- `label-visibility` - `labeled` (default), `selected`, or `unlabeled` (optional)
+- `dark` - Force dark mode (optional, boolean)
+
+
+
+## Children
+
+### ``
+
+- `title` - Title text (optional)
+- `subtitle` - Subtitle text (optional)
+- `icon` - A named [icon](icons) (optional)
+- `background-color` - Background color. Hex code (optional)
+- `show-close-button` - Show a close × (optional, default: `true`) [Android]
+- `pinned` - Keep header visible when scrolling (optional, default: `false`)
+
+### ``
+
+- `id` - Unique identifier (required)
+- `label` - Display text (required)
+- `icon` - A named [icon](icons) (required)
+- `url` - A URL to navigate to in the web view (required)
+- `active` - Highlight this item as active (optional, default: `false`)
+- `badge` - Badge text (optional)
+- `badge-color` - Hex code or named color (optional)
+
+
+
+### ``
+
+- `heading` - The group's heading (required)
+- `expanded` - Initially expanded (optional, default: `false`)
+- `icon` - Material icon (optional)
+
+### ``
+
+Add visual separators between navigation items. This item has no properties.
diff --git a/resources/views/docs/mobile/4/edge-components/slider.md b/resources/views/docs/mobile/4/edge-components/slider.md
new file mode 100644
index 00000000..0cab1eba
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/slider.md
@@ -0,0 +1,96 @@
+---
+title: Slider
+order: 545
+---
+
+## Overview
+
+A continuous (or stepped) value selector. The active track and thumb use `theme.primary` — there are no
+per-instance color overrides.
+
+`native:model` is supported with `live` / `blur` / `debounce` modifiers — useful for keeping PHP round-trips
+under control while the user drags.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Props
+
+- `value` - Current value (optional, float)
+- `min` - Minimum value (optional, float, default: `0`)
+- `max` - Maximum value (optional, float, default: `1`)
+- `step` - Snap increment (optional, float, default: `0` for continuous)
+- `disabled` - Disable the slider (optional, boolean, default: `false`)
+- `size` - `sm | md (default) | lg` (optional, string)
+- `a11y-label` - Accessibility label (optional)
+- `a11y-hint` - Accessibility hint (optional)
+
+## Events
+
+- `@change` - Livewire method called when the value changes. Receives the new float value
+
+## Two-way Binding
+
+@verbatim
+```blade
+{{-- Every drag tick fires --}}
+
+
+{{-- Only fires on drag release --}}
+
+
+{{-- Coalesce ticks into one event after 300ms idle --}}
+
+```
+@endverbatim
+
+`live` is the default and stress-tests the runtime's round-trip; `blur` is the most efficient for unsteady hands;
+`debounce` is the middle ground.
+
+## Examples
+
+### Volume slider
+
+@verbatim
+```blade
+
+
+ Volume
+ {{ $volume }}%
+
+
+
+```
+@endverbatim
+
+### Stepped picker
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\Slider;
+
+Slider::make()
+ ->value($volume)
+ ->min(0)
+ ->max(100)
+ ->step(1)
+ ->onChange('setVolume');
+```
+
+- `make()` - Create a slider
+- `value(float $val)`, `min(float $val)`, `max(float $val)`, `step(float $val)` - Range / current value
+- `disabled(bool $value = true)` - Disable the slider
+- `size(string $value)` - `sm | md | lg`
+- `a11yLabel(string $value)`, `a11yHint(string $value)` - Accessibility
+- `syncMode(string $mode)`, `debounceMs(int $ms)` - Set by `native:model` modifiers
+- `onChange(string $method)` - Livewire method invoked on change
diff --git a/resources/views/docs/mobile/4/edge-components/spacer.md b/resources/views/docs/mobile/4/edge-components/spacer.md
new file mode 100644
index 00000000..a706d560
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/spacer.md
@@ -0,0 +1,98 @@
+---
+title: Spacer
+order: 240
+---
+
+## Overview
+
+A flexible space element that expands to fill remaining space within a column or row. Spacers are the simplest way to
+push elements apart without calculating explicit sizes.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+
+
+## Default behavior
+
+Spacer ships with `flex-grow: 1` applied automatically. Inside a `` or ``, it claims all
+leftover space along the parent's main axis. Inside a non-flex parent (like ``), it does nothing — it has
+no children and no intrinsic size to draw.
+
+## Supported Tailwind classes
+
+The classes that affect how a spacer renders:
+
+| Class | Effect |
+|---|---|
+| `w-N`, `w-[N]`, `w-1/2` etc. | Lock the spacer to a fixed or fractional width |
+| `h-N`, `h-[N]` | Lock the spacer to a fixed height |
+| `flex-grow`, `flex-grow-0` | Override the default grow=1 (use `flex-grow-0` for a fixed-size spacer) |
+| `flex-shrink`, `flex-shrink-0` | Control shrink behavior |
+| `bg-*`, `bg-theme-*`, `bg-[#hex]`, alpha `/N` | Paint a visible background |
+| `opacity-*` | Adjust visibility |
+| `dark:*`, `ios:*`, `android:*` | Variant prefixes |
+
+See the full shared list at [Layout & Styling](layout#supported-tailwind-classes).
+
+## Examples
+
+### Push content to bottom
+
+@verbatim
+```blade
+
+ Welcome
+ Get started with your app.
+
+
+
+```
+@endverbatim
+
+### Toolbar with right-aligned trailing icon
+
+@verbatim
+```blade
+
+ Title
+
+
+
+```
+@endverbatim
+
+### Fixed-height spacer
+
+@verbatim
+```blade
+
+ Section One
+
+ Section Two
+
+```
+@endverbatim
+
+
+
+## Element
+
+```php
+use Native\Mobile\Edge\Elements\Spacer;
+
+Spacer::make(); // flex-grow: 1
+Spacer::make()->height(8); // fixed 8dp vertical
+```
diff --git a/resources/views/docs/mobile/4/edge-components/stack.md b/resources/views/docs/mobile/4/edge-components/stack.md
new file mode 100644
index 00000000..a9cb2063
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/stack.md
@@ -0,0 +1,109 @@
+---
+title: Stack
+order: 230
+---
+
+## Overview
+
+An overlay container that layers its children on top of each other — like `ZStack` in SwiftUI or `Box` in Jetpack
+Compose. The first child renders at the bottom; each subsequent child is placed on top.
+
+Useful for badges, image overlays, floating labels, and layered UI effects.
+
+@verbatim
+```blade
+
+
+
+ Overlay Text
+
+
+```
+@endverbatim
+
+## Children
+
+Accepts any EDGE elements as children. Children are rendered in order, with later children appearing on top of earlier
+ones.
+
+Each child is **placed at its natural size and centered** in the stack's bounds. Give a child `w-full` or `h-full` to
+force it to fill the stack.
+
+## Supported Tailwind classes
+
+Stack inherits the full class set documented at [Layout & Styling](layout#supported-tailwind-classes). The classes
+that shape how a stack behaves specifically:
+
+| Class | Effect on a stack |
+|---|---|
+| `w-*`, `h-*`, fractional, arbitrary `w-[N]` / `h-[N]` | Set the stack's own bounds — children center within it |
+| `flex-1` | Fills remaining space in the parent flex container |
+| `self-*` | This stack's alignment within its parent (`self-start`, `self-center`, `self-end`, `self-stretch`) |
+| `absolute`, `relative`, `top-N`, `right-N`, `bottom-N`, `left-N` | Position the stack itself when its parent uses absolute layout |
+
+Everything else from the shared list applies as on any element (`p-*`, `m-*`, `bg-*`, `rounded-*`, `border-*`,
+`shadow-*`, `opacity-*`, `dark:*`, `ios:*` / `android:*`, `glass:*`, alpha suffix `/N`, arbitrary `prefix-[value]`).
+
+## Examples
+
+### Avatar with centered badge
+
+@verbatim
+```blade
+
+
+
+
+```
+@endverbatim
+
+### Badge on an icon
+
+@verbatim
+```blade
+
+
+
+ 3
+
+
+```
+@endverbatim
+
+### Image with bottom-aligned overlay
+
+@verbatim
+```blade
+
+
+
+ Featured Article
+ Read more about this topic
+
+
+```
+@endverbatim
+
+
+
+## Element
+
+```php
+use Native\Mobile\Edge\Elements\Stack;
+use Native\Mobile\Edge\Elements\Image;
+use Native\Mobile\Edge\Elements\Text;
+
+Stack::make(
+ Image::make('https://example.com/photo.jpg'),
+ Text::make('Overlay'),
+)->width(200)->height(200);
+```
+
+- `make(Element ...$children)` - Create a stack with children. Layout / style fluent methods are inherited from the
+ base `Element` class — see [Layout & Styling](layout)
diff --git a/resources/views/docs/mobile/4/edge-components/tab-row.md b/resources/views/docs/mobile/4/edge-components/tab-row.md
new file mode 100644
index 00000000..c34b8447
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/tab-row.md
@@ -0,0 +1,127 @@
+---
+title: Tab Row
+order: 410
+---
+
+## Overview
+
+A horizontal tab strip with an underline indicator on the selected tab. Scrollable when tabs overflow. The row
+owns the selected-index state.
+
+Distinct from [``](bottom-nav) — bottom nav is your app's primary navigation chrome with full
+URL routing, while `` is an in-screen sectioning control whose tabs swap content within the same
+screen.
+
+Per Model 3, the active tab uses `theme.primary` and the underline is `theme.primary`. Inactive tabs use
+`theme.onSurfaceVariant`.
+
+@verbatim
+```blade
+
+
+
+
+
+```
+@endverbatim
+
+## Props (Row)
+
+- `value` / `selected-index` - Currently selected tab index (optional, int, default: `0`)
+- `a11y-label` - Accessibility label (optional)
+
+## Events
+
+- `@change` - Livewire method called when the selection changes. Receives the new index as a parameter
+
+## Two-way Binding
+
+`native:model` binds the selected index to a Livewire integer property:
+
+@verbatim
+```blade
+
+
+
+
+
+```
+@endverbatim
+
+## Children
+
+`` declares a single tab. Each accepts:
+
+- `label` - Tab label (required, string). Can also be passed as the first argument to `make()`
+- `icon` - Optional [icon](icons) name rendered above the label
+- `a11y-label` - Accessibility label override (optional)
+
+## Examples
+
+### Section switcher
+
+@verbatim
+```blade
+
+
+
+
+
+
+
+ @if($section === 0)
+
+ Overview content
+
+ @elseif($section === 1)
+
+ Activity content
+
+ @else
+
+ Members content
+
+ @endif
+
+```
+@endverbatim
+
+### Tabs with icons
+
+@verbatim
+```blade
+
+
+
+
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\TabRow;
+use Nativephp\NativeUi\Elements\Tab;
+
+TabRow::make(
+ Tab::make('Recent')->icon('history'),
+ Tab::make('Starred')->icon('star'),
+ Tab::make('Archived')->icon('archive'),
+)
+ ->selectedIndex($activeTab)
+ ->onChange('setActiveTab');
+```
+
+### `TabRow` methods
+
+- `make(Element ...$children)` - Create a row with tab children
+- `selectedIndex(int $index)` - Currently selected index
+- `a11yLabel(string $value)` - Accessibility label
+- `syncMode(string $mode)` - Set by `native:model` modifiers
+- `onChange(string $method)` - Livewire method invoked on change
+
+### `Tab` methods
+
+- `make(string $label = '')` - Create a tab with a label
+- `icon(string $icon)` - Tab icon
diff --git a/resources/views/docs/mobile/4/edge-components/text-input.md b/resources/views/docs/mobile/4/edge-components/text-input.md
new file mode 100644
index 00000000..c0a19557
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/text-input.md
@@ -0,0 +1,208 @@
+---
+title: Text Input
+order: 500
+---
+
+## Overview
+
+Native text input fields come in two variants:
+
+- `` — bordered field. Default, lower emphasis.
+- `` — surface-fill background + bottom indicator line. Higher emphasis.
+
+Both share the same prop set and event API. Choose based on emphasis, not behavior.
+
+On iOS they render as SwiftUI `TextField` / `SecureField` with Material3-style chrome; on Android they map to
+`OutlinedTextField` / `TextField` (filled). Per Model 3 there are no per-instance color, font, or border overrides
+— all chrome resolves from the theme. For fully custom input visuals drop to [``](pressable)
+wrapping your own drawing.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Props
+
+Both variants accept identical props.
+
+### Content
+
+- `value` - Current text value (optional, string)
+- `placeholder` - Placeholder shown when empty (optional, string)
+- `label` - Label rendered above the field (optional, string)
+- `supporting` - Helper text rendered below the field (optional, string)
+
+### State
+
+- `disabled` - Disable the input (optional, boolean, default: `false`)
+- `read-only` - Make the input read-only (optional, boolean, default: `false`)
+- `is-error` - Show error styling (border / indicator + supporting text turn `theme.destructive`)
+- `loading` - Show a spinner in the trailing position (optional, boolean, default: `false`)
+
+### Behavior
+
+- `keyboard` - Keyboard hint string: `text` (default), `number`, `email`, `phone`, `url`, `decimal`, `numberPassword`
+- `secure` - Mask input for passwords (optional, boolean, default: `false`)
+- `multiline` - Allow multiple lines (optional, boolean, default: `false`)
+- `max-length` - Maximum character count (optional, int)
+- `max-lines` - Maximum visible lines when `multiline` (optional, int)
+- `min-lines` - Minimum visible lines when `multiline` (optional, int)
+
+### Decorations
+
+- `prefix` - Text rendered before the input (optional, string)
+- `suffix` - Text rendered after the input (optional, string)
+- `leading-icon` - Icon name rendered at the start (optional, string)
+- `trailing-icon` - Icon name rendered at the end (optional, string)
+
+### Sizing & accessibility
+
+- `size` - `sm | md (default) | lg`
+- `a11y-label` - Accessibility label (optional)
+- `a11y-hint` - Accessibility hint (optional)
+
+## Events
+
+- `@change` - Livewire method called when the text changes. Receives the new value
+- `@submit` - Livewire method called when the user submits (e.g. presses return). Receives the current value
+
+
+
+## Two-way Binding
+
+Use the `native:model` directive for automatic two-way binding with a Livewire property. The directive expands to
+`:value`, `@change="__syncProperty(...)"`, and a `sync-mode` prop driven by the modifier chain.
+
+@verbatim
+```blade
+
+
+
+```
+@endverbatim
+
+`sync-mode` semantics:
+
+- `live` (default) — every keystroke fires `@change`
+- `blur` — only fires on focus loss / submit
+- `debounce` — fires after `debounce_ms` of inactivity, or immediately on blur / submit
+
+## Examples
+
+### Login form
+
+@verbatim
+```blade
+
+
+
+
+
+```
+@endverbatim
+
+### Filled variant with validation error
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Multiline textarea
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Search with submit
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Prefix and suffix
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\OutlinedTextInput;
+use Nativephp\NativeUi\Elements\FilledTextInput;
+
+OutlinedTextInput::make()
+ ->label('Email')
+ ->placeholder('you@example.com')
+ ->value($email)
+ ->keyboard('email')
+ ->leadingIcon('email')
+ ->onChange('updateEmail');
+```
+
+Both elements share the same fluent API (defined on `BaseTextInput`):
+
+- `value(string $text)`, `placeholder(string $text)`, `label(string $text)`, `supporting(string $text)`
+- `disabled(bool $value = true)`, `readOnly(bool $value = true)`, `error(bool $value = true)`, `loading(bool $value = true)`
+- `keyboard(string|int $type)`, `secure(bool $value = true)`, `maxLength(int $length)`
+- `multiline(bool $value = true)`, `maxLines(int $lines)`, `minLines(int $lines)`
+- `prefix(string $text)`, `suffix(string $text)`, `leadingIcon(string $name)`, `trailingIcon(string $name)`
+- `size(string $value)` - `sm | md | lg`
+- `a11yLabel(string $value)`, `a11yHint(string $value)`
+- `syncMode(string $mode)`, `debounceMs(int $ms)`
+- `onChange(string $method)`, `onSubmit(string $method)`
diff --git a/resources/views/docs/mobile/4/edge-components/text.md b/resources/views/docs/mobile/4/edge-components/text.md
new file mode 100644
index 00000000..bc4ead85
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/text.md
@@ -0,0 +1,137 @@
+---
+title: Text
+order: 300
+---
+
+## Overview
+
+Displays text content using platform-native typography. Text content goes between the opening and closing tags.
+
+@verbatim
+```blade
+
+ Hello, world!
+
+```
+@endverbatim
+
+## Props
+
+All [shared layout and style attributes](layout) are supported, plus:
+
+- `font-size` - Text size in sp/pt (optional, float, default: `16`)
+- `font-weight` - Weight 1-7 (optional, int, default: `3`):
+ - `1` thin
+ - `2` light
+ - `3` regular (normal)
+ - `4` medium
+ - `5` semibold
+ - `6` bold
+ - `7` heavy (extrabold)
+- `color` - Text color as hex string (optional, default: `#000000`)
+- `text-align` - Alignment: `0`=start, `1`=center, `2`=end (optional, int, default: `0`)
+- `max-lines` - Maximum lines before truncating with ellipsis (optional, int)
+
+
+
+## Dark Mode
+
+Use the `dark:` prefix with Tailwind classes or pass a dark-mode color override.
+
+@verbatim
+```blade
+
+ Adapts to dark mode
+
+```
+@endverbatim
+
+## Examples
+
+### Font weight scale
+
+@verbatim
+```blade
+Thin (1)
+Light (2)
+Regular (3)
+Medium (4)
+SemiBold (5)
+Bold (6)
+Heavy (7)
+```
+@endverbatim
+
+### Truncated text
+
+@verbatim
+```blade
+
+ This text will be truncated with an ellipsis after two lines if it overflows
+ the available space in its container.
+
+```
+@endverbatim
+
+### Centered heading
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+### Tappable text
+
+@verbatim
+```blade
+
+ Learn more
+
+```
+@endverbatim
+
+### Dynamic content
+
+@verbatim
+```blade
+
+ Score: {{ $score }}
+
+```
+@endverbatim
+
+
+
+## Element
+
+```php
+use Native\Mobile\Edge\Elements\Text;
+
+Text::make('Hello')
+ ->fontSize(18)
+ ->fontWeight(6)
+ ->color('#1E293B')
+ ->textAlign(1)
+ ->maxLines(2);
+```
+
+- `make(string $text = '')` - Create text with content
+- `fontSize(float $size)` - Text size
+- `fontWeight(int $weight)` - 1-7
+- `bold()` - Shortcut for `fontWeight(7)`
+- `color(string $hex)` - Text color
+- `textAlign(int $align)` - `0`=start, `1`=center, `2`=end
+- `maxLines(int $lines)` - Truncate after N lines
diff --git a/resources/views/docs/mobile/4/edge-components/toggle.md b/resources/views/docs/mobile/4/edge-components/toggle.md
new file mode 100644
index 00000000..f15e9c30
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/toggle.md
@@ -0,0 +1,108 @@
+---
+title: Toggle
+order: 510
+---
+
+## Overview
+
+A native on/off switch. Renders as a SwiftUI `Toggle` on iOS and a Material3 `Switch` on Android.
+
+Per Model 3, the active track / thumb colors come from `theme.primary` / `theme.onPrimary`. There are no per-instance
+color overrides. For custom visuals drop to [``](pressable) wrapping your own drawing.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Props
+
+- `value` - Current toggle state (optional, boolean, default: `false`)
+- `label` - Inline label text rendered to the left of the switch (optional)
+- `disabled` - Disable the toggle (optional, boolean, default: `false`)
+- `a11y-label` - Accessibility label (optional)
+- `a11y-hint` - Accessibility hint (optional)
+
+## Events
+
+- `@change` - Livewire method called when the toggle is flipped. Receives the new boolean value as a parameter
+
+
+
+## Two-way Binding
+
+Use the `native:model` directive for automatic two-way binding with a Livewire property. This expands to
+`:value` plus an `@change` handler that calls `__syncProperty`.
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+`sync-mode` and `debounce-ms` are accepted for API consistency with the other stateful components, but for a
+discrete tap the distinction between `live`, `blur`, and `debounce` makes no real difference — every flip is one
+event.
+
+## Examples
+
+### Settings list
+
+@verbatim
+```blade
+
+
+ Dark Mode
+
+
+
+
+ Notifications
+
+
+
+
+ Location
+
+
+
+```
+@endverbatim
+
+### With inline label
+
+@verbatim
+```blade
+
+```
+@endverbatim
+
+## Element
+
+```php
+use Nativephp\NativeUi\Elements\Toggle;
+
+Toggle::make()
+ ->value($darkMode)
+ ->label('Dark Mode')
+ ->disabled(false)
+ ->onChange('toggleDarkMode');
+```
+
+- `make()` - Create a toggle
+- `value(bool $checked)` - Current state
+- `label(string $text)` - Inline label
+- `disabled(bool $value = true)` - Disable the toggle
+- `a11yLabel(string $value)` - Accessibility label
+- `a11yHint(string $value)` - Accessibility hint
+- `syncMode(string $mode)` - `live | blur | debounce` (set by `native:model` modifiers)
+- `debounceMs(int $ms)` - Debounce interval when `syncMode === 'debounce'`
+- `onChange(string $method)` - Livewire method invoked on flip
diff --git a/resources/views/docs/mobile/4/edge-components/top-bar.md b/resources/views/docs/mobile/4/edge-components/top-bar.md
new file mode 100644
index 00000000..0643eab2
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/top-bar.md
@@ -0,0 +1,138 @@
+---
+title: Top Bar
+order: 50
+---
+
+## Overview
+
+
+
+
+
+
+
+
+A top bar with title, subtitle, and action buttons. This renders at the top of the screen.
+
+@verbatim
+```blade
+
+
+
+
+```
+@endverbatim
+
+## Props
+
+- `title` - The title text (required)
+- `subtitle` - A small line under the title (optional)
+- `show-navigation-icon` - Show the back chevron (optional, default: `true`)
+- `background-color` - Background color. Hex string (optional)
+- `text-color` - Title and icon color. Also flows to `` icons (optional)
+- `elevation` - Hairline thickness at the bottom of the bar in dp (optional)
+
+
+
+## Children
+
+A `` can contain up to 10 `` elements. These are displayed on the trailing
+edge of the bar.
+
+On Android, the first 3 actions are shown as icon buttons; additional actions collapse into an overflow menu (⋮).
+On iOS, if more than 5 actions are provided, they collapse into an overflow menu.
+
+### `` Props
+
+- `id` - Unique identifier (required)
+- `icon` - A named [icon](icons) (required)
+- `label` - Text label for the action. Used for accessibility and displayed in overflow menus (optional but recommended)
+- `url` - A URL to navigate to when tapped (optional)
+
+
+
+## Builder API
+
+When a `` is supplied by a [layout](../the-basics/layouts), you build it fluently with the `NavBar`
+and `NavAction` builders rather than writing it in Blade.
+
+```php
+use Native\Mobile\Edge\Layouts\Builders\NavAction;
+use Native\Mobile\Edge\Layouts\Builders\NavBar;
+
+NavBar::make()
+ ->title('SyncUp')
+ ->subtitle('All caught up')
+ ->back()
+ ->backgroundColor('#0891b2')
+ ->textColor('#FFFFFF')
+ ->elevation(8)
+ ->action(NavAction::make('search')->icon('search')->press('openSearch'));
+```
+
+### `NavBar` methods
+
+- `make()` - Create a new builder
+- `title(?string $title)` - Title text
+- `subtitle(?string $subtitle)` - Small line under the title
+- `back(bool $show = true)` - Show the back chevron
+- `backgroundColor(string $color)` - Bar background color
+- `textColor(string $color)` - Title and icon tint
+- `elevation(int $px)` - Hairline thickness at the bottom of the bar
+- `action(NavAction $action)` - Append a trailing action
+
+### `NavAction` methods
+
+- `make(string $id)` - Create an action with a unique id
+- `icon(string $icon)` - A named [icon](icons)
+- `label(string $label)` - Accessibility / overflow-menu label
+- `url(string $url)` - A URL to navigate to when tapped
+- `press(string $method)` - A Livewire-style method on the screen to invoke when tapped
+- `event(string $event)` - A native event name to dispatch (advanced)
+
+## Per-screen overrides
+
+Screens can contribute additional NavBar actions on top of what their layout supplies by overriding
+`navigationOptions()`:
+
+```php
+use Native\Mobile\Edge\Layouts\Builders\NavAction;
+use Native\Mobile\Edge\Layouts\Builders\NavBarOptions;
+use Native\Mobile\Edge\NativeComponent;
+
+class ItemDetail extends NativeComponent
+{
+ public function navigationOptions(): ?NavBarOptions
+ {
+ return NavBarOptions::make()
+ ->title("Item #{$this->param('id')}")
+ ->action(NavAction::make('save')->icon('save')->press('save'));
+ }
+
+ public function save(): void
+ {
+ // ...
+ }
+}
+```
+
+See [Layouts](../the-basics/layouts) for the full picture.
diff --git a/resources/views/docs/mobile/4/edge-components/web-view.md b/resources/views/docs/mobile/4/edge-components/web-view.md
new file mode 100644
index 00000000..caaa1829
--- /dev/null
+++ b/resources/views/docs/mobile/4/edge-components/web-view.md
@@ -0,0 +1,193 @@
+---
+title: Web View
+order: 265
+---
+
+With EDGE, native UI is the primary way to build your app — the web view is just one more component you can reach for,
+not the foundation everything sits on. You're free to compose entire screens from native EDGE components and never
+render a web view at all.
+
+That said, the web view is a first-class component, and you can lean on it as much (or as little) as you like. The web
+view lets you use whichever web technologies you are most comfortable with — Livewire, Vue, React, Svelte, HTMX... even
+jQuery! — to build part or all of your UI.
+
+A common pattern is to embed a web view for your content-heavy or existing web UI, then sprinkle native EDGE components
+around it — a native [Top Bar](top-bar), [Bottom Navigation](bottom-nav) or [Bottom Sheet](bottom-sheet) — to give the
+app a truly native feel where it matters most. If you prefer, you can still let a full-screen web view drive the whole
+app and call native functions and dispatch native UI from within it.
+
+When you do use a full-screen web view, it's rendered to fill the view of your application and remains visible to your
+users until another full-screen action takes place, such as accessing the camera or an in-app browser. The rest of this
+page covers how to make that surface behave well on device.
+
+## The Viewport
+
+Just like a normal browser, the web view has the concept of a **viewport** which represents the viewable area of the
+page. The viewport can be controlled with the `viewport` meta tag, just as you would in a traditional web application:
+
+```html
+
+```
+
+### Disable Zoom
+When building mobile apps, you may want to have a little more control over the experience. For example, you may
+want to disable user-controlled zoom, allowing your app to behave similarly to a traditional native app.
+
+To achieve this, you can set `user-scalable=no`:
+
+```html
+
+```
+
+## Edge-to-Edge
+
+To give you the most flexibility in how you design your app's UI, the web view occupies the entire screen, allowing you
+to render anything anywhere on the display whilst your app is in the foreground using just HTML, CSS and JavaScript.
+
+But you should bear in mind that not all parts of the display are visible to the user. Many devices have camera
+notches, rounded corners and curved displays. These areas may still be considered part of the `viewport`, but they may
+be invisible and/or non-interactive.
+
+To account for this in your UI, you should set the `viewport-fit=cover` option in your `viewport` meta tag and use the
+safe area insets.
+
+### Safe Areas
+
+Safe areas are the sections of the display which are not obscured by either a physical interruption (a rounded corner
+or camera), or some persistent UI, such as the Home Indicator (a.k.a. the bottom bar) or notch.
+
+Safe areas are calculated for your app by the device at runtime and adjust according to its orientation, allowing your
+UI to be responsive to the various device configurations with a simple and predictable set of CSS rules.
+
+The fundamental building blocks are a set of four values known as `insets`. These are injected into your pages as the
+following CSS variables:
+
+- `--inset-top`
+- `--inset-bottom`
+- `--inset-left`
+- `--inset-right`
+
+You can apply these insets in whichever way you need to build a usable interface.
+
+There is also a handy `nativephp-safe-area` CSS class that can be applied to most elements to ensure they sit within
+the safe areas of the display.
+
+Say you want a `fixed`-position header bar like this:
+
+
+
+If you're using Tailwind, you might try something like this:
+
+```html
+
+ ...
+
+```
+
+If you tried to do this without `viewport-fit=cover` and use of the safe areas, here's what you'd end up with in
+portrait view:
+
+
+
+And it may be even worse in landscape view:
+
+
+
+But by adding a few simple adjustments to our page, we can make it beautiful again (Well, maybe we should lose the
+red...):
+
+```html
+
+
+ ...
+
+```
+
+
+
+### Status Bar Style
+
+On Android, the icons in the Status Bar do not change color automatically based on the background color in your app.
+By default, they change based on whether the device is in Light/Dark Mode.
+
+If you have a consistent background color in both light and dark mode, you may use the `nativephp.status_bar_style`
+config key to set the appropriate status bar style for your app to give users the best experience.
+
+The possible options are:
+
+- `auto` - the default, which changes based on the device's Dark Mode setting
+- `light` - ideal if your app's background is dark-colored
+- `dark` - better if your app's background is light-colored
+
+
+
+## Keyboard Visibility
+
+When the on-screen keyboard appears, it can cover inputs, action buttons or other parts of your UI that the user needs
+to see or interact with. To help you adapt your layout, NativePHP automatically toggles a `keyboard-visible` class on
+the `` element whenever the software keyboard opens or closes — on both iOS and Android.
+
+You can use this class to hide, move or resize elements while the keyboard is up, using CSS alone:
+
+```css
+/* Hide a fixed bottom navigation bar while the user is typing */
+.bottom-nav {
+ transition: transform 0.2s ease-out;
+}
+
+body.keyboard-visible .bottom-nav {
+ transform: translateY(100%);
+}
+```
+
+If you're using Tailwind, you can register a `keyboard-visible` variant in your CSS file using the `@custom-variant`
+directive:
+
+```css
+@import "tailwindcss";
+
+@custom-variant keyboard-visible (&:where(body.keyboard-visible *));
+```
+
+You can then use it like any built-in variant:
+
+```html
+
+```
+
+This pairs well with the [Safe Areas](#safe-areas) insets, allowing you to build layouts that respond cleanly to both
+device geometry and keyboard state without writing any JavaScript.
+
+## WebView Compatibility
+
+On Android, the web view is powered by the system's built-in WebView component, which varies by device and OS version.
+Older Android versions ship with older WebView engines that may not support modern CSS features.
+
+For example, Tailwind CSS v4 uses `@theme` and other newer CSS features that are not supported on older WebView
+versions. If you are targeting a lower `min_sdk` to support older devices, consider using Tailwind CSS v3 or another
+CSS framework that generates compatible output. You can configure your minimum SDK version in your
+[Android SDK Versions](/docs/mobile/3/getting-started/configuration#android-sdk-versions) settings.
+
+Always test your app on emulators running your minimum supported Android version to catch these issues early. You can
+create emulators for older API levels in Android Studio's Virtual Device Manager.
+
+With just a few small changes, we've been able to define a layout that will work well on a multitude of devices
+without having to add complex calculations or lots of device-specific CSS rules to our code.
diff --git a/resources/views/docs/mobile/4/getting-started/_index.md b/resources/views/docs/mobile/4/getting-started/_index.md
new file mode 100644
index 00000000..5c684744
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/_index.md
@@ -0,0 +1,4 @@
+---
+title: Getting Started
+order: 1
+---
\ No newline at end of file
diff --git a/resources/views/docs/mobile/4/getting-started/changelog.md b/resources/views/docs/mobile/4/getting-started/changelog.md
new file mode 100644
index 00000000..c111fa05
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/changelog.md
@@ -0,0 +1,31 @@
+---
+title: Changelog
+order: 2
+---
+
+For changes prior to v4, see the [v3 documentation](/docs/mobile/3/getting-started/changelog).
+
+@forelse (\App\Support\GitHub::mobileAir()->releasesAfter('4.0.0') as $release)
+## {{ $release->name ?: $release->tag_name }}
+**Released: {{ \Carbon\Carbon::parse($release->published_at)->format('F j, Y') }}**
+
+{{ $release->getBodyForMarkdown() }}
+---
+@empty
+@endforelse
+
+## v4.0 — SuperNative (beta)
+
+### New Features
+
+- **[SuperNative](../super-native/introduction)** — fully native UI powered by SwiftUI on iOS and Jetpack Compose on Android, and the new default for v4 apps
+- **Livewire-like native components** — each screen is a PHP `NativeComponent` class holding its state and behavior, re-rendering the native UI as your properties change
+- **Shared memory between PHP and the native layer** — no serialization overhead or web view bridge between your code and the UI
+- **[Web view as a component](../edge-components/web-view)** — the classic web-view-first approach is now opt-out: render a single native route with a full-screen web view to keep building the v3 way
+- **[Layouts](../the-basics/layouts)** — declare shared chrome (nav bars, tab bars) once in a `NativeLayout` class and attach it to a route or group of routes
+- **[Native navigation stack](../the-basics/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
+
+### 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
new file mode 100644
index 00000000..ae22359e
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/commands.md
@@ -0,0 +1,302 @@
+---
+title: Command Reference
+order: 350
+---
+
+A complete reference of all `native:*` Artisan commands available in NativePHP Mobile.
+
+## Development Commands
+
+### native:install
+
+Install NativePHP into your Laravel application.
+
+```shell
+php artisan native:install {platform?}
+```
+
+| Option | Description |
+|--------|-------------|
+| `platform` | Target platform: `android`, `ios`, or `both` |
+| `--force` | Overwrite existing files |
+| `--fresh` | Alias for `--force` |
+| `--with-icu` | Include ICU support for Android (adds ~30MB) |
+| `--without-icu` | Exclude ICU support for Android |
+| `--skip-php` | Do not download PHP binaries |
+
+### native:run
+
+Build and run your app on a device or simulator.
+
+```shell
+php artisan native:run {os?} {udid?}
+```
+
+| Option | Description |
+|--------|---------------------------------------------------|
+| `os` | Target platform: `ios/i` or `android/a` |
+| `udid` | Specific device/simulator UDID |
+| `--build=debug` | Build type: `debug`, `release`, or `bundle` |
+| `--watch` | Enable hot reloading during development |
+| `--start-url=` | Initial URL/path to load (e.g., `/dashboard`) |
+| `--no-tty` | Disable TTY mode for non-interactive environments |
+
+
+
+### native:watch
+
+Watch for file changes and sync to a running mobile app.
+
+```shell
+php artisan native:watch {platform?} {target?}
+```
+
+| Option | Description |
+|--------|-----------------------------------------|
+| `platform` | Target platform: `ios/i` or `android/a` |
+| `target` | The device/simulator UDID to watch |
+
+### native:jump
+
+Start the NativePHP development server for testing mobile apps without building.
+
+```shell
+php artisan native:jump
+```
+
+| Option | Description |
+|--------|-------------|
+| `--host=0.0.0.0` | Host address to serve on |
+| `--ip=` v3.3+ | IP address to display in the QR code (overrides auto-detection) |
+| `--http-port=` | HTTP port to serve on (defaults to `nativephp.server.http_port`, typically `3000`) |
+| `--ws-port=` v3.3+ | WebSocket bridge port (defaults to `3001`) |
+| `--bridge-port=` v3.3+ | Internal TCP bridge port (defaults to `3002`) |
+| `--vite-proxy-port=` v3.3+ | Port Jump uses to proxy Vite HMR to the phone (defaults to `3003`) |
+| `--no-serve` v3.3+ | Do not start `artisan serve` automatically (use if running your own server) |
+| `--laravel-port=` | Laravel dev server port (defaults to `8000`; auto-detected when `artisan serve` is managed) |
+| `--no-mdns` | Disable mDNS service advertisement |
+
+### native:open
+
+Open the native project in Xcode or Android Studio.
+
+```shell
+php artisan native:open {os?}
+```
+
+| Option | Description |
+|--------|-----------------------------------------|
+| `os` | Target platform: `ios/i` or `android/a` |
+
+### native:tail
+
+Tail Laravel logs from a running Android app. (Android only)
+
+```shell
+php artisan native:tail
+```
+
+### native:version
+
+Display the current NativePHP Mobile version.
+
+```shell
+php artisan native:version
+```
+
+## Building & Release Commands
+
+
+
+### native:package
+
+Package your app for distribution with signing.
+
+```shell
+php artisan native:package {platform}
+```
+
+| Option | Description |
+|--------|---------------------------------------------------|
+| `platform` | Target platform: `android/a` or `ios/i` |
+| `--build-type=release` | Build type: `release` or `bundle` |
+| `--output=` | Output directory for signed artifacts |
+| `--jump-by=` | Skip ahead in version numbering |
+| `--no-tty` | Disable TTY mode for non-interactive environments |
+
+**Android Options:**
+
+| Option | Description |
+|--------|-------------|
+| `--keystore=` | Path to Android keystore file |
+| `--keystore-password=` | Keystore password |
+| `--key-alias=` | Key alias for signing |
+| `--key-password=` | Key password |
+| `--fcm-key=` | FCM Server Key for push notifications |
+| `--google-service-key=` | Google Service Account Key file path |
+| `--upload-to-play-store` | Upload to Play Store after packaging |
+| `--play-store-track=internal` | Play Store track: `internal`, `alpha`, `beta`, `production` |
+| `--test-push=` | Test Play Store upload with existing AAB file (skip build) |
+| `--skip-prepare` | Skip prepareAndroidBuild() to preserve existing project files |
+
+**iOS Options:**
+
+| Option | Description |
+|--------|-------------|
+| `--export-method=app-store` | Export method: `app-store`, `ad-hoc`, `enterprise`, `development` |
+| `--upload-to-app-store` | Upload to App Store Connect after packaging |
+| `--test-upload` | Test upload existing IPA (skip build) |
+| `--validate-only` | Only validate the archive without exporting |
+| `--validate-profile` | Validate provisioning profile entitlements |
+| `--rebuild` | Force rebuild by removing existing archive |
+| `--clean-caches` | Clear Xcode and SPM caches before building |
+| `--api-key=` | Path to App Store Connect API key file (.p8) |
+| `--api-key-id=` | App Store Connect API key ID |
+| `--api-issuer-id=` | App Store Connect API issuer ID |
+| `--certificate-path=` | Path to distribution certificate (.p12/.cer) |
+| `--certificate-password=` | Certificate password |
+| `--provisioning-profile-path=` | Path to provisioning profile (.mobileprovision) |
+| `--team-id=` | Apple Developer Team ID |
+
+### native:release
+
+Bump the version number in your `.env` file.
+
+```shell
+php artisan native:release {type}
+```
+
+| Option | Description |
+|--------|-------------|
+| `type` | Release type: `patch`, `minor`, or `major` |
+
+### native:credentials
+
+Generate signing credentials for iOS and Android.
+
+```shell
+php artisan native:credentials {platform?}
+```
+
+| Option | Description |
+|--------|--------------------------------------------------|
+| `platform` | Target platform: `android/a`, `ios/i`, or `both` |
+| `--reset` | Generate new keystore and PEM certificate |
+
+### native:check-build-number
+
+Validate and suggest build numbers for your app.
+
+```shell
+php artisan native:check-build-number
+```
+
+## Plugin Commands
+
+### native:plugin:create
+
+Scaffold a new NativePHP plugin interactively.
+
+```shell
+php artisan native:plugin:create
+```
+
+### native:plugin:list
+
+List all installed NativePHP plugins.
+
+```shell
+php artisan native:plugin:list
+```
+
+| Option | Description |
+|--------|-------------|
+| `--json` | Output as JSON |
+| `--all` | Show all installed plugins, including unregistered |
+
+### native:plugin:register
+
+Register a plugin in your NativeServiceProvider. When called without arguments, discovers all unregistered plugins and lets you register them.
+
+```shell
+php artisan native:plugin:register {plugin?}
+```
+
+| Option | Description |
+|--------|-------------|
+| `plugin` | Package name (e.g., `vendor/plugin-name`). Optional — omit to discover unregistered plugins |
+| `--remove` | Remove the plugin instead of adding it |
+| `--force` | Skip conflict warnings |
+
+### native:plugin:uninstall
+
+Completely uninstall a plugin.
+
+```shell
+php artisan native:plugin:uninstall {plugin}
+```
+
+| Option | Description |
+|--------|-------------|
+| `plugin` | Package name (e.g., `vendor/plugin-name`) |
+| `--force` | Skip confirmation prompts |
+| `--keep-files` | Do not delete the plugin source directory |
+
+### native:plugin:validate
+
+Validate a plugin's structure and manifest.
+
+```shell
+php artisan native:plugin:validate {path?}
+```
+
+| Option | Description |
+|--------|-------------|
+| `path` | Path to a specific plugin directory |
+
+### native:plugin:make-hook
+
+Create lifecycle hook commands for a plugin.
+
+```shell
+php artisan native:plugin:make-hook
+```
+
+### native:plugin:boost
+
+Create Boost AI guidelines for a plugin.
+
+```shell
+php artisan native:plugin:boost {plugin?}
+```
+
+| Option | Description |
+|--------|-------------|
+| `plugin` | Plugin name or path |
+| `--force` | Overwrite existing guidelines |
+
+### native:plugin:install-agent
+
+Install AI agents for plugin development.
+
+```shell
+php artisan native:plugin:install-agent
+```
+
+| Option | Description |
+|--------|-------------|
+| `--force` | Overwrite existing agent files |
+| `--all` | Install all agents without prompting |
diff --git a/resources/views/docs/mobile/4/getting-started/configuration.md b/resources/views/docs/mobile/4/getting-started/configuration.md
new file mode 100644
index 00000000..5cc588c6
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/configuration.md
@@ -0,0 +1,356 @@
+---
+title: Configuration
+order: 200
+---
+
+## Overview
+
+NativePHP for Mobile is designed so that most configuration happens **inside your Laravel application**, without
+requiring you to open Xcode or Android Studio and manually update config files.
+
+This page explains the key configuration points you can control through Laravel.
+
+## The `nativephp.php` Config File
+
+The `config/nativephp.php` config file contains a number of useful options.
+
+NativePHP uses sensible defaults and makes several assumptions based on default installations for tools required to
+build and run apps from your computer.
+
+You can override these defaults by editing the `nativephp.php` config file in your Laravel project, and in many cases
+simply by changing environment variables.
+
+## `NATIVEPHP_APP_ID`
+
+You must set your app ID to something unique. A common practice is to use a reverse-DNS-style name, e.g.
+`com.yourcompany.yourapp`.
+
+Your app ID (also known as a *Bundle Identifier*) is a critical piece of identification across both Android and iOS
+platforms. Different app IDs are treated as separate apps.
+
+And it is often referenced across multiple services, such as Apple Developer Center and the Google Play Console.
+
+So it's not something you want to be changing very often.
+
+## `NATIVEPHP_APP_VERSION`
+
+The `NATIVEPHP_APP_VERSION` environment variable sets your app's **public version string** - what shows up in App Store
+and Play Store listings, and on-device under "Settings → Apps" (or equivalent). It maps to `CFBundleShortVersionString`
+on iOS and `versionName` on Android.
+
+Its companion, `NATIVEPHP_APP_VERSION_CODE`, sets the **internal build number** the stores use to distinguish uploads
+(`CFBundleVersion` on iOS, `versionCode` on Android). Users don't normally see this one, but the stores require it to
+increase with every upload.
+
+`NATIVEPHP_APP_VERSION` defaults to `DEBUG`, which is what you want during development - every boot picks up your
+latest code without needing to bump a version number by hand.
+
+You only need to change this when you're cutting a release build for distribution. Rather than editing your `.env`
+manually, use the
+[`native:release` command](/docs/mobile/3/getting-started/commands#nativerelease), which bumps the version (and build
+number) for you.
+
+
+
+## Persistent Runtime
+
+v3.1 introduces a persistent PHP runtime that boots Laravel once and reuses the kernel across all subsequent requests.
+This dramatically improves performance — from ~200-300ms per request down to ~5-30ms.
+
+The `runtime` section controls this behavior:
+
+```php
+'runtime' => [
+ 'mode' => env('NATIVEPHP_RUNTIME_MODE', 'persistent'), // [tl! highlight]
+ 'reset_instances' => true,
+ 'gc_between_dispatches' => false,
+],
+```
+
+- `mode` — Set to `persistent` (default) to reuse the Laravel kernel, or `classic` to boot/shutdown per request.
+ If persistent boot fails, it falls back to classic mode automatically.
+- `reset_instances` — Whether to clear resolved facade instances between dispatches. (default: `true`)
+- `gc_between_dispatches` — Whether to run garbage collection between dispatches. Enable this if you notice memory
+ growth over time. (default: `false`)
+
+
+
+## Deep Links
+
+Configure deep linking to allow URLs to open your app directly:
+
+```php
+'deeplink_scheme' => env('NATIVEPHP_DEEPLINK_SCHEME'),
+'deeplink_host' => env('NATIVEPHP_DEEPLINK_HOST'),
+```
+
+The `deeplink_scheme` enables custom URL schemes (e.g. `myapp://some/path`), while `deeplink_host` enables
+verified HTTPS links and NFC tags (e.g. `https://your-host.com/path`).
+
+See the [Deep Links](../concepts/deep-links) documentation for full details.
+
+## Start URL
+
+Set the initial path that loads when your app starts:
+
+```php
+'start_url' => env('NATIVEPHP_START_URL', '/'),
+```
+
+This is useful if you want to land users on a specific page like `/dashboard` or `/onboarding` instead of the root.
+
+## Cleanup `env` keys
+
+The `cleanup_env_keys` array in the config file allows you to specify keys that should be removed from the `.env` file
+before bundling. This is useful for removing sensitive information like API keys or other secrets.
+
+## Cleanup `exclude_files`
+
+The `cleanup_exclude_files` array in the config file allows you to specify files and folders that should be removed
+before bundling. This is useful for removing files like logs or other temporary files that aren't required for your app
+to function and bloat your downloads.
+
+## Orientation
+
+NativePHP (as of v1.10.3) allows users to custom specific orientations per device through the config file. The config
+allows for granularity for iPad, iPhone and Android devices. Options for each device can be seen below.
+
+NOTE: if you want to disable iPad support completely simply apply `false` for each option.
+
+```php
+'orientation' => [
+ 'iphone' => [
+ 'portrait' => true,
+ 'upside_down' => false,
+ 'landscape_left' => false,
+ 'landscape_right' => false,
+ ],
+ 'android' => [
+ 'portrait' => true,
+ 'upside_down' => false,
+ 'landscape_left' => false,
+ 'landscape_right' => false,
+ ],
+],
+```
+
+Regardless of these orientation settings, if your app supports iPad, it will be available in all orientations.
+
+## iPad Support
+
+With NativePHP, your app can work on iPad too! If you wish to support iPad, simply set the `ipad` config option to `true`:
+
+```php
+'ipad' => true,
+```
+
+Using standard CSS responsive design principles, you can make your app work beautifully across all screen sizes.
+
+
+
+## Android SDK Versions
+
+The `android` section of your config file lets you control which Android SDK versions are used when building your app.
+These are nested under the `android` key in `config/nativephp.php`:
+
+```php
+'android' => [
+ 'compile_sdk' => env('NATIVEPHP_ANDROID_COMPILE_SDK', 36),
+ 'min_sdk' => env('NATIVEPHP_ANDROID_MIN_SDK', 33),
+ 'target_sdk' => env('NATIVEPHP_ANDROID_TARGET_SDK', 36),
+],
+```
+
+- `compile_sdk` - The SDK version used to compile your app. This determines which Android APIs are available to you
+ at build time. (default: `36`)
+- `min_sdk` - The minimum Android version your app supports. Devices running an older version won't be able to install
+ your app. (default: `26`, Android 8)
+- `target_sdk` - The SDK version your app is designed and tested against. Google Play uses this to apply appropriate
+ compatibility behaviors. (default: `36`)
+
+You can also set these via environment variables:
+
+```dotenv
+NATIVEPHP_ANDROID_COMPILE_SDK=36
+NATIVEPHP_ANDROID_MIN_SDK=26
+NATIVEPHP_ANDROID_TARGET_SDK=36
+```
+
+
+
+## Android Build Configuration
+
+Fine-tune your Android build process with these options under the `android.build` key:
+
+```php
+'android' => [
+ 'build' => [
+ 'minify_enabled' => env('NATIVEPHP_ANDROID_MINIFY_ENABLED', false),
+ 'shrink_resources' => env('NATIVEPHP_ANDROID_SHRINK_RESOURCES', false),
+ 'obfuscate' => env('NATIVEPHP_ANDROID_OBFUSCATE', false),
+ 'debug_symbols' => env('NATIVEPHP_ANDROID_DEBUG_SYMBOLS', 'FULL'),
+ 'parallel_builds' => env('NATIVEPHP_ANDROID_PARALLEL_BUILDS', true),
+ 'incremental_builds' => env('NATIVEPHP_ANDROID_INCREMENTAL_BUILDS', true),
+ ],
+],
+```
+
+- `minify_enabled` — Enable R8/ProGuard code shrinking. (default: `false`)
+- `shrink_resources` — Remove unused resources from the APK. (default: `false`)
+- `obfuscate` — Obfuscate class and method names. (default: `false`)
+- `debug_symbols` — Include debug symbols. Set to `FULL` for symbolicated crash reports. (default: `FULL`)
+- `parallel_builds` / `incremental_builds` — Gradle build performance options. (default: `true`)
+
+
+
+## Android Status Bar Style
+
+Control the color of the status bar and navigation bar icons:
+
+```php
+'android' => [
+ 'status_bar_style' => env('NATIVEPHP_ANDROID_STATUS_BAR_STYLE', 'auto'),
+],
+```
+
+Options: `auto` (detect from system theme), `light` (white icons), or `dark` (dark icons).
+
+## Development Server
+
+Configure the development server used by `native:jump` and `native:watch`:
+
+```php
+'server' => [
+ 'http_port' => env('NATIVEPHP_HTTP_PORT', 3000),
+ 'ws_port' => env('NATIVEPHP_WS_PORT', 8081),
+ 'service_name' => env('NATIVEPHP_SERVICE_NAME', 'NativePHP Server'),
+ 'open_browser' => env('NATIVEPHP_OPEN_BROWSER', true),
+],
+```
+
+- `http_port` — The port for serving your app during development. (default: `3000`)
+- `ws_port` — The WebSocket port for hot reload communication. (default: `8081`)
+- `service_name` — The mDNS service name advertised on your network. (default: `NativePHP Server`)
+- `open_browser` — Automatically open a browser with a QR code when the server starts. (default: `true`)
+
+## Hot Reload
+
+Customize which files trigger hot reloads during development:
+
+```php
+'hot_reload' => [
+ 'watch_paths' => [
+ 'app',
+ 'resources',
+ 'routes',
+ 'config',
+ 'public',
+ ],
+ 'exclude_patterns' => [
+ '\.git',
+ 'storage',
+ 'node_modules',
+ ],
+],
+```
+
+## Development Team (iOS)
+
+Set your Apple Developer Team ID for code signing:
+
+```php
+'development_team' => env('NATIVEPHP_DEVELOPMENT_TEAM'),
+```
+
+This is typically detected from your installed certificates, but you can override it here. Find your Team ID
+in your Apple Developer account under Membership details.
+
+## iOS Permission Strings
+
+Plugins declare their own iOS `Info.plist` usage descriptions through their manifests (see
+[Permissions & Dependencies](../plugins/permissions-dependencies)). When you install multiple plugins that
+claim the same key — for example, `mobile-camera` and `mobile-scanner` both setting
+`NSCameraUsageDescription` — the merged result is whichever plugin loaded last, which isn't always what you
+want to show App Store reviewers.
+
+The top-level `permissions` array in `config/nativephp.php` lets you override those merged values. Anything
+you set here is applied **after** all plugin manifests are merged, so it always wins:
+
+```php
+'permissions' => [
+ 'NSCameraUsageDescription' => 'Used to take a profile photo.',
+ 'NSMicrophoneUsageDescription' => 'Used to record audio with your videos.',
+ 'NSPhotoLibraryUsageDescription' => 'Used to select photos for your post.',
+],
+```
+
+Use this when you want a single, explicit usage string for App Store review, or when you need to tailor a
+plugin-provided description to match how your app actually uses the permission.
+
+
+
+## App Store Connect
+
+Configure automated iOS uploads with the App Store Connect API:
+
+```php
+'app_store_connect' => [
+ 'api_key' => env('APP_STORE_API_KEY'),
+ 'api_key_id' => env('APP_STORE_API_KEY_ID'),
+ 'api_issuer_id' => env('APP_STORE_API_ISSUER_ID'),
+ 'app_name' => env('APP_STORE_APP_NAME'),
+],
+```
+
+These credentials are used by `native:package --upload-to-app-store` to upload your IPA directly to
+App Store Connect without opening Xcode.
+
+
diff --git a/resources/views/docs/mobile/4/getting-started/contributing.md b/resources/views/docs/mobile/4/getting-started/contributing.md
new file mode 100644
index 00000000..2a1f3e44
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/contributing.md
@@ -0,0 +1,83 @@
+---
+title: Contributing
+order: 700
+---
+
+## Contributing
+
+We welcome contributions to NativePHP for Mobile! Whether it's bug fixes, new features, documentation improvements, or bug reports, every contribution helps make the project better.
+
+### How to Contribute
+
+1. **Fork the repository** on GitHub.
+2. **Clone your fork** locally:
+ ```bash
+ git clone git@github.com:your-username/mobile-air.git
+ cd mobile-air
+ ```
+3. **Create a new branch** for your feature or fix:
+ ```bash
+ git checkout -b feature/my-new-feature
+ ```
+4. **Make your changes** and ensure all tests pass.
+5. **Commit your changes** with a clear, descriptive commit message.
+6. **Push your branch** to your fork:
+ ```bash
+ git push origin feature/my-new-feature
+ ```
+7. **Open a Pull Request** against the `main` branch of the NativePHP Mobile repository.
+
+### Pull Request Guidelines
+
+- Keep your changes focused. If you have multiple unrelated changes, please submit them as separate pull requests.
+- Write clear, descriptive commit messages.
+- Include tests for any new functionality.
+- Ensure all existing tests pass before submitting.
+- Update documentation if your changes affect the public API.
+
+### Reporting Bugs
+
+If you discover a bug, please [open an issue](https://github.com/NativePHP/mobile-air/issues) on GitHub. Include as much detail as possible:
+
+- A clear, descriptive title.
+- Steps to reproduce the issue.
+- Expected behavior vs. actual behavior.
+- Your environment details (OS, PHP version, Laravel version, etc.).
+
+### Security Vulnerabilities
+
+If you discover a security vulnerability, please **do not** open a public issue. Instead, please send an email to [support@nativephp.com](mailto:support@nativephp.com). All security vulnerabilities will be promptly addressed.
+
+## Code of Conduct
+
+The NativePHP community is dedicated to providing a welcoming and inclusive experience for everyone. We expect all participants to adhere to the following standards:
+
+### Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
+
+### Our Standards
+
+Examples of behavior that contributes to a positive environment:
+
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
+
+Examples of unacceptable behavior:
+
+- The use of sexualized language or imagery, and sexual attention or advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a professional setting
+
+### Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project team at [support@nativephp.com](mailto:support@nativephp.com). All complaints will be reviewed and investigated promptly and fairly.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
diff --git a/resources/views/docs/mobile/4/getting-started/deployment.md b/resources/views/docs/mobile/4/getting-started/deployment.md
new file mode 100644
index 00000000..b5742691
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/deployment.md
@@ -0,0 +1,514 @@
+---
+title: Deployment
+order: 300
+---
+
+Deploying mobile apps is a complicated process — and it's different for each platform!
+
+
+
+Generally speaking you need to:
+
+1. **Releasing**: Create a _release build_ for each platform.
+2. **Testing**: Test this build on real devices.
+3. **Packaging**: Sign and distribute this build to the stores.
+4. **Submitting for Review**: Go through each store's submission process to have your app reviewed.
+5. **Publishing**: Releasing the new version to the stores and your users.
+
+It's initially more time-consuming when creating a brand new app in the stores, as you need to get the listing set up
+in each store and create your signing credentials.
+
+If you've never done it before, allow a couple of hours so you can focus on getting things right and understand
+everything you need.
+
+Don't rush through the app store processes! There are compliance items that if handled incorrectly will either prevent
+you from publishing your app, being unable to release it in the territories you want to make it available to, or simply
+having it get rejected immediately when you submit it for review if you don't get those right.
+
+It's typically easier once you've released the first version of your app and after you've done 2 or 3 apps, you'll fly
+through the process!
+
+
+
+## Releasing
+
+To prepare your app for release, bump the version number using the
+[`native:release` command](/docs/mobile/3/getting-started/commands#nativerelease):
+
+```shell
+php artisan native:release patch
+```
+
+You can pass `patch`, `minor`, or `major` depending on the type of release you're cutting. This updates
+`NATIVEPHP_APP_VERSION` in your `.env` and increments the build number for you.
+
+### Versioning
+
+App version numbers should follow [semantic versioning](https://semver.org) (e.g. `1.2.3`). The `native:release` command
+relies on this format to determine how to bump your version.
+
+Remember that your app versions are usually public-facing (e.g. in store listings and on-device settings and update
+screens) and can be useful for customers to reference if they need to contact you for help and support.
+
+The app version is managed via the `NATIVEPHP_APP_VERSION` key in your `.env`.
+
+### Build numbers
+
+Both the Google Play Store and Apple App Store require your app's build number to increase for each release you submit.
+
+The build number is managed via the `NATIVEPHP_APP_VERSION_CODE` key in your `.env`. You don't need to manage this
+yourself — running `native:release` automatically increments the build number and persists it back to your `.env`.
+
+### Run a `release` build
+
+Then run a release build:
+
+```shell
+php artisan native:run --build=release
+```
+
+This builds your application with various optimizations that reduce its overall size and improve its performance, such
+as removing debugging code and unnecessary features (i.e. Composer dev dependencies).
+
+**You should test this build on a real device.** Once you're happy that everything is working as intended you can then
+submit it to the stores for approval and distribution.
+
+- [Google Play Store submission guidelines](https://support.google.com/googleplay/android-developer/answer/9859152?hl=en-GB#zippy=%2Cmaximum-size-limit)
+- [Apple App Store submission guidelines](https://developer.apple.com/ios/submit/)
+
+## Packaging Your App
+
+The `native:package` command creates signed, production-ready apps for distribution to the App Store and Play Store.
+This command handles all the complexity of code signing, building release artifacts, and preparing files for submission.
+
+## Before You Begin
+
+Before you can package your app for distribution, ensure:
+
+1. Your app is fully developed and tested on both platforms
+2. You have a valid bundle ID and app ID configured in your `nativephp.php` config
+3. For Android: You have a signing keystore with a valid key alias
+4. For iOS: You have the necessary signing certificates and provisioning profiles from Apple Developer
+5. All configuration is complete (see the [configuration guide](/docs/mobile/3/getting-started/configuration))
+
+## Android Packaging
+
+### Creating Android Signing Credentials
+
+NativePHP provides a convenient command to generate all the signing credentials you need for Android:
+
+```bash
+php artisan native:credentials android
+```
+
+This command will:
+- Generate a new JKS keystore file
+- Create all necessary signing keys
+- Automatically add the credentials to your `.env` file
+- Add the keystore to your `.gitignore` to keep it secure
+
+The credentials will be saved in the `nativephp/credentials/android/` directory and automatically configured for use with the package command.
+
+### Required Signing Credentials
+
+To build a signed Android app, you need four pieces of information:
+
+| Credential | Option | Environment Variable | Description |
+|-----------|--------|----------------------|-------------|
+| Keystore file | `--keystore` | `ANDROID_KEYSTORE_FILE` | Path to your `.keystore` file |
+| Keystore password | `--keystore-password` | `ANDROID_KEYSTORE_PASSWORD` | Password for the keystore |
+| Key alias | `--key-alias` | `ANDROID_KEY_ALIAS` | Name of the key within the keystore |
+| Key password | `--key-password` | `ANDROID_KEY_PASSWORD` | Password for the specific key |
+
+### Building a Release APK
+
+An APK (Android Package) is a single binary file suitable for direct distribution or testing on specific devices:
+
+```bash
+php artisan native:package android \
+ --keystore=/path/to/my-app.keystore \
+ --keystore-password=mykeystorepassword \
+ --key-alias=my-app-key \
+ --key-password=mykeypassword
+```
+
+The build process prepares your Android project, compiles the code, and signs the APK with your certificate. When complete, the output directory opens automatically, showing your signed `app-release.apk` file.
+
+### Building an Android App Bundle (AAB)
+
+An AAB (Android App Bundle) is required for distribution through the Play Store. It's an optimized format that the Play Store uses to generate device-specific APKs automatically:
+
+```bash
+php artisan native:package android \
+ --build-type=bundle \
+ --keystore=/path/to/my-app.keystore \
+ --keystore-password=mykeystorepassword \
+ --key-alias=my-app-key \
+ --key-password=mykeypassword
+```
+
+This creates a signed `app-release.aab` file ready for Play Store submission.
+
+### Using Environment Variables
+
+Instead of passing credentials as command options, you can store them in your `.env` file:
+
+```env
+ANDROID_KEYSTORE_FILE=/path/to/my-app.keystore
+ANDROID_KEYSTORE_PASSWORD=mykeystorepassword
+ANDROID_KEY_ALIAS=my-app-key
+ANDROID_KEY_PASSWORD=mykeypassword
+```
+
+Then simply run:
+
+```bash
+php artisan native:package android --build-type=bundle
+```
+
+### Uploading to Play Store
+
+If you have a Google Service Account with Play Console access, you can upload directly from the command:
+
+```bash
+php artisan native:package android \
+ --build-type=bundle \
+ --keystore=/path/to/my-app.keystore \
+ --keystore-password=mykeystorepassword \
+ --key-alias=my-app-key \
+ --key-password=mykeypassword \
+ --upload-to-play-store \
+ --play-store-track=internal \
+ --google-service-key=/path/to/service-account-key.json
+```
+
+The `--play-store-track` option controls where the build is released:
+
+- `internal` - Internal testing (default, fastest review)
+- `alpha` - Closed alpha testing
+- `beta` - Closed beta testing
+- `production` - Production release
+
+### Testing Play Store Uploads
+
+If you already have an AAB file and want to test uploading without rebuilding, use `--test-push`:
+
+```bash
+php artisan native:package android \
+ --test-push=/path/to/app-release.aab \
+ --upload-to-play-store \
+ --play-store-track=internal \
+ --google-service-key=/path/to/service-account-key.json
+```
+
+This skips the entire build process and only handles the upload.
+
+### Skipping Build Preparation
+
+For incremental builds where you haven't changed native code, you can skip the preparation phase:
+
+```bash
+php artisan native:package android \
+ --build-type=bundle \
+ --keystore=/path/to/my-app.keystore \
+ --keystore-password=mykeystorepassword \
+ --key-alias=my-app-key \
+ --key-password=mykeypassword \
+ --skip-prepare
+```
+
+## iOS Packaging
+
+### Required Signing Credentials
+
+iOS apps require several credentials from Apple Developer:
+
+| Credential | Option | Environment Variable | Description |
+|-----------|--------|----------------------|-------------|
+| API Key file | `--api-key-path` | `APP_STORE_API_KEY_PATH` | Path to `.p8` file from App Store Connect |
+| API Key ID | `--api-key-id` | `APP_STORE_API_KEY_ID` | Key ID from App Store Connect |
+| API Issuer ID | `--api-issuer-id` | `APP_STORE_API_ISSUER_ID` | Issuer ID from App Store Connect |
+| Certificate | `--certificate-path` | `IOS_DISTRIBUTION_CERTIFICATE_PATH` | Distribution certificate (`.p12` or `.cer`) |
+| Certificate password | `--certificate-password` | `IOS_DISTRIBUTION_CERTIFICATE_PASSWORD` | Password for the certificate |
+| Provisioning profile | `--provisioning-profile-path` | `IOS_DISTRIBUTION_PROVISIONING_PROFILE_PATH` | Profile file (`.mobileprovision`) |
+| Team ID | `--team-id` | `IOS_TEAM_ID` | Apple Developer Team ID |
+
+### Setting Up App Store Connect API
+
+To upload to the App Store directly from the command line, you'll need an API key:
+
+1. Log in to [App Store Connect](https://appstoreconnect.apple.com)
+2. Navigate to Users & Access → Keys
+3. Click the "+" button to create a new key with "Developer" access
+4. Download the `.p8` file immediately (you can't download it again later)
+5. Note the Key ID and Issuer ID displayed on the page
+
+### Export Methods
+
+The `--export-method` option controls how your app is packaged:
+
+- `app-store` - For App Store distribution (default)
+- `ad-hoc` - For distribution to specific registered devices
+- `enterprise` - For enterprise distribution (requires enterprise program)
+- `development` - For development and testing
+
+### Building for App Store
+
+To build a production app ready for App Store submission:
+
+```bash
+php artisan native:package ios \
+ --export-method=app-store \
+ --api-key-path=/path/to/api-key.p8 \
+ --api-key-id=ABC123DEF \
+ --api-issuer-id=01234567-89ab-cdef-0123-456789abcdef \
+ --certificate-path=/path/to/distribution.p12 \
+ --certificate-password=certificatepassword \
+ --provisioning-profile-path=/path/to/profile.mobileprovision \
+ --team-id=ABC1234567
+```
+
+### Building for Ad-Hoc Distribution
+
+For distributing to specific devices without going through the App Store:
+
+```bash
+php artisan native:package ios \
+ --export-method=ad-hoc \
+ --certificate-path=/path/to/distribution.p12 \
+ --certificate-password=certificatepassword \
+ --provisioning-profile-path=/path/to/ad-hoc-profile.mobileprovision
+```
+
+### Building for Development
+
+For testing on your own device:
+
+```bash
+php artisan native:package ios \
+ --export-method=development \
+ --certificate-path=/path/to/development.p12 \
+ --certificate-password=certificatepassword \
+ --provisioning-profile-path=/path/to/development-profile.mobileprovision
+```
+
+### Using Environment Variables
+
+Store your iOS credentials in `.env`:
+
+```env
+APP_STORE_API_KEY_PATH=/path/to/api-key.p8
+APP_STORE_API_KEY_ID=ABC123DEF
+APP_STORE_API_ISSUER_ID=01234567-89ab-cdef-0123-456789abcdef
+IOS_DISTRIBUTION_CERTIFICATE_PATH=/path/to/distribution.p12
+IOS_DISTRIBUTION_CERTIFICATE_PASSWORD=certificatepassword
+IOS_DISTRIBUTION_PROVISIONING_PROFILE_PATH=/path/to/profile.mobileprovision
+IOS_TEAM_ID=ABC1234567
+```
+
+Then build with:
+
+```bash
+php artisan native:package ios --export-method=app-store
+```
+
+### Uploading to App Store Connect
+
+To automatically upload after building:
+
+```bash
+php artisan native:package ios \
+ --export-method=app-store \
+ --api-key-path=/path/to/api-key.p8 \
+ --api-key-id=ABC123DEF \
+ --api-issuer-id=01234567-89ab-cdef-0123-456789abcdef \
+ --certificate-path=/path/to/distribution.p12 \
+ --certificate-password=certificatepassword \
+ --provisioning-profile-path=/path/to/profile.mobileprovision \
+ --team-id=ABC1234567 \
+ --upload-to-app-store
+```
+
+The upload uses the App Store Connect API, so API credentials are required only when using `--upload-to-app-store`.
+
+### Validating Provisioning Profiles
+
+Before building, you can validate your provisioning profile to check push notification support and entitlements:
+
+```bash
+php artisan native:package ios \
+ --validate-profile \
+ --provisioning-profile-path=/path/to/profile.mobileprovision
+```
+
+This extracts and displays:
+
+- Profile name
+- All entitlements configured in the profile
+- Push notification support status
+- Associated domains
+- APS environment matching
+
+### Testing App Store Uploads
+
+To test uploading an existing IPA without rebuilding:
+
+```bash
+php artisan native:package ios \
+ --test-upload \
+ --api-key-path=/path/to/api-key.p8 \
+ --api-key-id=ABC123DEF \
+ --api-issuer-id=01234567-89ab-cdef-0123-456789abcdef
+```
+
+### Clearing Xcode Caches
+
+If you encounter build issues, clear Xcode and Swift Package Manager caches:
+
+```bash
+php artisan native:package ios \
+ --export-method=app-store \
+ --clean-caches
+```
+
+### Forcing a Clean Rebuild
+
+To force a complete rebuild and create a new archive:
+
+```bash
+php artisan native:package ios \
+ --export-method=app-store \
+ --rebuild
+```
+
+### Validating Without Exporting
+
+To validate the archive without creating an IPA:
+
+```bash
+php artisan native:package ios \
+ --validate-only
+```
+
+## Version Management
+
+### Build Numbers and Version Codes
+
+The `native:version` command handles version management. When building AABs for the Play Store with valid Google Service credentials, NativePHP automatically checks the Play Store for the latest build number and increments it.
+
+### Auto-Incrementing from Play Store
+
+When building a bundle with Play Store access:
+
+```bash
+php artisan native:package android \
+ --build-type=bundle \
+ --keystore=/path/to/my-app.keystore \
+ --keystore-password=mykeystorepassword \
+ --key-alias=my-app-key \
+ --key-password=mykeypassword \
+ --google-service-key=/path/to/service-account-key.json
+```
+
+The command automatically queries the Play Store to find your latest published build number and increments it.
+
+### Jumping Ahead in Version Numbers
+
+If you need to skip version numbers or jump ahead:
+
+```bash
+php artisan native:package android \
+ --build-type=bundle \
+ --keystore=/path/to/my-app.keystore \
+ --keystore-password=mykeystorepassword \
+ --key-alias=my-app-key \
+ --key-password=mykeypassword \
+ --jump-by=10
+```
+
+This adds 10 to the version code that would normally be used.
+
+## Tips & Troubleshooting
+
+### Common Android Issues
+
+**Keystore-related errors:**
+- Verify the keystore file exists and the path is correct
+- Check that the keystore password is correct
+- Confirm the key alias exists in the keystore with: `keytool -list -v -keystore /path/to/keystore`
+- Verify the key password matches
+
+**Build failures:**
+- Ensure you have the latest Android SDK and build tools installed
+- Check that your `nativephp/android` directory exists and is properly initialized
+- If you've modified native code, don't use `--skip-prepare`
+
+**Play Store upload failures:**
+- Verify the Google Service Account has access to your app in Play Console
+- Ensure the service account key file is valid and readable
+- Check that your bundle ID matches your Play Console app ID
+
+### Custom Output Directories
+
+By default, the build output opens in your system's file manager. To copy the artifact to a custom location:
+
+```bash
+php artisan native:package android \
+ --build-type=bundle \
+ --keystore=/path/to/my-app.keystore \
+ --keystore-password=mykeystorepassword \
+ --key-alias=my-app-key \
+ --key-password=mykeypassword \
+ --output=/path/to/custom/directory
+```
+
+### Building in Non-Interactive Environments
+
+For CI/CD pipelines or automated builds, disable TTY mode:
+
+```bash
+php artisan native:package android \
+ --build-type=bundle \
+ --keystore=/path/to/my-app.keystore \
+ --keystore-password=mykeystorepassword \
+ --key-alias=my-app-key \
+ --key-password=mykeypassword \
+ --no-tty
+```
+
+### Artifact Locations
+
+Once complete, signed artifacts are located at:
+
+**Android:**
+- APK (release): `nativephp/android/app/build/outputs/apk/release/app-release.apk`
+- AAB (bundle): `nativephp/android/app/build/outputs/bundle/release/app-release.aab`
+
+**iOS:**
+- IPA: Generated in Xcode's build output directory
diff --git a/resources/views/docs/mobile/4/getting-started/development.md b/resources/views/docs/mobile/4/getting-started/development.md
new file mode 100644
index 00000000..cbd99fae
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/development.md
@@ -0,0 +1,247 @@
+---
+title: Development
+order: 250
+---
+
+Developing your NativePHP apps can be done in the browser, using workflows with which you're already familiar.
+
+This allows you to iterate rapidly on parts like the UI and major functionality, even using your favorite tools for
+testing etc.
+
+But when you want to test _native_ features, then you must run your app on a real or emulated device.
+
+Whether you run your native app on an emulated or real device, it will require compilation after changes have been made.
+
+
+
+## Build your frontend
+
+If you're using Vite or similar tooling to build any part of your UI (e.g. for React/Vue, Tailwind etc), you'll need
+to run your asset build command _before_ compiling your app.
+
+To facilitate ease of development, you should install the `nativephpMobile` Vite plugin.
+
+### The `nativephpMobile` Vite plugin
+
+To make your frontend build process works well with NativePHP, simply add the `nativephpMobile` plugin to your
+`vite.config.js`:
+
+```js
+import { nativephpMobile, nativephpHotFile } from './vendor/nativephp/mobile/resources/js/vite-plugin.js'; // [tl! focus]
+
+export default defineConfig({
+ plugins: [
+ laravel({
+ input: ['resources/css/app.css', 'resources/js/app.js'],
+ refresh: true,
+ hotFile: nativephpHotFile(), // [tl! focus]
+ }),
+ tailwindcss(),
+ nativephpMobile(), // [tl! focus]
+ ]
+});
+```
+
+Once that's done, you'll need to adjust your Vite build command when creating builds for each platform — simply add the
+`--mode=[ios|android]` option. Run these before compiling your app for each platform in turn:
+
+```shell
+npm run build -- --mode=ios
+
+npm run build -- --mode=android
+```
+
+
+
+## Compile your app
+
+To compile and run your app, simply run:
+
+```shell
+php artisan native:run
+```
+
+This single command takes care of everything and allows you to run new builds of your application without having to
+learn any new editors or platform-specific tools.
+
+
+
+## Working with Xcode or Android Studio
+
+On occasion, it is useful to compile your app from inside the target platform's dedicated development tools, Android
+Studio and Xcode.
+
+If you're familiar with these tools, you can easily open the projects using the following Artisan command:
+
+```shell
+php artisan native:open
+```
+
+### Configuration
+
+You can configure the folders that the `watch` command pays attention to in your `config/nativephp.php` file:
+
+```php
+'hot_reload' => [
+ 'watch_paths' => [
+ 'app',
+ 'routes',
+ 'config',
+ 'database',
+ // Make sure "public" is listed in your config [tl! highlight:1]
+ 'public',
+ ],
+]
+```
+
+
+
+
+## Hot Reloading
+
+We've tried to make compiling your apps as fast as possible, but when coming from the 'make a change; hit refresh'-world
+of typical browser-based PHP development that we all love, compiling apps can feel like a slow and time-consuming
+process.
+
+Hot reloading aims to make your app development experience feel just like home.
+
+You can start hot reloading by running the following command:
+
+```shell
+php artisan native:watch
+```
+
+
+
+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.
+
+### 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`:
+
+```js
+import { nativephpMobile, nativephpHotFile } from './vendor/nativephp/mobile/resources/js/vite-plugin.js'; // [tl! focus]
+
+export default defineConfig({
+ plugins: [
+ laravel({
+ input: ['resources/css/app.css', 'resources/js/app.js'],
+ refresh: true,
+ hotFile: nativephpHotFile(), // [tl! focus]
+ }),
+ tailwindcss(),
+ nativephpMobile(),
+ ]
+});
+```
+
+**Note:** When testing on real devices, hot reloading is communicating with the Vite server running on your development machine.
+For this to work, ensure the test device is connected to the same Wi-Fi network as your development machine.
+
+
+
+This is useful during development for quickly testing changes without re-compiling your entire app. When you make
+changes to any files in your Laravel app, the web view will be reloaded and your changes should show almost immediately.
+
+Vite HMR is perfect for apps that use SPA frameworks like Vue or React to build the UI. It even works on real devices,
+not just simulators! As long as the device is on the same network as the development machine.
+
+**Don't forget to add `public/ios-hot` and `public/android-hot` to your `.gitignore` file!**
+
+
+
+## Laravel Boost
+
+NativePHP for Mobile supports [Laravel Boost](https://laravel.com/ai/boost) which aims to accelerate AI-assisted development by providing
+the essential context and structure that AI needs to generate high-quality, Laravel-specific code.
+
+After installing `nativephp/mobile` and `laravel/boost` simply run `php artisan boost:install` and follow the prompts
+to activate NativePHP for Laravel Boost!
diff --git a/resources/views/docs/mobile/4/getting-started/environment-setup.md b/resources/views/docs/mobile/4/getting-started/environment-setup.md
new file mode 100644
index 00000000..1847e743
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/environment-setup.md
@@ -0,0 +1,144 @@
+---
+title: Environment Setup
+order: 100
+---
+
+## Requirements
+
+1. PHP 8.3+
+2. Laravel 11+
+
+If you don't already have PHP installed on your machine, the most painless way to get PHP up and running on Mac and
+Windows is with [Laravel Herd](https://herd.laravel.com). It's fast and free!
+
+## iOS Requirements
+
+
+
+1. macOS (required - iOS development is only possible on an Apple silicon Mac, M1+)
+2. [Xcode 16.0 or later](https://apps.apple.com/app/xcode/id497799835)
+3. Xcode Command Line Tools
+4. Homebrew & CocoaPods
+5. _Optional_ iOS device for testing
+
+### Setting up iOS Development Environment
+
+1. **Install Xcode**
+ - Download from the [Mac App Store](https://apps.apple.com/app/xcode/id497799835)
+ - Minimum version: Xcode 16.0
+
+2. **Install Xcode Command Line Tools**
+ ```shell
+ xcode-select --install
+ ```
+ Verify installation:
+ ```shell
+ xcode-select -p
+ ```
+
+3. **Install Homebrew** (if not already installed)
+ ```shell
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+ ```
+
+4. **Install CocoaPods**
+ ```shell
+ brew install cocoapods
+ ```
+ Verify installation:
+ ```shell
+ pod --version
+ ```
+
+### Apple Developer Account
+You **do not** need to enroll in the [Apple Developer Program](https://developer.apple.com/programs/enroll/) ($99/year)
+to develop and test your apps on a Simulator. However, you will need to enroll when you want to:
+- Test your apps on real devices
+- Distribute your apps via the App Store
+- Test features that rely on a paid Apple Developer accounts, such as Push Notifications
+
+## Android Requirements
+
+1. [Android Studio 2024.2.1 or later](https://developer.android.com/studio)
+2. Android SDK with API 29 or higher
+3. **Windows only**: You must have [7zip](https://www.7-zip.org/) installed.
+
+
+
+### Setting up Android Studio and SDK
+
+1. **Download and Install Android Studio**
+ - Download from the [Android Studio download page](https://developer.android.com/studio)
+ - Minimum version required: Android Studio 2024.2.1
+
+2. **Install Android SDK**
+ - Open Android Studio
+ - Navigate to **Tools → SDK Manager**
+ - In the **SDK Platforms** tab, install at least one Android SDK platform for API 29 or higher
+ - Latest stable version: Android 16 (API 36)
+ - You only need to install one API version to get started
+ - In the **SDK Tools** tab, ensure **Android SDK Build-Tools** and **Android SDK Platform-Tools** are installed
+
+That's it! Android Studio handles all the necessary configuration automatically.
+
+### Preparing for NativePHP
+
+1. Check that you can run `java -version` and `adb devices` from the terminal.
+2. The following environment variables set:
+
+#### On macOS
+```shell
+# This isn't required if JAVA_HOME is already set in your environment variables (check using `printenv | grep JAVA_HOME`)
+export JAVA_HOME=$(/usr/libexec/java_home -v 17)
+
+export ANDROID_HOME=$HOME/Library/Android/sdk
+export PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools
+```
+
+#### On Windows
+The example below assumes default installation paths for the Android SDK and JDK:
+
+```shell
+set ANDROID_HOME=C:\Users\yourname\AppData\Local\Android\Sdk
+set PATH=%PATH%;%JAVA_HOME%\bin;%ANDROID_HOME%\platform-tools
+
+# This isn't required if JAVA_HOME is already set in the Windows Env Variables
+set JAVA_HOME=C:\Program Files\Microsoft\jdk-17.0.8.7-hotspot
+```
+
+### "No AVDs found" error
+If you encounter this error, it means no Virtual Devices are configured in Android Studio.
+To resolve it, open Android Studio, navigate to Virtual Devices, and create at least one device.
+
+## Testing on Real Devices
+
+You don't _need_ a physical iOS/Android device to compile and test your application, as NativePHP for Mobile supports
+the iOS Simulator and Android emulators. However, we highly recommend that you test your application on a real device
+before submitting to the Apple App Store and Google Play Store.
+
+### On iOS
+If you want to run your app on a real iOS device, you need to make sure it is in
+[Developer Mode](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device)
+and that it's been added to your Apple Developer account as
+[a registered device](https://developer.apple.com/account/resources/devices/list).
+
+### On Android
+On Android you need to [enable developer options](https://developer.android.com/studio/debug/dev-options#enable)
+and have USB debugging (ADB) enabled.
diff --git a/resources/views/docs/mobile/4/getting-started/installation.md b/resources/views/docs/mobile/4/getting-started/installation.md
new file mode 100644
index 00000000..ae602ec2
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/installation.md
@@ -0,0 +1,118 @@
+---
+title: Installation
+order: 100
+---
+
+## Install the Composer package
+
+NativePHP contains all the libraries, classes, commands, and interfaces that your application will need to work with
+iOS and Android. And it's a single command away:
+
+```shell
+composer require nativephp/mobile
+```
+
+### We love Laravel
+
+NativePHP for Mobile is built to work with Laravel. We recommend that you install it into a
+[new Laravel application](https://laravel.com/docs/installation) for your NativePHP application.
+
+### Notes for Windows users
+
+#### Windows Defender
+
+Add `C:\temp`, as well as your project folder, to your Windows Defender exclusions list to significantly speed up
+Composer installs during app compilation. This prevents its real-time scanning from processing the many temporary files
+created during the build process, which slows the process considerably.
+
+#### No WSL support
+
+NativePHP does not work in WSL (Windows Subsystem for Linux). You must install and run NativePHP directly on Windows.
+
+## Run the NativePHP installer
+
+**Before** running the `install` command, it is important to set the following variables in your `.env`:
+
+```dotenv
+NATIVEPHP_APP_ID=com.yourcompany.yourapp
+```
+
+Find out more about this option in
+[Configuration](/docs/getting-started/configuration#codenativephp-app-idcode).
+
+
+
+
+```shell
+php artisan native:install
+```
+
+The NativePHP installer takes care of setting up and configuring your Laravel application to work with iOS and Android.
+
+You may be prompted about whether you would like to install the ICU-enabled PHP binaries. You should install these if
+your application relies on the `intl` PHP extension.
+
+If you don't need `intl` or are not sure, choose the default, non-ICU builds.
+
+
+
+### The `nativephp` Directory
+
+After running: `php artisan native:install` you’ll see a new `nativephp` directory at the root of your Laravel project
+as well as a `config/nativephp.php` config file.
+
+The `nativephp` folder contains the native application project files needed to build your app for the desired platforms.
+
+You should not need to manually open or edit any native project files under normal circumstances. NativePHP handles
+the heavy lifting for you.
+
+**You should treat this directory as ephemeral.** When upgrading the NativePHP package, it will be necessary to run
+`php artisan native:install --force`, which completely rebuilds this directory, deleting all files within.
+
+For this reason, we also recommend you add the `nativephp` folder to your `.gitignore`.
+
+## Start your app
+
+**Heads up!** Before starting your app in a native context, try running it in the browser. You may bump into exceptions
+which need addressing before you can run your app natively, and may be trickier to spot when doing so.
+
+Once you're ready:
+
+```shell
+php artisan native:run
+```
+
+Just follow the prompts! This will start compiling your application and boot it on whichever device you select.
+
+### Running on a real device
+
+#### On iOS
+If you want to run your app on a real iOS device, you need to make sure it is in
+[Developer Mode](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device)
+and that it's been added to your Apple Developer account as
+[a registered device](https://developer.apple.com/account/resources/devices/list).
+
+#### On Android
+On Android you need to [enable developer options](https://developer.android.com/studio/debug/dev-options#enable)
+and have USB debugging (ADB) enabled.
+
+And that's it! You should now see your Laravel application running as a native app! 🎉
diff --git a/resources/views/docs/mobile/4/getting-started/introduction.md b/resources/views/docs/mobile/4/getting-started/introduction.md
new file mode 100644
index 00000000..15a2bf6a
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/introduction.md
@@ -0,0 +1,62 @@
+---
+title: Introduction
+order: 1
+---
+
+## Enjoy building mobile apps!
+
+NativePHP for Mobile is the first library of its kind that lets you run full PHP applications natively on mobile
+devices — no web server required.
+
+By embedding a pre-compiled PHP runtime alongside Laravel, and bridging directly into each platform’s native
+APIs, NativePHP brings the power of modern PHP to truly native mobile apps. Build performant, offline-capable
+experiences using the tools you already know.
+
+**It's never been this easy to build beautiful, offline-first apps for iOS and Android.**
+
+## What makes NativePHP for Mobile special?
+
+- 📱 **Native performance**
+ Your app runs natively through an embedded PHP runtime optimized for each platform.
+- 🔥 **True native APIs**
+ Access camera, biometrics, push notifications, and more. Build beautiful UIs with native components. All from one
+ cohesive library that does it all.
+- ⚡ **Laravel powered**
+ Leverage the entire Laravel ecosystem and your existing skillset.
+- 🚫 **No web server required**
+ Your app runs entirely on-device and can operate completely offline-first.
+- 🔄 **Cross platform**
+ Build apps for both iOS and Android from a single codebase.
+
+## Old tools, new tricks
+
+With NativePHP for Mobile, you don’t need to learn Swift, Kotlin, or anything new.
+No new languages. No unfamiliar build tools. No fighting with Gradle or Xcode.
+
+Just PHP.
+
+Developers around the world are using the skills they already have to build and ship real mobile apps — faster than
+ever. In just a few minutes, you can go from code to app store submission.
+
+## How does it work?
+
+1. A pre-compiled version of PHP is bundled with your code into a Swift/Kotlin shell application.
+2. NativePHP's custom Swift/Kotlin bridges manage the PHP environment, running your PHP code directly.
+3. A custom PHP extension is compiled into PHP, that exposes PHP interfaces to native functions.
+4. Build with HTML, JavaScript, Tailwind, Blade, Livewire, React, Vue, Svelte — whatever you're most comfortable with!
+5. And now in v3: use truly native UI components too with [EDGE](/docs/mobile/3/edge-components/)!
+
+You simply interact with an easy-to-use set of functions from PHP and everything just works!
+
+## Batteries included
+
+NativePHP for Mobile is way more than just a web view wrapper for your server-based application. Your application lives
+_on device_ and is shipped with each installation.
+
+Thanks to our custom PHP extension, you can interact with many native APIs today, with more coming all the time. Check out the API documentation section to see everything that's available.
+
+You have the full power of PHP and Laravel at your fingertips... literally! And you're not sandboxed into the web view;
+this goes way beyond what's possible with PWAs and WASM without any of the complexity... we've got full-cream PHP at
+the ready!
+
+**What are you waiting for!? [Let's go!](quick-start)**
diff --git a/resources/views/docs/mobile/4/getting-started/quick-start.md b/resources/views/docs/mobile/4/getting-started/quick-start.md
new file mode 100644
index 00000000..7fbce38f
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/quick-start.md
@@ -0,0 +1,75 @@
+---
+title: Quick Start
+order: 2
+---
+
+## Jump in
+
+Don't waste hours downloading, installing, and configuring Xcode and Android Studio; just
+[Jump](https://bifrost.nativephp.com/jump):
+
+1. Install the Jump app on your iOS or Android device
+2. Run the following commands:
+
+### New Laravel app
+
+If you are creating new Laravel app, you can build using our starter kit:
+
+```bash
+laravel new my-app --using=nativephp/mobile-starter
+
+cd my-app
+
+php artisan native:jump
+```
+
+### Existing Laravel app
+
+If you already have a Laravel app:
+
+```bash
+composer require nativephp/mobile
+
+php artisan native:jump
+```
+
+Scan the QR code with Jump and you're off!
+
+## Install & run
+
+If you've already got your [environment set up](environment-setup) to build mobile apps using Xcode and/or Android
+Studio, you can build and run your app locally:
+
+```bash
+# Install NativePHP for Mobile into a new Laravel app
+composer require nativephp/mobile
+
+# Ready your app to go native
+php artisan native:install
+
+# Run your app on a mobile device
+php artisan native:run
+```
+
+#### The `native` command
+
+When you run `native:install`, NativePHP installs a `native` script helper that can be used as a convenient wrapper to
+the `native` Artisan command namespace. Once this is installed you can do the following:
+
+```shell
+# Instead of...
+php artisan native:run
+
+# Do
+php native run
+
+# Or
+./native run
+```
+
+## Need help?
+
+- **Community** - Join our [Discord](/discord) for support and discussions.
+- **Examples** - Check out the Kitchen Sink demo app
+ on [Android](https://play.google.com/store/apps/details?id=com.nativephp.kitchensinkapp) and
+ [iOS](https://testflight.apple.com/join/vm9Qtshy)!
diff --git a/resources/views/docs/mobile/4/getting-started/roadmap.md b/resources/views/docs/mobile/4/getting-started/roadmap.md
new file mode 100644
index 00000000..c5d347f2
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/roadmap.md
@@ -0,0 +1,32 @@
+---
+title: Roadmap
+order: 400
+---
+
+NativePHP for Mobile is stable and already deployed in production apps released on the app stores. But we're not done
+yet! Here's what we're focusing on next:
+
+## Background tasks
+
+We will be adding the ability to run code in the background, even when your app isn't in the foreground. Perfect for
+syncing data, processing uploads, or handling push notifications.
+
+## Native UI through EDGE
+
+We will be expanding EDGE's (Element Definition and Generation Engine) capabilities, letting you define more truly
+native UI components from your PHP code. Build navigation bars, tab bars, and other native elements that feel right at
+home on each platform.
+
+## Performance
+
+We will be improving NativePHP's performance, making apps faster and more efficient. Expect improvements to startup
+time, memory usage, and overall responsiveness.
+
+
diff --git a/resources/views/docs/mobile/4/getting-started/support-policy.md b/resources/views/docs/mobile/4/getting-started/support-policy.md
new file mode 100644
index 00000000..396f4fc6
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/support-policy.md
@@ -0,0 +1,22 @@
+---
+title: Support Policy
+order: 600
+---
+
+NativePHP for Mobile is still very new. We aim to make it workable with as many versions of iOS and Android as is
+reasonable. Considering that we have a very small team and a lot of work, our current stance on version support is this:
+
+**We aim (but do not guarantee) to support all the current and upcoming major, currently vendor-supported versions of
+the platforms, with a focus on the current major release as a priority.**
+
+In practical terms, as of September 2025, this means we intend for NativePHP to be compatible — in part or in whole —
+with:
+
+- iOS 18+
+- Android 13+
+
+We do not guarantee support of all features across these versions, and whilst NativePHP may work in part on even older
+versions than the currently-supported ones, we do not provide support for these under this standard policy.
+
+If you require explicit backwards compatibility with older or unsupported versions, we will be happy to have you join
+our [partner](/partners) program, where a custom support policy can be arranged.
diff --git a/resources/views/docs/mobile/4/getting-started/upgrade-guide.md b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md
new file mode 100644
index 00000000..ea5cca07
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/upgrade-guide.md
@@ -0,0 +1,180 @@
+---
+title: Upgrade Guide
+order: 3
+---
+
+## 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
+dramatically improves request performance.
+
+
+### Update your dependency
+
+```json
+"require": {
+ "nativephp/mobile": "~3.0.0" // [tl! remove]
+ "nativephp/mobile": "~3.1.0" // [tl! add]
+}
+```
+
+```sh
+composer update
+php artisan native:install --force
+```
+
+
+
+
+## Android 8+ Support
+
+In order to support Android API 26 add the following to your `config/nativephp.php`:
+
+```php
+'android' => [
+ 'compile_sdk' => env('NATIVEPHP_ANDROID_COMPILE_SDK', 36),
+ 'min_sdk' => env('NATIVEPHP_ANDROID_MIN_SDK', 33),
+ 'target_sdk' => env('NATIVEPHP_ANDROID_TARGET_SDK', 36),
+],
+```
+
+No code changes are required — this is handled entirely at the build level.
+
+## ICU/Intl Support on iOS
+
+iOS builds now include full **ICU support**, which means the PHP `intl` extension works on both platforms.
+This was previously only available on Android.
+
+This is a big deal — packages like [Filament](https://filamentphp.com) depend on `intl` for number formatting,
+date formatting, and pluralization. With v3.1, **Filament works on both iOS and Android** out of the box.
+
+ICU support remains optional via the `--with-icu` / `--without-icu` flags during installation it adds ~30MB to your app on Android and ~100MB on iOS.
+---
+
+## Upgrading To 3.0 From 2.x
+
+NativePHP for Mobile v3 introduces a plugin-based architecture that makes the entire native layer extensible.
+All core functionality continues to work as before, but the underlying system is now modular and open to
+third-party plugins.
+
+## Remove the NativePHP Composer Repository
+
+v3 no longer requires the private Composer repository or license authentication. Remove the `nativephp.composer.sh`
+repository from your `composer.json`:
+
+```json
+"repositories": [
+ {
+ "type": "composer",
+ "url": "https://nativephp.composer.sh"
+ }
+]
+```
+
+Delete the entire `repositories` block above (or just the NativePHP entry if you have other repositories).
+
+You can also remove any stored credentials for `nativephp.composer.sh` from your `auth.json` if you have one.
+
+Then update your version constraint and run the upgrade:
+
+```json
+"require": {
+ "nativephp/mobile": "~2.0.0" // [tl! remove]
+ "nativephp/mobile": "~3.0.0" // [tl! add]
+}
+```
+
+```sh
+composer update
+php artisan native:install --force
+```
+
+## Plugin Architecture
+
+v3 introduces a comprehensive plugin system. Native functionality is now delivered through plugins — including all
+the official core APIs you already use (Camera, Biometrics, Scanner, etc.). These continue to work exactly as before;
+you don't need to change how you call them.
+
+The difference is that **third-party developers can now create plugins** that add new native functionality to your
+app. Plugins are standard Composer packages that include Swift (iOS) and Kotlin (Android) code alongside their
+PHP interface.
+
+Read more about the plugin system in the [Plugins documentation](../plugins/introduction), or browse
+ready-made plugins on the [NativePHP Plugin Marketplace](https://nativephp.com/plugins).
+
+## NativeServiceProvider
+
+v3 introduces a `NativeServiceProvider` for registering third-party plugins. Publish it with:
+
+```shell
+php artisan vendor:publish --tag=nativephp-plugins-provider
+```
+
+This creates `app/Providers/NativeServiceProvider.php`. Any third-party plugins you install must be registered
+here before their native code is compiled into your app. This is a security measure to prevent transitive
+dependencies from automatically including native code without your consent.
+
+```shell
+php artisan native:plugin:register vendor/some-plugin
+```
+
+Core APIs provided by `nativephp/mobile` do not need to be registered manually — they are included automatically.
+
+Read more about [Using Plugins](../plugins/using-plugins).
+
+## Core APIs Are Now Plugins
+
+All core APIs (Camera, Biometrics, Dialog, Scanner, Geolocation, etc.) are now implemented as plugins internally.
+The PHP facades and events you use remain the same — no changes to your application code are needed.
+
+Browse the full list of available core plugins in the [Plugins documentation](../plugins/introduction).
+
+## Plugin Management Commands
+
+v3 adds several new Artisan commands for working with plugins:
+
+| Command | Description |
+|---------|-------------|
+| `native:plugin:create` | Scaffold a new plugin |
+| `native:plugin:register` | Register a plugin in your NativeServiceProvider |
+| `native:plugin:list` | List installed plugins |
+| `native:plugin:uninstall` | Remove a plugin |
+| `native:plugin:validate` | Validate plugin structure |
+| `native:plugin:make-hook` | Create a lifecycle hook |
+
+## Bridge Functions
+
+Plugins communicate with native code through **bridge functions** — a standardized pattern for calling
+Swift and Kotlin code from PHP via `nativephp_call()`. Each plugin declares its bridge functions in a
+`nativephp.json` manifest.
+
+If you've been using the core APIs through their facades, nothing changes for you. Bridge functions are primarily
+relevant if you're building your own plugins.
+
+Read more in the [Bridge Functions documentation](../plugins/bridge-functions).
+
+## Plugin Marketplace
+
+Find ready-made plugins for common use cases, or get the Dev Kit to build your own.
+[Visit the NativePHP Plugin Marketplace](https://nativephp.com/plugins).
+
+## Command Reference
+
+v3 includes a comprehensive [Command Reference](commands) documenting all `native:*` Artisan commands
+with their options and usage.
+
+## Rebuild Required
+
+After upgrading, you must rebuild your native application:
+
+```shell
+php artisan native:install --force
+php artisan native:run
+```
+
+The `--force` flag ensures the `nativephp` directory is completely rebuilt with the v3 native project files.
diff --git a/resources/views/docs/mobile/4/getting-started/versioning.md b/resources/views/docs/mobile/4/getting-started/versioning.md
new file mode 100644
index 00000000..07640b03
--- /dev/null
+++ b/resources/views/docs/mobile/4/getting-started/versioning.md
@@ -0,0 +1,69 @@
+---
+title: Versioning Policy
+order: 500
+---
+
+NativePHP for Mobile follows [semantic versioning](https://semver.org) with a mobile-specific approach that distinguishes between
+Laravel-only changes and native code changes. This ensures predictable updates and optimal compatibility.
+
+Our aim is to limit the amount of work you need to do to get the latest updates and ensure everything works.
+
+We will aim to post update instructions with each release.
+
+## Release types
+
+### Patch releases
+
+Patch releases of `nativephp/mobile` should have **no breaking changes** and **only change Laravel/PHP code**.
+This will typically include bug fixes and dependency updates that don't affect native code.
+
+These releases should be completely compatible with the existing version of your native applications.
+
+This means that you can:
+
+- Safely update via `composer update`.
+- Avoid a complete rebuild (no need to `native:install --force`).
+- Allow for easier app updates avoiding the app stores.
+
+### Minor releases
+
+Minor releases may contain **native code changes**. Respecting semantic versioning, these still should not contain
+breaking changes, but there may be new native APIs, Kotlin/Swift updates, platform-specific features, or native
+dependency changes.
+
+Minor releases will:
+
+- Require a complete rebuild (`php artisan native:install --force`) to work with the latest APIs.
+- Need app store submission for distribution.
+- Include advance notice and migration guides where necessary.
+
+### Major releases
+
+Major releases are reserved for breaking changes. This will usually follow a period of deprecations so that you have
+time to make the necessary changes to your application code.
+
+## Version constraints
+
+We recommend using the [tilde range operator](https://getcomposer.org/doc/articles/versions.md#tilde-version-range-)
+with a full minimum patch release defined in your `composer.json`:
+
+```json
+{
+ "require": {
+ "nativephp/mobile": "~2.0.0"
+ }
+}
+```
+
+This automatically receives patch updates while giving you control over minor releases.
+
+## Your application versioning
+
+Just because we're using semantic versioning for the `nativephp/mobile` package, doesn't mean your app must follow that
+same scheme.
+
+You have complete freedom in versioning your own applications! You may use semantic versioning, codenames,
+date-based versions, or any scheme that works for your project, team or business.
+
+Remember that your app versions are usually public-facing (e.g. in store listings and on-device settings and update
+screens) and can be useful for customers to reference if they need to contact you for help and support.
diff --git a/resources/views/docs/mobile/4/plugins/_index.md b/resources/views/docs/mobile/4/plugins/_index.md
new file mode 100644
index 00000000..bbc1a5ec
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/_index.md
@@ -0,0 +1,4 @@
+---
+title: Plugins
+order: 60
+---
diff --git a/resources/views/docs/mobile/4/plugins/advanced-configuration.md b/resources/views/docs/mobile/4/plugins/advanced-configuration.md
new file mode 100644
index 00000000..c8840b30
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/advanced-configuration.md
@@ -0,0 +1,454 @@
+---
+title: Advanced Configuration
+order: 750
+---
+
+
+
+## Secrets & Environment Variables
+
+Plugins that require API keys, tokens, or other sensitive configuration can declare required environment variables using
+the `secrets` field. NativePHP validates these before building.
+
+```json
+{
+ "secrets": {
+ "MAPBOX_DOWNLOADS_TOKEN": {
+ "description": "Mapbox SDK download token from mapbox.com/account/access-tokens",
+ "required": true
+ },
+ "FIREBASE_API_KEY": {
+ "description": "Firebase project API key",
+ "required": false
+ }
+ }
+}
+```
+
+Each secret has:
+- **description** — Instructions for obtaining the value
+- **required** — Whether the build should fail if missing (default: `true`)
+
+### Using Secrets
+
+Reference secrets anywhere in your manifest using `${ENV_VAR}` syntax:
+
+```json
+{
+ "android": {
+ "repositories": [
+ {
+ "url": "https://api.mapbox.com/downloads/v2/releases/maven",
+ "credentials": {
+ "password": "${MAPBOX_DOWNLOADS_TOKEN}"
+ }
+ }
+ ]
+ }
+}
+```
+
+Placeholders are substituted at build time. If a required secret is missing, the build fails with a helpful message
+telling users exactly which variables to set in their `.env` file.
+
+## Android Manifest Components
+
+Plugins can register Android components (Activities, Services, Receivers, Providers) that get merged into the app's
+`AndroidManifest.xml`:
+
+```json
+{
+ "android": {
+ "activities": [
+ {
+ "name": ".MyPluginActivity",
+ "theme": "@style/Theme.AppCompat.Light.NoActionBar",
+ "exported": false,
+ "configChanges": "orientation|screenSize"
+ }
+ ],
+ "services": [
+ {
+ "name": ".BackgroundSyncService",
+ "exported": false,
+ "foregroundServiceType": "dataSync"
+ }
+ ],
+ "receivers": [
+ {
+ "name": ".BootReceiver",
+ "exported": true,
+ "intent-filters": [
+ {
+ "action": "android.intent.action.BOOT_COMPLETED",
+ "category": "android.intent.category.DEFAULT"
+ }
+ ]
+ }
+ ],
+ "providers": [
+ {
+ "name": ".MyContentProvider",
+ "authorities": "${applicationId}.myplugin.provider",
+ "exported": false,
+ "grantUriPermissions": true
+ }
+ ]
+ }
+}
+```
+
+### Component Names
+
+Names starting with `.` are relative to your plugin's package. For example, if your plugin uses the package
+`com.nativephp.plugins.mlplugin`, then `.MyActivity` becomes `com.nativephp.plugins.mlplugin.MyActivity`.
+
+Use fully qualified names for components outside your plugin's package.
+
+### Activity Attributes
+
+| Attribute | Description |
+|-----------|-------------|
+| `name` | Component class name (required) |
+| `theme` | Activity theme resource |
+| `exported` | Whether other apps can start this activity |
+| `configChanges` | Configuration changes the activity handles itself |
+| `launchMode` | Launch mode (standard, singleTop, singleTask, singleInstance) |
+| `screenOrientation` | Orientation lock (portrait, landscape, etc.) |
+| `intent-filters` | Array of intent filter configurations |
+
+### Service Attributes
+
+| Attribute | Description |
+|-----------|-------------|
+| `name` | Component class name (required) |
+| `exported` | Whether other apps can bind to this service |
+| `permission` | Permission required to access the service |
+| `foregroundServiceType` | Type for foreground services (camera, microphone, location, etc.). Supports array format for multiple types. |
+
+## Android Features
+
+Declare hardware or software features your plugin requires using the `features` array. These are added as
+`` elements in `AndroidManifest.xml`:
+
+```json
+{
+ "android": {
+ "features": [
+ {"name": "android.hardware.camera", "required": true},
+ {"name": "android.hardware.camera.autofocus", "required": false},
+ {"name": "android.hardware.bluetooth_le", "required": true}
+ ]
+ }
+}
+```
+
+Each feature has:
+- **name** — The feature name (e.g., `android.hardware.camera`)
+- **required** — Whether the app requires this feature (default: `true`)
+
+Setting `required: false` allows your app to be installed on devices without the feature, but you must check
+for availability at runtime.
+
+## Android Meta-Data
+
+Add application-level `` elements for SDK configuration:
+
+```json
+{
+ "android": {
+ "meta_data": [
+ {
+ "name": "com.google.android.geo.API_KEY",
+ "value": "${GOOGLE_MAPS_API_KEY}"
+ },
+ {
+ "name": "com.google.firebase.messaging.default_notification_icon",
+ "value": "@drawable/ic_notification"
+ }
+ ]
+ }
+}
+```
+
+Each entry has:
+- **name** — The meta-data key
+- **value** — The value (supports `${ENV_VAR}` placeholders)
+
+## Declarative Assets
+
+Copy static files to the native projects using the `assets` field. This is simpler than writing a `copy_assets` hook for
+basic file copying:
+
+```json
+{
+ "assets": {
+ "android": {
+ "models/detector.tflite": "assets/ml/detector.tflite",
+ "config/settings.xml": "res/raw/plugin_settings.xml"
+ },
+ "ios": {
+ "models/detector.mlmodel": "Resources/ml/detector.mlmodel",
+ "config/settings.plist": "Resources/plugin_settings.plist"
+ }
+ }
+}
+```
+
+The format is `"source": "destination"`:
+- **source** — Relative path from your plugin's `resources/` directory
+- **destination** — Where to place the file in the native project
+
+### Android Destinations
+
+- `assets/...` — App assets (accessible via `AssetManager`)
+- `res/raw/...` — Raw resources (accessible via `R.raw.*`)
+- `res/drawable/...` — Drawable resources
+
+### iOS Destinations
+
+- `Resources/...` — Bundle resources
+
+### Placeholder Substitution
+
+Text-based assets (XML, JSON, plist, etc.) support `${ENV_VAR}` placeholders that are replaced with environment
+variable values during the build:
+
+```xml
+
+
+ ${MY_PLUGIN_API_KEY}
+
+```
+
+
+
+## iOS Background Modes
+
+Enable background execution capabilities with the `background_modes` array. These values are added to
+`UIBackgroundModes` in `Info.plist`:
+
+```json
+{
+ "ios": {
+ "background_modes": ["audio", "fetch", "processing", "location"]
+ }
+}
+```
+
+Common values:
+- `audio` — Audio playback or recording
+- `fetch` — Background fetch
+- `processing` — Background processing tasks
+- `location` — Location updates
+- `remote-notification` — Push notification processing
+- `bluetooth-central` — Bluetooth LE central mode
+- `bluetooth-peripheral` — Bluetooth LE peripheral mode
+
+
+
+## iOS Entitlements
+
+Configure app entitlements for capabilities like Maps, App Groups, HealthKit, or iCloud:
+
+```json
+{
+ "ios": {
+ "entitlements": {
+ "com.apple.developer.maps": true,
+ "com.apple.security.application-groups": ["group.com.example.shared"],
+ "com.apple.developer.associated-domains": ["applinks:example.com"],
+ "com.apple.developer.healthkit": true
+ }
+ }
+}
+```
+
+Values can be:
+- **Boolean** — `true`/`false` for simple capabilities
+- **Array** — For capabilities requiring multiple values (App Groups, Associated Domains)
+- **String** — For single-value entitlements
+
+Entitlements are written to `NativePHP.entitlements`. If the file doesn't exist, it's created automatically.
+
+
+
+## iOS Capabilities
+
+Declare iOS capabilities your plugin requires. These are separate from entitlements and are used for Xcode project
+configuration:
+
+```json
+{
+ "ios": {
+ "capabilities": ["push-notifications", "background-modes", "healthkit"]
+ }
+}
+```
+
+## Minimum Platform Versions
+
+Specify minimum platform versions your plugin requires:
+
+```json
+{
+ "android": {
+ "min_version": 29
+ },
+ "ios": {
+ "min_version": "18.0"
+ }
+}
+```
+
+- **Android** — Minimum SDK version (integer, e.g., `29` for Android 10)
+- **iOS** — Minimum iOS version (string, e.g., `"18.0"`)
+
+NativePHP currently supports a minimum of Android SDK 29 and iOS 18. Your plugin's minimum versions cannot be lower
+than these. Use this field when your plugin requires a higher version than NativePHP's baseline.
+
+If a user's app targets a lower version than your plugin requires, they'll receive a warning during plugin validation.
+
+## Initialization Functions
+
+Plugins can specify native functions to call during app initialization. This is useful for SDKs that require early
+setup before any bridge functions are called:
+
+```json
+{
+ "android": {
+ "init_function": "com.myvendor.plugins.myplugin.MyPluginInit.initialize"
+ },
+ "ios": {
+ "init_function": "MyPluginInit.initialize"
+ }
+}
+```
+
+The init function is called once when the app starts, before any bridge functions are available. Use this for:
+- SDK initialization that must happen early
+- Setting up global state or singletons
+- Registering observers or listeners
+
+
+
+
+
+## Complete Example
+
+Here's a complete manifest for a plugin that integrates Firebase ML Kit with a custom Activity:
+
+```json
+{
+ "namespace": "FirebaseML",
+ "bridge_functions": [
+ {
+ "name": "FirebaseML.Analyze",
+ "android": "com.nativephp.plugins.firebaseml.AnalyzeFunctions.Analyze",
+ "ios": "FirebaseMLFunctions.Analyze"
+ }
+ ],
+ "events": [
+ "Vendor\\FirebaseML\\Events\\AnalysisComplete"
+ ],
+ "android": {
+ "permissions": [
+ "android.permission.CAMERA",
+ "android.permission.INTERNET"
+ ],
+ "features": [
+ {"name": "android.hardware.camera", "required": true}
+ ],
+ "dependencies": {
+ "implementation": [
+ "com.google.firebase:firebase-ml-vision:24.1.0",
+ "com.google.firebase:firebase-core:21.1.1"
+ ]
+ },
+ "activities": [
+ {
+ "name": ".CameraPreviewActivity",
+ "theme": "@style/Theme.AppCompat.Light.NoActionBar",
+ "exported": false,
+ "configChanges": "orientation|screenSize|keyboardHidden"
+ }
+ ],
+ "meta_data": [
+ {
+ "name": "com.google.firebase.ml.vision.DEPENDENCIES",
+ "value": "ocr"
+ }
+ ]
+ },
+ "ios": {
+ "info_plist": {
+ "NSCameraUsageDescription": "Camera is used for ML analysis"
+ },
+ "dependencies": {
+ "pods": [
+ {"name": "Firebase/MLVision", "version": "~> 10.0"},
+ {"name": "Firebase/Core", "version": "~> 10.0"}
+ ]
+ },
+ "background_modes": ["processing"],
+ "entitlements": {
+ "com.apple.developer.associated-domains": ["applinks:example.com"]
+ }
+ },
+ "assets": {
+ "android": {
+ "google-services.json": "google-services.json"
+ },
+ "ios": {
+ "GoogleService-Info.plist": "Resources/GoogleService-Info.plist"
+ }
+ },
+ "secrets": {
+ "FIREBASE_API_KEY": {
+ "description": "Firebase API key from Firebase Console",
+ "required": true
+ }
+ },
+ "hooks": {
+ "pre_compile": "nativephp:firebase-ml:setup"
+ }
+}
+```
+
+## Official Plugins & Dev Kit
+
+Browse ready-made plugins for common use cases, or get the Plugin Dev Kit to build your own.
+[Visit the NativePHP Plugin Marketplace →](https://nativephp.com/plugins)
diff --git a/resources/views/docs/mobile/4/plugins/best-practices.md b/resources/views/docs/mobile/4/plugins/best-practices.md
new file mode 100644
index 00000000..2d3f942d
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/best-practices.md
@@ -0,0 +1,362 @@
+---
+title: Best Practices
+order: 900
+---
+
+## Overview
+
+Building a plugin that works is only the first step. Building one that's easy to install, well-documented, and
+tested across platforms and frontend stacks is what makes a plugin worth publishing.
+
+This page covers the standards we expect from plugins listed on the
+[NativePHP Plugin Marketplace](https://nativephp.com/plugins) and what makes the difference between a plugin
+developers trust and one they abandon after 10 minutes.
+
+## Documentation
+
+Every plugin must ship with a comprehensive README. This is the first thing developers see and it determines
+whether they'll bother installing your plugin at all.
+
+### Required README Sections
+
+Your README should include all of the following:
+
+**Installation:**
+
+```markdown
+## Installation
+
+composer require vendor/my-plugin
+
+php artisan native:plugin:register vendor/my-plugin
+```
+
+**PHP usage with complete examples:**
+
+```markdown
+## Usage (PHP)
+
+use Vendor\MyPlugin\Facades\MyPlugin;
+
+// Basic usage
+$result = MyPlugin::doSomething(['option' => 'value']);
+
+// Listening for events in Livewire
+#[OnNative(SomethingCompleted::class)]
+public function handleResult($data)
+{
+ $this->result = $data['result'];
+}
+```
+
+**JavaScript usage for SPA frameworks:**
+
+```markdown
+## Usage (JavaScript)
+
+import { DoSomething } from 'vendor-my-plugin';
+
+// In Vue/React components
+const result = await DoSomething({ option: 'value' });
+```
+
+**Available methods, events, and required permissions** — document every public method your facade exposes,
+every event your plugin dispatches, and every permission it requires. Don't make developers read your source
+code to figure out what your plugin does.
+
+**Environment variables and secrets** — if your plugin requires API keys or tokens, document exactly where to
+get them and how to configure them.
+
+### Keep Documentation Current
+
+Update your README whenever you change your plugin's API. Outdated documentation is worse than no documentation — it
+actively misleads developers and wastes their time. If a method signature changes, update the README in the same
+commit.
+
+## JavaScript Implementations
+
+Every plugin must provide a JavaScript library alongside the PHP facade. Many NativePHP apps use Inertia with
+Vue or React, and those developers need to call your native functions directly from their components without
+going through Livewire.
+
+Your `resources/js/` directory should export clean, documented functions:
+
+```js
+// resources/js/index.js
+const baseUrl = '/_native/api/call';
+
+async function bridgeCall(method, params = {}) {
+ const response = await fetch(baseUrl, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ method, params })
+ });
+ return response.json();
+}
+
+export async function DoSomething(options = {}) {
+ return bridgeCall('MyPlugin.DoSomething', options);
+}
+
+export async function DoSomethingElse(id, options = {}) {
+ return bridgeCall('MyPlugin.DoSomethingElse', { id, ...options });
+}
+```
+
+Provide a named export for every bridge function your plugin exposes. If your plugin dispatches events, document
+how to listen for them in both Livewire and SPA contexts.
+
+### npm Package
+
+Consider publishing your JavaScript library as an npm package so developers can `npm install` it and get proper
+TypeScript definitions, autocompletion, and tree-shaking.
+
+## Testing on Real Devices
+
+Simulators and emulators are useful for development, but they don't catch everything. Many native APIs behave
+differently — or don't work at all — on real hardware. Camera, biometrics, Bluetooth, NFC, GPS, and sensors all
+require real device testing.
+
+### Requirements
+
+- **Android** — test on a physical Android device. Don't rely solely on the Android emulator.
+- **iOS** — test on a physical iPhone or iPad. The iOS simulator doesn't support camera, biometrics, or many
+ hardware features.
+- Test on devices running both current and previous major OS versions when possible.
+
+### Provide a Test App
+
+Ideally, provide a link to a test build so that the NativePHP team and other developers can verify your plugin
+works without having to set up the full build chain:
+
+- **iOS** — distribute via [TestFlight](https://developer.apple.com/testflight/)
+- **Android** — distribute via a [Google Play testing track](https://support.google.com/googleplay/android-developer/answer/9845334)
+ (internal, closed, or open testing)
+
+Include the test app link in your README and your plugin marketplace submission. This significantly speeds up the
+review process and builds trust with users.
+
+## Frontend Stack Compatibility
+
+NativePHP apps use different frontend stacks. Your plugin must work with all of them.
+
+### Test With
+
+- **Livewire v3** — the most common stack for NativePHP apps. Test that `#[OnNative]` event listeners work,
+ that facade calls from Livewire actions return correct data, and that loading states behave properly.
+- **Livewire v4** — test forward compatibility.
+- **Inertia + Vue** — test your JavaScript library imports and bridge calls from Vue components. Verify events
+ are received correctly.
+- **Inertia + React** — same as Vue. Test imports, bridge calls, and event handling from React components.
+
+If your plugin only supports a subset of these stacks, document this clearly in your README. But aim for full
+compatibility — it's the difference between a plugin that works for everyone and one that fragments the ecosystem.
+
+### Example: Livewire Component
+
+```php
+use Livewire\Component;
+use Native\Mobile\Attributes\OnNative;
+use Vendor\MyPlugin\Facades\MyPlugin;
+use Vendor\MyPlugin\Events\ScanComplete;
+
+class Scanner extends Component
+{
+ public ?string $result = null;
+
+ public function scan(): void
+ {
+ MyPlugin::startScan();
+ }
+
+ #[OnNative(ScanComplete::class)]
+ public function handleScan($data): void
+ {
+ $this->result = $data['value'];
+ }
+
+ public function render()
+ {
+ return view('livewire.scanner');
+ }
+}
+```
+
+### Example: Vue Component (Inertia)
+
+```vue
+
+
+
+
+ @{{ result }}
+
+```
+
+## Boost Guidelines
+
+If your users use [Laravel Boost](https://laravel.com/ai/boost), providing Boost guidelines makes your
+plugin dramatically easier to work with. When developers ask their assistant to use your plugin, it will
+know exactly how — which methods to call, what events to listen for, and how to handle responses.
+
+Generate guidelines with:
+
+```shell
+php artisan native:plugin:boost
+```
+
+This creates `resources/boost/guidelines/core.blade.php` in your plugin. Edit it to include:
+
+- All available facade methods with descriptions and parameter types
+- All events your plugin dispatches with their payload shapes
+- JavaScript usage examples
+- Common patterns and gotchas
+- Required permissions and configuration
+
+When users install your plugin and run `php artisan boost:install`, these guidelines are automatically loaded.
+
+## Validation
+
+Run the validation command before every release:
+
+```shell
+php artisan native:plugin:validate
+```
+
+This catches:
+- Manifest syntax errors and missing required fields
+- Bridge function declarations that don't match native code
+- Hook commands that aren't registered
+- Missing declared assets
+
+Your plugin should pass validation with zero errors. If you're using the
+[Plugin Dev Kit](/products/plugin-dev-kit), use the `/validate-nativephp-plugin` command which runs additional
+checks beyond the Artisan command.
+
+Fix every warning too — they often indicate issues that will cause confusing failures for your users at build
+time or runtime.
+
+## Automated Review Checks
+
+When you submit your plugin, we run automated checks against your repository. These must all pass before
+your plugin can be approved. You can also run `php artisan native:plugin:validate` locally to catch issues early.
+
+### Required for Approval
+
+The following checks must pass before your plugin can be approved:
+
+**License file** — Your repository must include a `LICENSE`, `LICENSE.md`, or `LICENSE.txt` file at the root.
+
+**Release version** — Your repository must have at least one GitHub release or tag.
+
+**Webhook configured** — A GitHub webhook must be configured for your repository so that your plugin data
+syncs automatically when you push changes or create releases. We'll attempt to set this up automatically
+when you submit. If it can't be installed automatically, you'll see manual setup instructions on your plugin
+dashboard.
+
+**Support channel** — You must provide a support email address or URL when submitting your plugin so
+developers can reach you with questions or issues. You can update this anytime from your plugin dashboard.
+
+### Additional Checks
+
+**iOS native code** — Your plugin must include native Swift code in `resources/ios/Sources/`. See
+[Bridge Functions](/docs/mobile/3/plugins/bridge-functions) for the implementation pattern.
+
+**Android native code** — Your plugin must include native Kotlin code in `resources/android/src/`. See
+[Bridge Functions](/docs/mobile/3/plugins/bridge-functions) for the implementation pattern.
+
+**JavaScript library** — Your plugin must include a JavaScript library in `resources/js/` that exports
+a function for every bridge function. This allows Inertia + Vue/React developers to call your native functions
+directly. See the [JavaScript Implementations](#javascript-implementations) section above.
+
+**Require `nativephp/mobile`** — Your `composer.json` must require the `nativephp/mobile` SDK. This ensures
+your plugin is properly integrated with the NativePHP build pipeline:
+
+```json
+{
+ "require": {
+ "nativephp/mobile": "^3.0"
+ }
+}
+```
+
+**iOS `min_version`** — Your `nativephp.json` must specify a minimum iOS version. See
+[Advanced Configuration](/docs/mobile/3/plugins/advanced-configuration) for details:
+
+```json
+{
+ "ios": {
+ "min_version": "18.0"
+ }
+}
+```
+
+**Android `min_version`** — Your `nativephp.json` must specify a minimum Android SDK version. See
+[Advanced Configuration](/docs/mobile/3/plugins/advanced-configuration) for details:
+
+```json
+{
+ "android": {
+ "min_version": 29
+ }
+}
+```
+
+## Checklist
+
+Before submitting your plugin to the [NativePHP Plugin Marketplace](https://nativephp.com/plugins), verify:
+
+**Required for approval:**
+
+- [ ] LICENSE, LICENSE.md, or LICENSE.txt file in your repository
+- [ ] At least one GitHub release or tag
+- [ ] GitHub webhook configured (automatic or manual)
+- [ ] Support channel provided (email or URL)
+
+**Automated checks:**
+
+- [ ] iOS native code in `resources/ios/Sources/`
+- [ ] Android native code in `resources/android/src/`
+- [ ] JavaScript library in `resources/js/`
+- [ ] `nativephp/mobile` required in `composer.json`
+- [ ] iOS `min_version` set in `nativephp.json`
+- [ ] Android `min_version` set in `nativephp.json`
+
+**Documentation & quality:**
+
+- [ ] README documents installation, PHP usage, and JS usage with complete examples
+- [ ] README documents all public methods, events, and required permissions
+- [ ] `php artisan native:plugin:validate` passes with zero errors
+- [ ] Tested on a physical Android device
+- [ ] Tested on a physical iOS device (if iOS is supported)
+- [ ] Tested with Livewire v3 and v4
+- [ ] Tested with Inertia + Vue
+- [ ] Tested with Inertia + React
+- [ ] Boost guidelines are included (`php artisan native:plugin:boost`)
+- [ ] TestFlight and/or Google Play testing track link provided
+- [ ] All secrets and environment variables are documented
+- [ ] Changelog is maintained for version history
+
+## Official Plugins & Dev Kit
+
+The official NativePHP plugins follow all of these best practices and serve as reference implementations. Browse
+them on the [Plugin Marketplace](https://nativephp.com/plugins) for examples of well-structured, well-documented
+plugins.
+
+Need help building your plugin to these standards? The [Plugin Dev Kit](/products/plugin-dev-kit) generates
+production-ready plugins with proper structure, documentation, and Boost guidelines built in.
+
+[Get the Plugin Dev Kit →](/products/plugin-dev-kit)
\ No newline at end of file
diff --git a/resources/views/docs/mobile/4/plugins/bridge-functions.md b/resources/views/docs/mobile/4/plugins/bridge-functions.md
new file mode 100644
index 00000000..5de9df57
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/bridge-functions.md
@@ -0,0 +1,140 @@
+---
+title: Bridge Functions
+order: 400
+---
+
+## How Bridge Functions Work
+
+Bridge functions are the connection between your PHP code and native platform code. When you call a method like
+`MyPlugin::doSomething()`, NativePHP routes that to your Swift or Kotlin implementation running on the device.
+
+The flow:
+1. PHP calls `nativephp_call('MyPlugin.DoSomething', $params)`
+2. The native bridge locates the registered function
+3. Your native code executes and returns a response
+4. PHP receives the result
+
+## Declaring Bridge Functions
+
+In your `nativephp.json`, declare each function with its platform implementations:
+
+```json
+{
+ "bridge_functions": [
+ {
+ "name": "MyPlugin.DoSomething",
+ "ios": "MyPluginFunctions.DoSomething",
+ "android": "com.myvendor.plugins.myplugin.MyPluginFunctions.DoSomething",
+ "description": "Does something useful"
+ }
+ ]
+}
+```
+
+The `name` is what PHP uses. The platform-specific values point to your native class and method.
+
+### Naming Convention
+
+- **`name`** — A unique identifier like `MyPlugin.DoSomething`. This is what PHP code uses.
+- **`ios`** — Swift enum/class path: `EnumName.ClassName`
+- **`android`** — Full Kotlin class path including your vendor package (e.g., `com.myvendor.plugins.myplugin.ClassName`)
+
+## Swift Implementation (iOS)
+
+Create your functions in `resources/ios/Sources/`:
+
+```swift
+import Foundation
+
+enum MyPluginFunctions {
+
+ class DoSomething: BridgeFunction {
+ func execute(parameters: [String: Any]) throws -> [String: Any] {
+ let option = parameters["option"] as? String ?? ""
+
+ // Do your native work here
+
+ return BridgeResponse.success(data: [
+ "result": "completed",
+ "option": option
+ ])
+ }
+ }
+}
+```
+
+Key points:
+- Implement the `BridgeFunction` protocol
+- Parameters come as a dictionary
+- Return using `BridgeResponse.success()` or `BridgeResponse.error()`
+
+## Kotlin Implementation (Android)
+
+Create your functions in `resources/android/src/`. Use your own vendor-namespaced package:
+
+```kotlin
+package com.myvendor.plugins.myplugin
+
+import com.nativephp.mobile.bridge.BridgeFunction
+import com.nativephp.mobile.bridge.BridgeResponse
+
+object MyPluginFunctions {
+
+ class DoSomething : BridgeFunction {
+ override fun execute(parameters: Map): Map {
+ val option = parameters["option"] as? String ?: ""
+
+ // Do your native work here
+
+ return BridgeResponse.success(mapOf(
+ "result" to "completed",
+ "option" to option
+ ))
+ }
+ }
+}
+```
+
+The package declaration determines where your file is placed during compilation. Using `com.myvendor.plugins.myplugin` ensures
+your code is isolated from other plugins and the core NativePHP code.
+
+## Calling from PHP
+
+Create a facade method that calls your bridge function:
+
+```php
+class MyPlugin
+{
+ public function doSomething(array $options = []): mixed
+ {
+ if (function_exists('nativephp_call')) {
+ $result = nativephp_call('MyPlugin.DoSomething', json_encode($options));
+
+ return json_decode($result)?->data;
+ }
+
+ return null;
+ }
+}
+```
+
+## Error Handling
+
+Return errors from native code using `BridgeResponse.error()`:
+
+```swift
+// Swift
+return BridgeResponse.error(message: "Something went wrong")
+```
+
+```kotlin
+// Kotlin
+return BridgeResponse.error("Something went wrong")
+```
+
+The error message is available in PHP through the response.
+
+## Official Plugins & Dev Kit
+
+Need native functionality without writing Kotlin or Swift? Browse ready-made plugins or get the Dev Kit to build your own.
+[Visit the NativePHP Plugin Marketplace →](https://nativephp.com/plugins)
diff --git a/resources/views/docs/mobile/4/plugins/core/_index.md b/resources/views/docs/mobile/4/plugins/core/_index.md
new file mode 100644
index 00000000..00b3c8ea
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/_index.md
@@ -0,0 +1,4 @@
+---
+title: Core Plugins
+order: 900
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/biometrics.md b/resources/views/docs/mobile/4/plugins/core/biometrics.md
new file mode 100644
index 00000000..90d4d0b5
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/biometrics.md
@@ -0,0 +1,4 @@
+---
+title: Biometrics
+order: 100
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/browser.md b/resources/views/docs/mobile/4/plugins/core/browser.md
new file mode 100644
index 00000000..b4a7af4e
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/browser.md
@@ -0,0 +1,4 @@
+---
+title: Browser
+order: 200
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/camera.md b/resources/views/docs/mobile/4/plugins/core/camera.md
new file mode 100644
index 00000000..b865c1d6
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/camera.md
@@ -0,0 +1,4 @@
+---
+title: Camera
+order: 300
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/device.md b/resources/views/docs/mobile/4/plugins/core/device.md
new file mode 100644
index 00000000..8ed74240
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/device.md
@@ -0,0 +1,4 @@
+---
+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
new file mode 100644
index 00000000..788aec6e
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/dialog.md
@@ -0,0 +1,4 @@
+---
+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
new file mode 100644
index 00000000..9136481d
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/file.md
@@ -0,0 +1,4 @@
+---
+title: File
+order: 600
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/firebase.md b/resources/views/docs/mobile/4/plugins/core/firebase.md
new file mode 100644
index 00000000..146d6479
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/firebase.md
@@ -0,0 +1,4 @@
+---
+title: Firebase (Push Notifications)
+order: 650
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/geolocation.md b/resources/views/docs/mobile/4/plugins/core/geolocation.md
new file mode 100644
index 00000000..1aebb4c4
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/geolocation.md
@@ -0,0 +1,4 @@
+---
+title: Geolocation
+order: 700
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/microphone.md b/resources/views/docs/mobile/4/plugins/core/microphone.md
new file mode 100644
index 00000000..da177ff6
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/microphone.md
@@ -0,0 +1,4 @@
+---
+title: Microphone
+order: 900
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/network.md b/resources/views/docs/mobile/4/plugins/core/network.md
new file mode 100644
index 00000000..d15b6462
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/network.md
@@ -0,0 +1,4 @@
+---
+title: Network
+order: 1000
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/scanner.md b/resources/views/docs/mobile/4/plugins/core/scanner.md
new file mode 100644
index 00000000..d393b24d
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/scanner.md
@@ -0,0 +1,4 @@
+---
+title: Scanner
+order: 1200
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/secure-storage.md b/resources/views/docs/mobile/4/plugins/core/secure-storage.md
new file mode 100644
index 00000000..b63e58ce
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/secure-storage.md
@@ -0,0 +1,4 @@
+---
+title: SecureStorage
+order: 1300
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/share.md b/resources/views/docs/mobile/4/plugins/core/share.md
new file mode 100644
index 00000000..9c63624d
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/share.md
@@ -0,0 +1,4 @@
+---
+title: Share
+order: 1400
+---
diff --git a/resources/views/docs/mobile/4/plugins/core/system.md b/resources/views/docs/mobile/4/plugins/core/system.md
new file mode 100644
index 00000000..44f7e38d
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/core/system.md
@@ -0,0 +1,4 @@
+---
+title: System
+order: 1500
+---
diff --git a/resources/views/docs/mobile/4/plugins/creating-plugins.md b/resources/views/docs/mobile/4/plugins/creating-plugins.md
new file mode 100644
index 00000000..216a8208
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/creating-plugins.md
@@ -0,0 +1,376 @@
+---
+title: Creating Plugins
+order: 300
+---
+
+## Scaffolding a Plugin
+
+The quickest way to create a plugin is with the interactive scaffolding command:
+
+```shell
+php artisan native:plugin:create
+```
+
+This walks you through naming, namespace selection, and feature options, then generates a complete plugin structure.
+
+
+
+## Plugin Structure
+
+A plugin follows a standard layout:
+
+```
+my-plugin/
+├── composer.json # Package metadata, type must be "nativephp-plugin"
+├── nativephp.json # Plugin manifest
+├── src/
+│ ├── MyPluginServiceProvider.php
+│ ├── MyPlugin.php # Main class
+│ ├── Facades/
+│ │ └── MyPlugin.php
+│ ├── Events/
+│ │ └── SomethingHappened.php
+│ └── Commands/ # Lifecycle hook commands
+├── resources/
+│ ├── android/src/ # Kotlin bridge functions
+│ ├── ios/Sources/ # Swift bridge functions
+│ └── js/ # JavaScript library stubs
+```
+
+## Android Package Naming
+
+Android/Kotlin code must declare a package at the top of each file. Use your own vendor-namespaced package to avoid
+conflicts:
+
+```kotlin
+// resources/android/src/MyPluginFunctions.kt
+package com.myvendor.plugins.myplugin
+
+import com.nativephp.mobile.bridge.BridgeFunction
+import com.nativephp.mobile.bridge.BridgeResponse
+
+object MyPluginFunctions {
+ class DoSomething : BridgeFunction {
+ override fun execute(parameters: Map): Map {
+ return BridgeResponse.success(mapOf("status" to "done"))
+ }
+ }
+}
+```
+
+The compiler places files based on their package declaration, so `package com.myvendor.plugins.myplugin` results in the file
+being placed at `app/src/main/java/com/myvendor/plugins/myplugin/MyPluginFunctions.kt`.
+
+
+
+Reference the full package path in your manifest's bridge functions:
+
+```json
+{
+ "bridge_functions": [{
+ "name": "MyPlugin.DoSomething",
+ "android": "com.myvendor.plugins.myplugin.MyPluginFunctions.DoSomething"
+ }]
+}
+```
+
+## The composer.json
+
+Your `composer.json` must specify the plugin type:
+
+```json
+{
+ "name": "vendor/my-plugin",
+ "type": "nativephp-plugin",
+ "extra": {
+ "laravel": {
+ "providers": ["Vendor\\MyPlugin\\MyPluginServiceProvider"]
+ },
+ "nativephp": {
+ "manifest": "nativephp.json"
+ }
+ }
+}
+```
+
+The `type: nativephp-plugin` tells NativePHP to look for native code in this package.
+
+## The nativephp.json Manifest
+
+The manifest declares native-specific configuration for your plugin. Package metadata (`name`, `version`, `description`,
+`service_provider`) comes from your `composer.json` — don't duplicate it here.
+
+```json
+{
+ "namespace": "MyPlugin",
+ "bridge_functions": [
+ {
+ "name": "MyPlugin.DoSomething",
+ "ios": "MyPluginFunctions.DoSomething",
+ "android": "com.nativephp.plugins.myplugin.MyPluginFunctions.DoSomething"
+ }
+ ],
+ "events": ["Vendor\\MyPlugin\\Events\\SomethingHappened"],
+ "android": {
+ "permissions": ["android.permission.CAMERA"],
+ "dependencies": {
+ "implementation": ["com.google.mlkit:barcode-scanning:17.2.0"]
+ }
+ },
+ "ios": {
+ "info_plist": {
+ "NSCameraUsageDescription": "Camera is used for scanning"
+ },
+ "dependencies": {
+ "pods": [{"name": "GoogleMLKit/BarcodeScanning", "version": "~> 4.0"}]
+ }
+ }
+}
+```
+
+### Manifest Fields
+
+| Field | Required | Description |
+|-------|----------|-------------|
+| `namespace` | Yes | Namespace for the plugin (used for code generation and directory structure) |
+| `bridge_functions` | No | Array of native function mappings |
+| `events` | No | Event classes the plugin dispatches |
+| `android.permissions` | No | Android permission strings |
+| `android.features` | No | Android uses-feature declarations |
+| `android.dependencies` | No | Gradle dependencies |
+| `android.repositories` | No | Custom Maven repositories |
+| `android.activities` | No | Activities to register in manifest |
+| `android.services` | No | Services to register in manifest |
+| `android.receivers` | No | Broadcast receivers to register |
+| `android.providers` | No | Content providers to register |
+| `android.meta_data` | No | Application meta-data entries |
+| `android.min_version` | No | Minimum Android SDK version required |
+| `android.init_function` | No | Native function to call during app initialization |
+| `ios.info_plist` | No | Info.plist entries (permissions, API keys) |
+| `ios.dependencies` | No | Swift packages and CocoaPods |
+| `ios.background_modes` | No | UIBackgroundModes values |
+| `ios.entitlements` | No | App entitlements |
+| `ios.capabilities` | No | iOS capabilities for Xcode project |
+| `ios.min_version` | No | Minimum iOS version required |
+| `ios.init_function` | No | Native function to call during app initialization |
+| `assets` | No | Declarative asset copying |
+| `hooks` | No | Lifecycle hook commands |
+| `secrets` | No | Required environment variables |
+
+See [Advanced Configuration](advanced-configuration) for detailed documentation on each field.
+
+## Local Development
+
+During development, add your plugin to your app's `composer.json` as a path repository:
+
+```json
+{
+ "repositories": [
+ {"type": "path", "url": "../packages/my-plugin"}
+ ]
+}
+```
+
+Then require it:
+
+```shell
+composer require vendor/my-plugin
+```
+
+Changes to your plugin's PHP code are picked up immediately. Changes to native code require a rebuild with
+`php artisan native:run`.
+
+When testing significant changes to your plugin's native code or manifest, you may need to force a fresh install of
+the native projects:
+
+```shell
+php artisan native:install --force
+```
+
+This ensures the native projects are rebuilt from scratch with your latest plugin configuration.
+
+
+
+## Registering Plugins
+
+After installing a plugin with Composer, you need to register it so it gets compiled into your native builds.
+
+### First Time Setup
+
+Publish the NativeServiceProvider:
+
+```shell
+php artisan vendor:publish --tag=nativephp-plugins-provider
+```
+
+This creates `app/Providers/NativeServiceProvider.php`.
+
+### Register a Plugin
+
+```shell
+php artisan native:plugin:register vendor/plugin-name
+```
+
+This automatically adds the plugin's service provider to your `plugins()` array:
+
+```php
+public function plugins(): array
+{
+ return [
+ \Vendor\PluginName\PluginNameServiceProvider::class,
+ ];
+}
+```
+
+### List Plugins
+
+```shell
+# Show registered plugins
+php artisan native:plugin:list
+
+# Show all installed plugins (including unregistered)
+php artisan native:plugin:list --all
+```
+
+### Remove a Plugin
+
+```shell
+php artisan native:plugin:register vendor/plugin-name --remove
+```
+
+
+
+## JavaScript Library
+
+Plugins can provide a JavaScript library for SPA frameworks. The scaffolding creates a stub in `resources/js/`:
+
+```js
+// resources/js/myPlugin.js
+const baseUrl = '/_native/api/call';
+
+async function bridgeCall(method, params = {}) {
+ const response = await fetch(baseUrl, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ method, params })
+ });
+ return response.json();
+}
+
+export async function doSomething(options = {}) {
+ return bridgeCall('MyPlugin.DoSomething', options);
+}
+```
+
+Users can then import your functions directly in Vue, React, or vanilla JS.
+
+## NativePHP Plugin Development Kit
+
+
+
+If you're using [Claude Code](https://claude.com/claude-code), the Plugin Development Kit supercharges your workflow
+with specialized agents trained on NativePHP's architecture.
+
+### What's Included
+
+- **Kotlin/Android Expert Agent** — Writes correct bridge functions, handles Android lifecycles, configures Gradle
+- **Swift/iOS Expert Agent** — Implements iOS bridge functions, manages Info.plist, configures SPM/CocoaPods
+- **Plugin Architect Agent** — Designs plugin structure, manifest configuration, and Laravel integration
+- **Interactive Commands** — `/create-nativephp-plugin` scaffolds complete plugins from a description
+- **Validation Tools** — `/validate-nativephp-plugin` catches errors before you build
+
+### Why It's Worth It
+
+Writing native mobile code is hard. These agents understand:
+
+- NativePHP's bridge function patterns and response formats
+- Platform-specific APIs and how to expose them to PHP
+- Permission declarations, entitlements, and manifest configuration
+- Event dispatching from native code to Livewire components
+- Dependency management across Gradle, CocoaPods, and SPM
+
+Instead of learning two new languages and their ecosystems, describe what you need and let the agents handle the
+implementation details.
+
+[Get the Plugin Dev Kit →](/products/plugin-dev-kit)
+
+## AI Development Tools
+
+NativePHP includes built-in commands for AI-assisted plugin development.
+
+### Install Development Agents
+
+Install specialized AI agents for plugin development:
+
+```shell
+php artisan native:plugin:install-agent
+```
+
+This copies agent definition files to your project's `.claude/agents/` directory. Available agents include:
+
+- **kotlin-android-expert** — Deep Android/Kotlin native development
+- **swift-ios-expert** — Deep iOS/Swift native development
+- **js-bridge-expert** — JavaScript/TypeScript client integration
+- **plugin-writer** — General plugin scaffolding and structure
+- **plugin-docs-writer** — Documentation and Boost guidelines
+
+Use `--all` to install all agents without prompting, or `--force` to overwrite existing files.
+
+### Create Boost Guidelines
+
+If you're using [Boost](https://laravel.com/ai/boost), create AI guidelines for your plugin:
+
+```shell
+php artisan native:plugin:boost
+```
+
+This generates a `resources/boost/guidelines/core.blade.php` file in your plugin that documents:
+
+- How to use your plugin's facade
+- Available methods and their descriptions
+- Events and how to listen for them
+- JavaScript usage examples
+
+When users install your plugin and run `php artisan boost:install`, these guidelines are automatically loaded,
+helping AI assistants understand how to use your plugin correctly.
+
+## Ready to Build?
+
+You now have everything you need to create NativePHP plugins. For most developers, the
+[Plugin Development Kit](/products/plugin-dev-kit) is the fastest path from idea to working plugin — it
+handles the native code complexity so you can focus on what your plugin does, not how to write Kotlin and Swift.
diff --git a/resources/views/docs/mobile/4/plugins/events.md b/resources/views/docs/mobile/4/plugins/events.md
new file mode 100644
index 00000000..1b9eb9ce
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/events.md
@@ -0,0 +1,109 @@
+---
+title: Events
+order: 500
+---
+
+## Dispatching Events from Native Code
+
+Many native operations are asynchronous — ML inference, sensor readings, background tasks. Your native code needs a
+way to send results back to PHP when they're ready. That's where events come in.
+
+Events are dispatched from native code and received by your Livewire components.
+
+## Declaring Events
+
+Add your event classes to the manifest:
+
+```json
+{
+ "events": [
+ "Vendor\\MyPlugin\\Events\\ProcessingComplete",
+ "Vendor\\MyPlugin\\Events\\ProcessingError"
+ ]
+}
+```
+
+## Creating Event Classes
+
+Events are simple PHP classes:
+
+```php
+namespace Vendor\MyPlugin\Events;
+
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+class ProcessingComplete
+{
+ use Dispatchable, SerializesModels;
+
+ public function __construct(
+ public string $result,
+ public ?string $id = null
+ ) {}
+}
+```
+
+
+
+## Swift Event Dispatching
+
+Dispatch events using `LaravelBridge.shared.send`:
+
+```swift
+// Build your payload
+let payload: [String: Any] = [
+ "result": processedData,
+ "id": requestId
+]
+
+// Dispatch to PHP
+LaravelBridge.shared.send?(
+ "Vendor\\MyPlugin\\Events\\ProcessingComplete",
+ payload
+)
+```
+
+This runs synchronously on the main thread, so wrap in `DispatchQueue.main.async` if needed.
+
+## Kotlin Event Dispatching
+
+Dispatch events using `NativeActionCoordinator.dispatchEvent`:
+
+```kotlin
+import android.os.Handler
+import android.os.Looper
+
+// Build your payload
+val payload = JSONObject().apply {
+ put("result", processedData)
+ put("id", requestId)
+}
+
+// Must dispatch on main thread
+Handler(Looper.getMainLooper()).post {
+ NativeActionCoordinator.dispatchEvent(
+ activity,
+ "Vendor\\MyPlugin\\Events\\ProcessingComplete",
+ payload.toString()
+ )
+}
+```
+
+
+
+## Official Plugins & Dev Kit
+
+Skip the complexity — browse ready-made plugins or get the Dev Kit to build your own.
+[Visit the NativePHP Plugin Marketplace →](https://nativephp.com/plugins)
diff --git a/resources/views/docs/mobile/4/plugins/introduction.md b/resources/views/docs/mobile/4/plugins/introduction.md
new file mode 100644
index 00000000..111120df
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/introduction.md
@@ -0,0 +1,69 @@
+---
+title: Introduction
+order: 100
+---
+
+## What are Plugins?
+
+Plugins extend NativePHP for Mobile with native functionality. Need on-device ML,
+Bluetooth, or a custom hardware integration? Plugins let you add these capabilities without forking the core package.
+
+A plugin is a Composer package that bundles:
+- **PHP code** — Facades, events, and service providers you use in Laravel
+- **Native code** — Swift (iOS) and Kotlin (Android) implementations
+- **A manifest** — Declares what the plugin provides and needs
+
+When you build your app, NativePHP compiles registered plugins' native code into your app.
+
+## Why Plugins?
+
+All native functionality in NativePHP Mobile comes through plugins — including official plugins for camera, biometrics,
+push notifications, and more. This architecture means:
+
+- **Official plugins** provide core functionality and serve as reference implementations
+- **Community plugins** extend the platform with new capabilities
+- **Your own plugins** let you integrate proprietary SDKs or custom native code
+
+Install a plugin and its native features become available to your PHP code through a simple facade.
+
+## What Plugins Can Do
+
+Plugins have full access to native platform capabilities:
+
+- **Bridge functions** — Call Swift/Kotlin code from PHP and get results back
+- **Events** — Dispatch events from native code to your Livewire components
+- **Permissions** — Declare required permissions (camera, location, etc.)
+- **Dependencies** — Include native libraries via Gradle, CocoaPods, or Swift Package Manager
+- **Custom repositories** — Use private Maven repos for enterprise SDKs
+- **Android components** — Register Activities, Services, Receivers, and Content Providers
+- **Assets** — Bundle ML models, configuration files, and other resources
+- **Lifecycle hooks** — Run code at build time to download models, validate config, etc.
+- **Secrets** — Declare required environment variables with validation
+
+## Plugin Architecture
+
+Plugins follow the same patterns as NativePHP's core:
+
+```php
+use Vendor\MyPlugin\Facades\MyPlugin;
+
+// Call native functions
+MyPlugin::doSomething();
+
+// Listen for events
+#[OnNative(MyPlugin\Events\SomethingHappened::class)]
+public function handleResult($data)
+{
+ // Handle it
+}
+```
+
+The native code runs on-device, communicates with your PHP through the bridge, and dispatches events back to your
+Livewire components. It's the same model you're already using.
+
+## Getting Started
+
+Ready to build your own plugin? Check out [Creating Plugins](./creating-plugins) for the full guide.
+
+Or browse the [NativePHP Plugin Marketplace](https://nativephp.com/plugins) for ready-made plugins and the Dev Kit
+to build your own.
diff --git a/resources/views/docs/mobile/4/plugins/lifecycle-hooks.md b/resources/views/docs/mobile/4/plugins/lifecycle-hooks.md
new file mode 100644
index 00000000..315c0b13
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/lifecycle-hooks.md
@@ -0,0 +1,139 @@
+---
+title: Lifecycle Hooks
+order: 600
+---
+
+## What are Lifecycle Hooks?
+
+Hooks let your plugin run code at specific points during the build process. Need to download an ML model before
+compilation? Copy assets to the right platform directory? Run validation? Hooks handle these scenarios.
+
+
+
+## Available Hooks
+
+| Hook | When it Runs |
+|------|--------------|
+| `pre_compile` | Before native code compilation |
+| `post_compile` | After compilation, before build |
+| `copy_assets` | When copying assets to native projects (runs after declarative asset copying) |
+| `post_build` | After a successful build |
+
+## Creating Hook Commands
+
+Generate a hook command with the scaffolding tool:
+
+```shell
+php artisan native:plugin:make-hook
+```
+
+This walks you through selecting your plugin and which hooks to create. It generates the command class, updates
+your manifest, and registers the command in your service provider.
+
+## Hook Command Structure
+
+Hook commands extend `NativePluginHookCommand`:
+
+```php
+use Native\Mobile\Plugins\Commands\NativePluginHookCommand;
+
+class CopyAssetsCommand extends NativePluginHookCommand
+{
+ protected $signature = 'nativephp:my-plugin:copy-assets';
+
+ public function handle(): int
+ {
+ if ($this->isAndroid()) {
+ $this->copyToAndroidAssets('models/model.tflite', 'models/model.tflite');
+ }
+
+ if ($this->isIos()) {
+ $this->copyToIosBundle('models/model.mlmodel', 'models/model.mlmodel');
+ }
+
+ return self::SUCCESS;
+ }
+}
+```
+
+## Available Helpers
+
+The base command provides helpers for common tasks:
+
+**Platform Detection:**
+- `$this->platform()` — Returns `'ios'` or `'android'`
+- `$this->isIos()`, `$this->isAndroid()` — Boolean checks
+
+**Paths:**
+- `$this->buildPath()` — Path to the native project being built
+- `$this->pluginPath()` — Path to your plugin package
+- `$this->appId()` — The app's bundle ID (e.g., `com.example.app`)
+
+**File Operations:**
+- `$this->copyToAndroidAssets($src, $dest)` — Copy to Android assets
+- `$this->copyToIosBundle($src, $dest)` — Copy to iOS bundle
+- `$this->downloadIfMissing($url, $dest)` — Download a file if it doesn't exist
+- `$this->unzip($zipPath, $extractTo)` — Extract a zip file
+
+## Declaring Hooks in the Manifest
+
+Add hooks to your `nativephp.json`:
+
+```json
+{
+ "hooks": {
+ "copy_assets": "nativephp:my-plugin:copy-assets",
+ "pre_compile": "nativephp:my-plugin:pre-compile"
+ }
+}
+```
+
+The value is your Artisan command signature.
+
+## Example: Downloading an ML Model
+
+```php
+public function handle(): int
+{
+ $modelPath = $this->pluginPath() . '/resources/models/model.tflite';
+
+ // Download if not cached locally
+ $this->downloadIfMissing(
+ 'https://example.com/models/v2/model.tflite',
+ $modelPath
+ );
+
+ // Copy to the appropriate platform location
+ if ($this->isAndroid()) {
+ $this->copyToAndroidAssets('models/model.tflite', 'models/model.tflite');
+ $this->info('Model copied to Android assets');
+ }
+
+ if ($this->isIos()) {
+ $this->copyToIosBundle('models/model.tflite', 'models/model.tflite');
+ $this->info('Model copied to iOS bundle');
+ }
+
+ return self::SUCCESS;
+}
+```
+
+
+
+## Official Plugins & Dev Kit
+
+Browse ready-made plugins or get the Dev Kit to build your own.
+[Visit the NativePHP Plugin Marketplace →](https://nativephp.com/plugins)
diff --git a/resources/views/docs/mobile/4/plugins/permissions-dependencies.md b/resources/views/docs/mobile/4/plugins/permissions-dependencies.md
new file mode 100644
index 00000000..30f0ccea
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/permissions-dependencies.md
@@ -0,0 +1,258 @@
+---
+title: Permissions & Dependencies
+order: 700
+---
+
+## Platform Configuration
+
+Platform-specific settings are grouped under `android` and `ios` keys in your manifest. This keeps all configuration for
+each platform together:
+
+```json
+{
+ "android": {
+ "permissions": [...],
+ "dependencies": {...},
+ "repositories": [...],
+ "activities": [...],
+ "services": [...]
+ },
+ "ios": {
+ "info_plist": {...},
+ "dependencies": {...}
+ }
+}
+```
+
+## Permissions
+
+### Android Permissions
+
+List Android permissions as strings under `android.permissions`:
+
+```json
+{
+ "android": {
+ "permissions": [
+ "android.permission.CAMERA",
+ "android.permission.RECORD_AUDIO",
+ "android.permission.ACCESS_FINE_LOCATION"
+ ]
+ }
+}
+```
+
+These are added to the app's `AndroidManifest.xml` at build time.
+
+See the Android Manifest.permission reference
+for a complete list of available permissions.
+
+### iOS Info.plist Entries
+
+iOS requires usage descriptions for each permission, plus any API keys or configuration tokens your plugin needs. Provide
+these as key-value pairs under `ios.info_plist`:
+
+```json
+{
+ "ios": {
+ "info_plist": {
+ "NSCameraUsageDescription": "This app uses the camera for scanning",
+ "NSMicrophoneUsageDescription": "This app records audio for transcription",
+ "NSLocationWhenInUseUsageDescription": "This app needs your location",
+ "MBXAccessToken": "${MAPBOX_ACCESS_TOKEN}"
+ }
+ }
+}
+```
+
+These are merged into the app's `Info.plist`. You can include:
+- Permission usage descriptions (`NS*UsageDescription` keys)
+- API tokens and configuration keys
+- Any other Info.plist entries your plugin requires
+
+See the Apple Information Property List reference
+for all available keys.
+
+Use `${ENV_VAR}` placeholders for sensitive values like API tokens.
+
+
+
+## Dependencies
+
+### Android Dependencies
+
+Add Gradle dependencies under `android.dependencies`:
+
+```json
+{
+ "android": {
+ "dependencies": {
+ "implementation": [
+ "com.google.mlkit:face-detection:16.1.5",
+ "org.tensorflow:tensorflow-lite:2.13.0"
+ ]
+ }
+ }
+}
+```
+
+These are added to the app's `build.gradle.kts` during compilation. You can use any Gradle dependency type:
+
+- `implementation` — Standard dependency
+- `api` — Exposed to consumers
+- `compileOnly` — Compile-time only
+- `runtimeOnly` — Runtime only
+
+### iOS Dependencies
+
+#### CocoaPods
+
+For CocoaPods dependencies, use the `pods` array:
+
+```json
+{
+ "ios": {
+ "dependencies": {
+ "pods": [
+ {"name": "GoogleMLKit/FaceDetection", "version": "~> 4.0"},
+ {"name": "TensorFlowLiteSwift", "version": "~> 2.13"}
+ ]
+ }
+ }
+}
+```
+
+Each pod object accepts:
+- `name` — The pod name (required)
+- `version` — Version constraint (optional, e.g., `~> 4.0`, `>= 1.0`)
+
+NativePHP generates a `Podfile` and runs `pod install` during the iOS build process.
+
+#### Swift Packages
+
+For Swift Package Manager dependencies:
+
+```json
+{
+ "ios": {
+ "dependencies": {
+ "swift_packages": [
+ {
+ "url": "https://github.com/example/SomePackage",
+ "version": "1.0.0"
+ }
+ ]
+ }
+ }
+}
+```
+
+
+
+## Custom Repositories
+
+Some dependencies require private or non-standard Maven repositories (like Mapbox). Add them under
+`android.repositories`:
+
+```json
+{
+ "android": {
+ "repositories": [
+ {
+ "url": "https://api.mapbox.com/downloads/v2/releases/maven",
+ "credentials": {
+ "username": "mapbox",
+ "password": "${MAPBOX_DOWNLOADS_TOKEN}"
+ }
+ }
+ ]
+ }
+}
+```
+
+Repository configuration:
+- `url` — The repository URL (required)
+- `credentials` — Optional authentication
+ - `username` — Username or token name
+ - `password` — Password or token (supports `${ENV_VAR}` placeholders)
+
+These are added to the app's `settings.gradle.kts`.
+
+
+
+## Full Example
+
+Here's a complete manifest for an ML plugin that uses Mapbox maps:
+
+```json
+{
+ "name": "vendor/ml-maps-plugin",
+ "namespace": "MLMaps",
+ "android": {
+ "permissions": [
+ "android.permission.CAMERA",
+ "android.permission.ACCESS_FINE_LOCATION"
+ ],
+ "dependencies": {
+ "implementation": [
+ "com.google.mlkit:object-detection:17.0.0",
+ "com.mapbox.maps:android:11.0.0"
+ ]
+ },
+ "repositories": [
+ {
+ "url": "https://api.mapbox.com/downloads/v2/releases/maven",
+ "credentials": {
+ "username": "mapbox",
+ "password": "${MAPBOX_DOWNLOADS_TOKEN}"
+ }
+ }
+ ]
+ },
+ "ios": {
+ "info_plist": {
+ "NSCameraUsageDescription": "Camera is used for real-time object detection",
+ "NSLocationWhenInUseUsageDescription": "Location is used to display your position on the map",
+ "MBXAccessToken": "${MAPBOX_PUBLIC_TOKEN}"
+ },
+ "dependencies": {
+ "pods": [
+ {"name": "MapboxMaps", "version": "~> 11.0"}
+ ]
+ }
+ },
+ "secrets": {
+ "MAPBOX_DOWNLOADS_TOKEN": {
+ "description": "Mapbox SDK download token from mapbox.com/account/access-tokens",
+ "required": true
+ },
+ "MAPBOX_PUBLIC_TOKEN": {
+ "description": "Mapbox public access token for runtime API calls",
+ "required": true
+ }
+ }
+}
+```
+
+## Official Plugins & Dev Kit
+
+Skip the configuration complexity — browse ready-made plugins or get the Dev Kit to build your own.
+[Visit the NativePHP Plugin Marketplace →](https://nativephp.com/plugins)
diff --git a/resources/views/docs/mobile/4/plugins/using-plugins.md b/resources/views/docs/mobile/4/plugins/using-plugins.md
new file mode 100644
index 00000000..abe0c2fc
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/using-plugins.md
@@ -0,0 +1,146 @@
+---
+title: Using Plugins
+order: 200
+---
+
+## Installing a Plugin
+
+Plugins are standard Composer packages. Install them like any other Laravel package:
+
+```shell
+composer require vendor/nativephp-plugin-name
+```
+
+The plugin's PHP service provider will be auto-discovered by Laravel, but the native code won't be included in builds
+until you explicitly register it.
+
+### Installing Premium Plugins
+
+Premium plugins from the [NativePHP Plugin Marketplace](/plugins) are served via a private Composer repository.
+You'll need to configure Composer before you can install them.
+
+**1. Add the NativePHP plugins repository:**
+
+```shell
+composer config repositories.nativephp-plugins composer https://plugins.nativephp.com
+```
+
+**2. Configure your credentials:**
+
+```shell
+composer config http-basic.plugins.nativephp.com your-email@example.com your-license-key
+```
+
+You can find your credentials on your [Purchased Plugins](/dashboard/purchased-plugins) dashboard.
+
+**3. Install the plugin:**
+
+Once configured, install premium plugins just like any other:
+
+```shell
+composer require vendor/nativephp-premium-plugin
+```
+
+## Register the Plugin
+
+For security, plugins must be explicitly registered before their native code is compiled into your app. This prevents
+transitive dependencies from automatically including native code without your consent.
+
+First, ensure you've published the NativeServiceProvider:
+
+```shell
+php artisan vendor:publish --tag=nativephp-plugins-provider
+```
+
+Then register the plugin:
+
+```shell
+php artisan native:plugin:register vendor/nativephp-plugin-name
+```
+
+This adds the plugin to your `app/Providers/NativeServiceProvider.php` file.
+
+## Verify Installation
+
+Check that NativePHP sees your plugin:
+
+```shell
+php artisan native:plugin:list
+```
+
+You'll see the plugin name, version, and what it provides (bridge functions, events, hooks).
+
+## Rebuild Your App
+
+After installing a plugin, rebuild to compile its native code:
+
+```shell
+php artisan native:run
+```
+
+The plugin's Swift and Kotlin code gets compiled into your app automatically.
+
+## Using Plugin Features
+
+Each plugin provides its own facade for interacting with native functionality.
+
+```php
+use Vendor\PluginName\Facades\PluginName;
+
+// Call a native function
+$result = PluginName::doSomething(['option' => 'value']);
+```
+
+## Listening to Plugin Events
+
+Plugins dispatch events to your Livewire components. Use the `#[OnNative]` attribute to listen for them:
+
+```php
+use Native\Mobile\Attributes\OnNative;
+use Vendor\PluginName\Events\SomethingCompleted;
+
+#[OnNative(SomethingCompleted::class)]
+public function handleCompletion($result)
+{
+ // React to the event
+}
+```
+
+
+
+## Permissions
+
+Some plugins require additional permissions. These are declared in the plugin's manifest and automatically merged
+into your app's configuration during build.
+
+If a plugin needs camera access, microphone, or other sensitive permissions, you'll see them listed when you run
+`native:plugin:list`.
+
+## Removing a Plugin
+
+To completely uninstall a plugin:
+
+```shell
+php artisan native:plugin:uninstall vendor/nativephp-plugin-name
+```
+
+This command:
+- Unregisters the plugin from your `NativeServiceProvider`
+- Removes the package via Composer
+- Removes the path repository from `composer.json` (if applicable)
+- Optionally deletes the plugin source directory (for local path repositories)
+
+Use `--force` to skip confirmation prompts, or `--keep-files` to preserve the source directory when uninstalling
+a local plugin.
+
+## Official Plugins & Dev Kit
+
+Find ready-made plugins for common use cases, or get the Dev Kit to build your own.
+[Visit the NativePHP Plugin Marketplace →](https://nativephp.com/plugins)
diff --git a/resources/views/docs/mobile/4/plugins/validation-testing.md b/resources/views/docs/mobile/4/plugins/validation-testing.md
new file mode 100644
index 00000000..6ea09d21
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/validation-testing.md
@@ -0,0 +1,87 @@
+---
+title: Validation & Testing
+order: 800
+---
+
+## Validating Your Plugin
+
+Before building, validate your plugin to catch common issues:
+
+```shell
+php artisan native:plugin:validate
+```
+
+This checks:
+- Manifest syntax and required fields
+- Bridge function declarations match native code
+- Hook commands are registered and exist
+- Declared assets are present
+
+## Common Validation Errors
+
+**"Bridge function not found in native code"**
+
+Your manifest declares a function, but the Swift or Kotlin implementation is missing or named differently. Check
+that class names and function names match exactly.
+
+**"Invalid manifest JSON"**
+
+Your `nativephp.json` has a syntax error. Check for trailing commas, missing quotes, or unclosed brackets.
+
+**"Hook command not registered"**
+
+The manifest references an Artisan command that isn't registered in your service provider. Make sure
+`native:plugin:make-hook` has updated your service provider, or add it manually.
+
+## Testing During Development
+
+### Test PHP Code
+
+Your PHP facades and event handling work like any Laravel code. Write standard PHPUnit tests:
+
+```php
+public function test_plugin_facade_is_accessible()
+{
+ $this->assertInstanceOf(MyPlugin::class, app(MyPlugin::class));
+}
+```
+
+### Test Native Code
+
+Native code can only be tested by running the app. Use this workflow:
+
+1. Install your plugin locally via path repository
+2. Run `php artisan native:run`
+3. Trigger your plugin's functionality in the app
+4. Check the console output for errors
+
+
+
+## Debugging Tips
+
+**Plugin not discovered?**
+- Verify `composer.json` has `"type": "nativephp-plugin"`
+- Run `composer dump-autoload`
+- Check `php artisan native:plugin:list`
+
+**Native function not found at runtime?**
+- Rebuild the app after changing native code
+- Check the manifest's function names match exactly
+- Verify the Kotlin package name is correct
+
+**Events not firing?**
+- Confirm you're dispatching on the main thread
+- Check the event class name matches the manifest
+- Verify the `#[OnNative]` attribute uses the correct class
+
+## Official Plugins & Dev Kit
+
+Skip the debugging — browse ready-made plugins or get the Dev Kit to build your own.
+[Visit the NativePHP Plugin Marketplace →](https://nativephp.com/plugins)
diff --git a/resources/views/docs/mobile/4/plugins/vibe.md b/resources/views/docs/mobile/4/plugins/vibe.md
new file mode 100644
index 00000000..c7400e03
--- /dev/null
+++ b/resources/views/docs/mobile/4/plugins/vibe.md
@@ -0,0 +1,380 @@
+---
+title: Vibe (Websockets)
+order: 250
+---
+
+## Overview
+
+Vibe brings live server events into your NativePHP Mobile app over the **Pusher protocol** — so it works with
+**[Vask](https://vask.dev)**, **[Laravel Reverb](https://reverb.laravel.com)**, or **Pusher** without changing your
+code. Your components subscribe to channels and react to broadcasts, exactly like Laravel Echo does in the browser.
+
+The websocket lives on the native side (Swift/Kotlin, via the official Pusher SDKs — PusherSwift on iOS,
+`pusher-java-client` on Android). PHP is purely a client subscriber: it declares what to subscribe to and handles the
+events that arrive. Mobile apps never broadcast — they only listen.
+
+Events arrive as native events and re-render the component that subscribed to them.
+
+
+
+
+
+```php
+use Nativephp\Vibe\Facades\Vibe;
+```
+
+
+
+
+## Installation
+
+```shell
+composer require nativephp/mobile-vibe
+php artisan native:plugin:register nativephp/mobile-vibe
+```
+
+Then rebuild your app so the plugin's native code is compiled in:
+
+```shell
+php artisan native:run
+```
+
+## Configuration
+
+Vibe reads the standard Laravel `PUSHER_*` variables from your app's `.env`. These are bundled into the app at build
+time.
+
+```dotenv
+PUSHER_APP_KEY=your-app-key
+PUSHER_HOST=wss.vask.dev # the WEBSOCKET host
+PUSHER_PORT=443
+PUSHER_SCHEME=https
+```
+
+
+
+For private and presence channels, also point Vibe at your backend's authorization endpoint:
+
+```dotenv
+VIBE_AUTH_ENDPOINT=https://your-backend.example.com/api/v1/broadcasting/auth
+```
+
+## Subscribing to channels
+
+Subscribe inside a `NativeComponent`'s `mount()` method. The closures you pass run as component methods, so `$this`
+refers to the component and any property you assign triggers a re-render.
+
+Subscriptions are torn down automatically when the component unmounts — leave the screen and you leave the channel (or
+presence room).
+
+### Public channels
+
+`Vibe::channel()` subscribes to a public channel. Chain `->on()` to react to a broadcast. The `$event` argument is a
+plain object of the broadcast payload, so you read fields with `$event->field`.
+
+
+
+
+
+```php
+use Nativephp\Vibe\Facades\Vibe;
+
+class OrderStatus extends NativeComponent
+{
+ public string $status = 'pending';
+
+ public function mount(): void
+ {
+ Vibe::channel('orders')->on('OrderShipped', function ($event) {
+ $this->status = $event->status; // $this is the component
+ });
+ }
+}
+```
+
+
+
+
+The event name must match what your server broadcasts.
+
+
+
+### Authentication for private and presence channels
+
+Private (`private-`) and presence (`presence-`) channels require a signed authorization from **your remote Laravel
+backend** — a `/broadcasting/auth` endpoint guarded by `auth:sanctum`, with channel authorization callbacks. The app
+authenticates to it with a bearer token.
+
+Register a token resolver **once** (for example in `AppServiceProvider::boot()`). It returns the current bearer token
+and is called at subscribe time.
+
+
+
+
+
+```php
+use Nativephp\Vibe\Facades\Vibe;
+use Native\Mobile\Facades\SecureStorage;
+
+// AppServiceProvider::boot()
+Vibe::resolveTokenUsing(fn () => SecureStorage::get('api_token'));
+```
+
+
+
+
+After a re-login or token refresh, push the fresh token to the live connection so subsequent subscriptions authorize
+with it:
+
+
+
+
+
+```php
+Vibe::withToken($freshToken);
+```
+
+
+
+
+
+
+### Private channels
+
+`Vibe::private()` auto-prefixes the channel name with `private-` and authorizes it through your endpoint. Otherwise it
+behaves like a public channel.
+
+
+
+
+
+```php
+use Nativephp\Vibe\Facades\Vibe;
+
+class OrderStatus extends NativeComponent
+{
+ public string $status = 'pending';
+
+ public function mount(): void
+ {
+ Vibe::private('orders.42')->on('OrderShipped', function ($event) {
+ $this->status = $event->status;
+ });
+ }
+}
+```
+
+
+
+
+### Presence channels
+
+`Vibe::presence()` auto-prefixes the name with `presence-` and, on top of events, tracks who is in the room. The auth
+response carries `channel_data` (a member's id and info), which Vibe surfaces through three lifecycle callbacks:
+
+- `->here()` runs once on join with the full roster
+- `->joining()` runs when someone joins
+- `->leaving()` runs when someone leaves
+
+Each member is an array shaped `['id' => ..., 'info' => [...]]`. You can still chain `->on()` to handle broadcasts on the
+same channel.
+
+
+
+
+
+```php
+use Nativephp\Vibe\Facades\Vibe;
+
+class ChatRoom extends NativeComponent
+{
+ public array $online = [];
+ public array $messages = [];
+
+ public function mount(): void
+ {
+ Vibe::presence('room.1')
+ ->here(fn (array $members) => $this->online = $members) // each: ['id' => ..., 'info' => [...]]
+ ->joining(fn (array $member) => $this->online[] = $member)
+ ->leaving(function (array $member) {
+ $this->online = array_values(array_filter(
+ $this->online,
+ fn ($m) => $m['id'] !== $member['id'],
+ ));
+ })
+ ->on('MessageSent', fn ($event) => $this->messages[] = $event->body);
+ }
+}
+```
+
+
+
+
+## Handling events with `#[OnEcho]`
+
+As an alternative to the fluent `->on()` callback, you can annotate a public component method with the `#[OnEcho]`
+attribute. Method parameters are bound by name from the broadcast payload.
+
+
+
+
+
+```php
+use Nativephp\Vibe\Facades\Vibe;
+use Nativephp\Vibe\Attributes\OnEcho;
+
+class OrderStatus extends NativeComponent
+{
+ public string $status = 'pending';
+
+ public function mount(): void
+ {
+ Vibe::channel('orders'); // subscribe; no ->on() needed
+ }
+
+ #[OnEcho('OrderShipped')]
+ public function whenShipped(string $status): void
+ {
+ $this->status = $status;
+ }
+}
+```
+
+
+
+
+## Methods
+
+All methods are called on the `Vibe` facade. `channel()`, `private()`, `presence()`, and `subscribe()` return a
+`PendingSubscription` you can chain.
+
+### `channel()`
+
+Subscribes to a public channel.
+
+**Parameters:**
+- `string $name` - The channel name
+
+**Returns:** `PendingSubscription`
+
+### `private()`
+
+Subscribes to a private channel, auto-prefixing the name with `private-`. Requires authentication.
+
+**Parameters:**
+- `string $name` - The channel name (without the `private-` prefix)
+
+**Returns:** `PendingSubscription`
+
+### `presence()`
+
+Subscribes to a presence channel, auto-prefixing the name with `presence-`. Requires authentication and tracks members.
+
+**Parameters:**
+- `string $name` - The channel name (without the `presence-` prefix)
+
+**Returns:** `PendingSubscription`
+
+### `subscribe()`
+
+Low-level primitive. You pass the full, already-prefixed channel name.
+
+**Parameters:**
+- `string $fullChannelName` - The complete channel name, including any `private-` / `presence-` prefix
+
+**Returns:** `PendingSubscription`
+
+### `resolveTokenUsing()`
+
+Registers the bearer-token resolver used to authorize private and presence subscriptions. Register it once, typically in
+`AppServiceProvider::boot()`. It is called at subscribe time.
+
+**Parameters:**
+- `Closure $resolver` - Returns the current bearer token (e.g. from secure storage)
+
+**Returns:** `void`
+
+### `withToken()`
+
+Pushes a fresh bearer token to the live connection after a re-login or refresh.
+
+**Parameters:**
+- `string $token` - The new bearer token
+
+**Returns:** `void`
+
+## `PendingSubscription`
+
+The chainable object returned by the subscription methods above.
+
+### `on()`
+
+Runs `$callback` on each matching broadcast. The `$event` argument is a plain object of the payload; read fields with
+`$event->field`.
+
+**Parameters:**
+- `string $event` - The broadcast event name (match your server's `broadcastAs()`)
+- `Closure $callback` - Receives `$event`, the payload object
+
+**Returns:** `PendingSubscription`
+
+### `here()`
+
+Presence only. Runs once when you join, with the full member roster.
+
+**Parameters:**
+- `Closure $callback` - Receives `array $members`; each member is `['id' => ..., 'info' => [...]]`
+
+**Returns:** `PendingSubscription`
+
+### `joining()`
+
+Presence only. Runs when a member joins.
+
+**Parameters:**
+- `Closure $callback` - Receives `array $member`, shaped `['id' => ..., 'info' => [...]]`
+
+**Returns:** `PendingSubscription`
+
+### `leaving()`
+
+Presence only. Runs when a member leaves.
+
+**Parameters:**
+- `Closure $callback` - Receives `array $member`, shaped `['id' => ..., 'info' => [...]]`
+
+**Returns:** `PendingSubscription`
+
+## Notes
+
+- **Foreground-only:** Websockets are foreground-only on mobile. The OS suspends the socket when your app goes to the
+ background. For delivery while the app is closed, use push notifications via the
+ [Firebase plugin](./core/firebase).
+- **Liveness, not source of truth:** Treat websocket events as a *liveness* signal, not authoritative state. On
+ reconnect, refetch the canonical data from your backend.
+- **Auto-teardown:** Subscriptions are removed automatically when the component unmounts. Leaving a screen leaves its
+ channels and presence rooms.
+- **Private/presence require a remote backend:** Signing happens on your remote Laravel app at `/broadcasting/auth`
+ (behind `auth:sanctum`) with channel authorization callbacks. The device never holds the app secret.
+
+
diff --git a/resources/views/docs/mobile/4/super-native/_index.md b/resources/views/docs/mobile/4/super-native/_index.md
new file mode 100644
index 00000000..4408d8b9
--- /dev/null
+++ b/resources/views/docs/mobile/4/super-native/_index.md
@@ -0,0 +1,4 @@
+---
+title: SuperNative
+order: 45
+---
diff --git a/resources/views/docs/mobile/4/super-native/introduction.md b/resources/views/docs/mobile/4/super-native/introduction.md
new file mode 100644
index 00000000..2bdbe6ec
--- /dev/null
+++ b/resources/views/docs/mobile/4/super-native/introduction.md
@@ -0,0 +1,106 @@
+---
+title: Introduction
+order: 1
+---
+
+## What is SuperNative?
+
+SuperNative is the headline feature of NativePHP for Mobile v4: it drops the web view entirely in favor of fully
+native UI — SwiftUI on iOS and Jetpack Compose on Android.
+
+Your screens are no longer HTML rendered inside a browser shell. They are real, platform-native views, built and
+updated by your PHP code. Same Laravel app, same Blade templates — genuinely native UI.
+
+SuperNative is **the default** in v4. New apps render native screens from the very first route — no configuration
+required. Prefer to keep building with the web view? [Opting out](#is-the-web-view-still-an-option) is one route
+and one component.
+
+## Try it now
+
+The fastest way to see SuperNative is to run the demo app,
+[`nativephp/super-native`](https://github.com/nativephp/super-native), on a simulator or device.
+
+You'll need a working NativePHP for Mobile [development environment](../getting-started/environment-setup) first
+(Xcode for iOS, Android Studio for Android). Then clone the demo, install it, and run it:
+
+```shell
+git clone https://github.com/nativephp/super-native
+cd super-native
+composer install
+php artisan native:install
+php artisan native:run
+```
+
+`native:run` builds the app and launches it on your connected device or simulator. Explore the source to see how
+the screens are built, then start swapping in your own.
+
+## How it works
+
+SuperNative builds on three ideas working together:
+
+- **Shared memory with PHP** — the native layer and your PHP application share memory directly, so there's no
+ network round-trip, no serialization overhead, and no waiting on a web view bridge. State changes flow between
+ PHP and the native UI almost instantly.
+- **Livewire-like components** — each screen is driven by a PHP component class that holds its state and behavior,
+ just like a Livewire component. User interactions call your methods, your properties update, and the UI
+ re-renders to match.
+- **Blade components for DX** — you define your UI with the same [EDGE component](../edge-components/introduction)
+ syntax you already know. Familiar, expressive Blade templates compile down to native SwiftUI and Compose views.
+
+If you've built anything with Livewire, you already know how to build with SuperNative.
+
+## Why SuperNative?
+
+Two reasons above all:
+
+- **Performance** — native views render and animate at full platform speed. No web view startup cost, no DOM, no
+ JavaScript bridge. Scrolling, transitions and gestures feel exactly the way users expect because they're powered
+ by the same UI frameworks every other native app uses.
+- **Accessibility** — SwiftUI and Jetpack Compose come with the platform's accessibility support built in.
+ Screen readers, dynamic type, contrast settings and assistive controls work with your app out of the box,
+ rather than being approximated through a browser.
+
+## Is the web view still an option?
+
+Yes. SuperNative is the default, but it's entirely **opt-out**: the [web view](../edge-components/web-view) is
+still available and fully supported — as a component. To run your app the classic web-view-first way, register a
+single native route that renders a full-screen web view:
+
+```php
+use App\NativeComponents\WebShell;
+
+Route::native('/', WebShell::class);
+```
+
+@verbatim
+```blade
+
+
+
+```
+@endverbatim
+
+That's it — your whole Laravel app renders in the web view exactly as it did in v3, and you can still sprinkle
+native UI around it or adopt SuperNative screen by screen whenever you're ready.
+
+## What's coming
+
+SuperNative is in beta and moving quickly. On the roadmap:
+
+- [Jump](../the-basics/jump) support
+- A revised kitchen sink app
+- Full plugin support for all first-party plugins
+
+## For Plugin Developers
+
+There are **no breaking changes** in our plugin architecture for v4.
+
+Start updating your plugins now by widening the `nativephp/mobile` constraint in your plugin's `composer.json` to
+allow v4:
+
+```json
+"require": {
+ "nativephp/mobile": "^3.0" // [tl! remove]
+ "nativephp/mobile": "^3.0|^4.0" // [tl! add]
+}
+```
diff --git a/resources/views/docs/mobile/4/testing/_index.md b/resources/views/docs/mobile/4/testing/_index.md
new file mode 100644
index 00000000..fb7078a3
--- /dev/null
+++ b/resources/views/docs/mobile/4/testing/_index.md
@@ -0,0 +1,4 @@
+---
+title: Testing
+order: 70
+---
diff --git a/resources/views/docs/mobile/4/testing/advanced.md b/resources/views/docs/mobile/4/testing/advanced.md
new file mode 100644
index 00000000..10d6a17e
--- /dev/null
+++ b/resources/views/docs/mobile/4/testing/advanced.md
@@ -0,0 +1,151 @@
+---
+title: Advanced
+order: 500
+---
+
+## Overview
+
+Beyond driving and asserting, the suite gives you tools for the finer points: rendering a screen as a specific
+platform, guarding against wasteful re-renders, locking a screen's tree with snapshots, inspecting the raw wire tree,
+and folding everything into Pest's `expect()` syntax.
+
+## Platform variants
+
+`ios:` and `android:` Tailwind variants let a screen adapt per platform. Pass `platform` to render as one or the
+other, activating the matching variants for that frame:
+
+```php
+it('applies android-only translucency on the home quick-nav', function () {
+ Native::visit('/', platform: 'android')
+ ->assertElement('stack', fn (array $n) => ($n['style']['bg_color'] ?? null) === '#33FFFFFF');
+});
+
+it('omits the android translucency on ios', function () {
+ Native::visit('/', platform: 'ios')
+ ->assertMissingElement('stack', fn (array $n) => ($n['style']['bg_color'] ?? null) === '#33FFFFFF');
+});
+```
+
+`assertElement($type, $matcher)` finds an element of a wire type, optionally narrowed by a closure receiving the wire
+node; `assertMissingElement()` is its inverse.
+
+## Render-count guards
+
+Every interaction re-renders. When performance matters, assert on exactly how many frames a screen produced.
+
+- `renderCount()` — frames rendered so far (the initial mount counts as 1).
+- `assertRenderCount($count)` — an exact frame count.
+- `assertRerendered()` — the last interaction produced a new frame.
+- `assertNotRerendered()` — it produced none.
+
+```php
+it('re-renders exactly once per interaction', function () {
+ Native::test(HapticsDemo::class)
+ ->assertRenderCount(1)
+ ->tap('vibrate-card')
+ ->assertRerendered()
+ ->assertRenderCount(2);
+});
+```
+
+An interaction that navigates away publishes its final state rather than a fresh frame, so it doesn't re-render:
+
+```php
+it('skips the re-render when navigating away', function () {
+ Native::visit('/media')
+ ->tap('Scanner')
+ ->assertNotRerendered();
+});
+```
+
+## Wire snapshots
+
+`assertMatchesSnapshot()` captures the current wire tree and compares it against a committed snapshot on later runs.
+Volatile fields — node ids and content hashes — are stripped, and callback ids are replaced with the expression they
+point at, so snapshots stay stable across runs and read cleanly in review (a press handler shows as `@increment`
+rather than an opaque number).
+
+```php
+it('matches the haptics screen wire snapshot', function () {
+ Native::test(HapticsDemo::class)
+ ->assertMatchesSnapshot()
+ ->tap('vibrate-card')
+ ->assertMatchesSnapshot('after-one-buzz');
+});
+```
+
+The first run writes the snapshot to `tests/__snapshots__//.json` and passes. Commit that file — it's
+the baseline. When you intentionally change a screen, rewrite the snapshots by running with `UPDATE_SNAPSHOTS=1`:
+
+```bash
+UPDATE_SNAPSHOTS=1 php artisan test
+```
+
+Pass a name to `assertMatchesSnapshot('after-one-buzz')` when a test takes more than one snapshot; unnamed snapshots
+are numbered in order.
+
+## Inspecting the tree
+
+Sometimes an assertion helper isn't enough and you want the raw data. These return values rather than the harness:
+
+- `tree()` — the most recently published wire tree, as a nested array.
+- `instance()` — the live component instance.
+- `get($property)` — read a public or `#[Computed]` property.
+- `bridge()` — the `FakeBridge` for this test.
+- `navigationIntent()` — the pending navigation intent, if any.
+- `dumpTree()` — dump the current tree while debugging (returns the harness, so it chains).
+
+```php
+it('accumulates continuous scans', function () {
+ $screen = Native::test(ScannerDemo::class)
+ ->call('scanContinuously')
+ ->emitNative(CodeScanned::class, ['data' => 'SKU-1', 'format' => 'ean13'])
+ ->emitNative(CodeScanned::class, ['data' => 'SKU-2', 'format' => 'ean13']);
+
+ expect($screen->get('scans'))->toHaveCount(2)
+ ->and($screen->get('scans')[1]['data'])->toBe('SKU-2');
+});
+```
+
+## Pest expectation sugar
+
+If you prefer Pest's `expect()` style end to end, register the expectation extensions once in `tests/Pest.php`:
+
+```php
+\Native\Mobile\Testing\PestExpectations::register();
+```
+
+That adds `toSee`, `toNotSee`, `toHaveSet`, `toHaveNavigatedTo`, `toHaveElement`, and `toBeOnScreen`, so harness
+assertions compose into `expect()` chains:
+
+```php
+it('composes harness assertions into expect() chains', function () {
+ expect(Native::visit('/dialogs/toast'))
+ ->toSee('Toasts')
+ ->toHaveSet('duration', 'long')
+ ->toHaveElement('button')
+ ->toNotSee('Nonexistent');
+
+ expect(Native::visit('/media')->tap('Scanner'))
+ ->toHaveNavigatedTo('/media/scanner');
+});
+```
+
+## A whole-app smoke test
+
+Because mounting a screen needs no device, you can render every routed screen in one data-driven test — a fast guard
+that nothing throws on the way to its first frame:
+
+```php
+it('mounts and renders every routed screen without a device', function (string $uri) {
+ expect(Native::visit($uri)->tree())->not->toBeEmpty();
+})->with([
+ '/',
+ '/media',
+ '/system',
+ '/system/haptics',
+ '/system/geolocation',
+ '/dialogs/toast',
+ // ...every route your app registers
+]);
+```
diff --git a/resources/views/docs/mobile/4/testing/interactions.md b/resources/views/docs/mobile/4/testing/interactions.md
new file mode 100644
index 00000000..179ec49f
--- /dev/null
+++ b/resources/views/docs/mobile/4/testing/interactions.md
@@ -0,0 +1,175 @@
+---
+title: Interactions
+order: 200
+---
+
+## Overview
+
+Interactions drive a screen the same way a person would — pressing buttons, typing into fields, flipping toggles. Each
+one dispatches a real event through the component's normal event path and then re-renders, so the assertions after it
+see the updated frame.
+
+Every interaction method returns the harness, so they chain fluently.
+
+## Targeting elements
+
+Most interactions take a `target`. There are two ways to point at an element.
+
+### By visible text
+
+`tap()` finds the nearest pressable element whose subtree contains the given text. This is the most natural way to
+press a labelled button or card:
+
+```php
+Native::test(Dashboard::class)->tap('Refresh');
+```
+
+### By `ref`
+
+For anything without unique visible text — an icon-only button, a specific input, a card among many — give the
+element a `ref` and target it by name. A `ref` works in Blade on any element:
+
+@verbatim
+```blade
+
+ Tap to vibrate
+
+
+
+```
+@endverbatim
+
+...and via `->ref()` on element builders:
+
+```php
+Pressable::make()->ref('vibrate-card')->onPress('vibrate');
+```
+
+Then target that name from the test:
+
+```php
+Native::test(HapticsDemo::class)
+ ->tap('vibrate-card')
+ ->assertSet('buzzes', 1);
+```
+
+Refs make a test read cleanly and survive copy changes — the label can move without breaking the test.
+
+## Pressing
+
+- `tap($target)` — press by `ref` or visible text (the friendliest default).
+- `press($target)` — press an element bound to a method, expression, or `ref`.
+- `longPress($target)` — fire a long-press.
+
+```php
+it('opens the menu on long press', function () {
+ Native::test(Gallery::class)
+ ->longPress('photo-1')
+ ->assertSee('Delete');
+});
+```
+
+## Inputs and form controls
+
+Each control fires the same event its native counterpart emits:
+
+- `input($target, $text)` — type into a text field.
+- `submit($target, $text = '')` — submit a field (e.g. the return key).
+- `toggle($target, $value)` — flip a toggle on or off.
+- `check($target, $value = true)` — check or uncheck a checkbox.
+- `slide($target, $value)` — move a slider to a float value.
+- `select($target, $value)` — choose a value in a select.
+- `selectRadio($target, $value)` — pick a radio option.
+- `changeTab($target, $index)` — switch a tab row to an index.
+- `dismissSheet($target)` — dismiss a bottom sheet.
+
+Fields bound with `wire:model` sync their property as you'd expect:
+
+```php
+it('fills and submits the toast form', function () {
+ Native::visit('/dialogs/toast')
+ ->input('message-input', 'Hello from CI')
+ ->assertSet('message', 'Hello from CI')
+ ->press('show-toast')
+ ->assertNativeCalled('Dialog.Toast', fn (array $p) => $p['message'] === 'Hello from CI');
+});
+```
+
+## Setting properties and calling methods
+
+Two lower-level tools reach the component directly.
+
+`set($property, $value)` sets a public property through the same binding path a native input uses — so it fires any
+`updatedFoo()` hook — then re-renders:
+
+```php
+Native::test(SecureStorageDemo::class)
+ ->set('key', 'api-key')
+ ->set('value', 's3cret')
+ ->call('store');
+```
+
+`call($method, ...$args)` invokes a component method directly, then re-renders. Reach for it to trigger logic that
+isn't wired to a visible control, or to set up state:
+
+```php
+Native::test(Home::class)
+ ->call('doubleTapped')
+ ->assertSet('gesture', 'Double-tapped!');
+```
+
+## The generic primitive: `fireEvent()`
+
+All the input sugar above is built on `fireEvent($target, $type, $fields = [])`, which dispatches a wire event at the
+callback registered for a target. You rarely need it directly, but it's there when you're driving a custom element
+that emits an event the sugar doesn't cover:
+
+```php
+Native::test(CustomControl::class)
+ ->fireEvent('custom-widget', TestableComponent::EVENT_SLIDER_CHANGE, ['value' => 0.5]);
+```
+
+## Polling
+
+Screens that refresh with `#[Poll]` don't wait for a timer under test — you fire them on demand.
+
+- `firePolls()` fires every `#[Poll]` method immediately and re-renders, as if all timers came due at once.
+- `firePoll($method)` fires a single `#[Poll]` method by name.
+
+```php
+it('polls the native recording status on demand', function () {
+ Native::fakeBridge()->respondTo('Microphone.GetStatus', ['status' => 'recording']);
+
+ Native::test(MicrophoneDemo::class)
+ ->call('startRecording')
+ ->firePoll('pollStatus')
+ ->assertSet('status', 'Native status: recording')
+ ->assertSee('Native status: recording');
+});
+```
+
+## Search
+
+For screens that override `onSearchQuery()`, run a query through the same handler the runloop uses. `search($query)`
+drives the handler and stages the results for the next frame; `searchResults()` returns those staged results:
+
+```php
+it('filters the contact list', function () {
+ $screen = Native::test(ContactSearch::class)->search('ada');
+
+ expect($screen->searchResults())->toHaveCount(1);
+ $screen->assertSee('Ada Lovelace');
+});
+```
+
+## Back button
+
+`pressBack()` simulates the system back gesture or hardware back button:
+
+```php
+Native::test(EditPost::class)
+ ->pressBack()
+ ->assertWentBack();
+```
+
+See [Navigation & Flows](navigation) for asserting on where back takes you.
diff --git a/resources/views/docs/mobile/4/testing/introduction.md b/resources/views/docs/mobile/4/testing/introduction.md
new file mode 100644
index 00000000..60db584f
--- /dev/null
+++ b/resources/views/docs/mobile/4/testing/introduction.md
@@ -0,0 +1,92 @@
+---
+title: Introduction
+order: 100
+---
+
+## Overview
+
+NativePHP ships a testing suite for your [SuperNative](../super-native/introduction) screens. It mounts a
+`NativeComponent`, renders its Blade, dispatches events, and re-renders — the whole component lifecycle — entirely
+in-process.
+
+There's no device, no simulator, and no running app. Tests are plain [Pest](https://pestphp.com) (or PHPUnit) and run
+anywhere PHP does, including CI. A `FakeBridge` stands in for the native runtime and captures every element tree the
+component publishes and every native call it makes, so your assertions target the published wire tree and the
+component's state.
+
+Rendering fidelity — how SwiftUI or Compose actually paints those elements on screen — is out of scope by design. The
+suite asserts on *what your component published*, exactly the layer you control from PHP.
+
+## The `Native` entry point
+
+Every test starts from the `Native` facade. It has three entry points:
+
+```php
+use Native\Mobile\Testing\Native;
+```
+
+- `Native::test(Counter::class)` mounts a component class directly.
+- `Native::visit('/profile/5')` mounts the component registered for a native route, resolving the route's params and
+ layout exactly as navigation would on device.
+- `Native::fakeBridge()` returns the `FakeBridge` so you can script native responses before the component mounts.
+
+Both `test()` and `visit()` return a `TestableComponent` — the fluent harness you chain assertions and interactions
+onto.
+
+## Your first test
+
+Say you have a `Counter` screen with a public `$count` property and an `increment` button:
+
+```php
+use App\NativeComponents\Counter;
+use Native\Mobile\Testing\Native;
+
+it('increments the count', function () {
+ Native::test(Counter::class)
+ ->assertSee('Count: 0')
+ ->tap('Increment')
+ ->assertSet('count', 1)
+ ->assertSee('Count: 1');
+});
+```
+
+`assertSee()` looks for text anywhere in the rendered tree. `tap()` presses the nearest pressable element whose
+subtree shows the given text (or one carrying a matching `ref` — more on that in [Interactions](interactions)).
+`assertSet()` reads a public or `#[Computed]` property and compares it. Every interaction re-renders the component, so
+the assertions that follow see the fresh frame.
+
+## Passing params and data
+
+`test()` accepts route params and navigation data, mirroring the values a screen receives on device:
+
+```php
+it('shows the requested profile', function () {
+ Native::test(Profile::class, params: ['id' => 5], data: ['from' => 'search'])
+ ->assertSee('Profile #5');
+});
+```
+
+When you use `visit()`, params come from the route URI automatically:
+
+```php
+Native::visit('/profile/5')->assertSee('Profile #5');
+```
+
+## Generating a test
+
+Scaffold a test for any screen with the `native:make-test` command:
+
+```bash
+php artisan native:make-test Counter
+```
+
+This writes `tests/Feature/CounterTest.php` with a couple of starter `it()` blocks ready to fill in. A bare name
+resolves to `App\NativeComponents\Counter`; you can also pass a nested name like `Settings/Profile` or a fully
+qualified class name. Pass `--force` to overwrite an existing test.
+
+## What's next
+
+- [Interactions](interactions) — tapping, typing, toggling, and every other way to drive a screen.
+- [Native Events & the Bridge](native-events) — delivering device events and asserting on native calls.
+- [Navigation & Flows](navigation) — walking between screens and asserting on chrome.
+- [Advanced](advanced) — platform variants, render-count guards, wire snapshots, and Pest sugar.
diff --git a/resources/views/docs/mobile/4/testing/native-events.md b/resources/views/docs/mobile/4/testing/native-events.md
new file mode 100644
index 00000000..22e79eaa
--- /dev/null
+++ b/resources/views/docs/mobile/4/testing/native-events.md
@@ -0,0 +1,152 @@
+---
+title: Native Events & the Bridge
+order: 300
+---
+
+## Overview
+
+Native APIs are asynchronous: your screen calls the bridge, the device does its work, and later an event comes back
+carrying the result. The testing suite lets you drive both halves of that round trip deterministically — you deliver
+the event yourself, and you assert on the calls the component made.
+
+The `FakeBridge` records every native call in the order it happened, and can answer synchronous calls with scripted
+responses. You reach its assertions through the harness, or the bridge itself through `bridge()`.
+
+## Delivering a native event
+
+`emitNative($event, $payload = [])` delivers a native event — what the device sends when a bridge API completes or a
+plugin pushes an event. It fires `#[On]` listeners, fluent `->on()` closures, and any pending `then()` / `catch()`
+callbacks the component chained onto its bridge call, then re-renders.
+
+Pass the event class and its payload:
+
+```php
+use Native\Mobile\Events\Motion\ShakeDetected;
+
+it('counts device shakes delivered as native events', function () {
+ Native::visit('/')
+ ->emitNative(ShakeDetected::class)
+ ->emitNative(ShakeDetected::class)
+ ->assertSet('shakes', 2)
+ ->assertSee('Shaken 2×!');
+});
+```
+
+## Asserting on native calls
+
+After an interaction, assert on what the component sent to the bridge:
+
+- `assertNativeCalled($method, $paramsFilter = null)` — the method was called; an optional closure receives the
+ decoded params of each call to narrow the match.
+- `assertNativeNotCalled($method)` — the method was never called.
+- `assertNativeCalledTimes($method, $times)` — it was called exactly that many times.
+- `assertNativeCallOrder($methods)` — the given methods appear in this relative order (other calls may interleave).
+
+```php
+use Native\Mobile\Events\Alert\ButtonPressed;
+
+it('alerts then toasts, in that order', function () {
+ Native::test(AlertDemo::class)
+ ->call('confirmAlert')
+ ->emitNative(ButtonPressed::class, ['index' => 1, 'label' => 'Delete'])
+ ->assertNativeCalledTimes('Dialog.Alert', 1)
+ ->assertNativeCallOrder(['Dialog.Alert', 'Dialog.Toast']);
+});
+```
+
+The params filter lets you assert on exactly what was sent:
+
+```php
+->assertNativeCalled('SecureStorage.Set', fn (array $p) => $p['key'] === 'api-key');
+```
+
+## Asserting a pending callback
+
+When a screen chains a fluent handler onto a bridge call — `->locationReceived(...)`, `->photoTaken(...)` — a callback
+is registered and waits for that native event. You can assert on the wait itself:
+
+- `assertAwaitingNativeEvent($eventClass)` — a callback is registered and waiting for this event.
+- `assertNotAwaitingNativeEvent($eventClass)` — no callback awaits it (e.g. before the call, or after the one-shot
+ fired).
+
+This is ideal for a full geolocation round trip: press, confirm the wait, deliver the event, confirm the wait is
+consumed and the state landed.
+
+```php
+use Native\Mobile\Events\Geolocation\LocationReceived;
+
+it('tracks the pending location callback through its lifecycle', function () {
+ Native::test(GeolocationDemo::class)
+ ->assertNotAwaitingNativeEvent(LocationReceived::class)
+ ->press('get-position')
+ ->assertSet('locating', true)
+ ->assertAwaitingNativeEvent(LocationReceived::class)
+ ->emitNative(LocationReceived::class, [
+ 'success' => true,
+ 'latitude' => 48.85,
+ 'longitude' => 2.35,
+ ])
+ ->assertNotAwaitingNativeEvent(LocationReceived::class)
+ ->assertSet('latitude', 48.85);
+});
+```
+
+## Scripting synchronous responses
+
+Some bridge calls return a value straight away rather than firing an event later. Script those with `respondTo()` on
+the `FakeBridge`. The response shape is what the native side would return — an array becomes JSON on the way back to
+the component:
+
+```php
+it('turns on when the bridge confirms the toggle', function () {
+ Native::fakeBridge()->respondTo('Device.ToggleFlashlight', [
+ 'success' => true,
+ 'state' => true,
+ ]);
+
+ Native::test(FlashlightDemo::class)
+ ->call('toggle')
+ ->assertSet('on', true);
+});
+```
+
+`respondTo()` also accepts a closure, which receives the decoded call params and returns the response — handy when the
+answer depends on the request.
+
+## Scripting before mount
+
+Screens often read the bridge during `mount()` to hydrate themselves. Because those calls happen before you get a
+harness back, script them up front with `Native::fakeBridge()` — it enables the bridge and returns it so you can
+`respondTo()` before mounting:
+
+```php
+it('hydrates device info at mount from scripted bridge responses', function () {
+ Native::fakeBridge()
+ ->respondTo('Device.GetId', ['id' => 'test-device-123'])
+ ->respondTo('Device.GetInfo', ['info' => json_encode(['model' => 'iPhone 17 Pro', 'os' => 'iOS 26'])])
+ ->respondTo('Device.GetBatteryInfo', ['info' => json_encode(['level' => 0.8, 'state' => 'charging'])]);
+
+ Native::test(DeviceDemo::class)
+ ->assertSet('deviceId', 'test-device-123')
+ ->assertSet('info', ['model' => 'iPhone 17 Pro', 'os' => 'iOS 26'])
+ ->assertSet('battery', ['level' => 0.8, 'state' => 'charging']);
+});
+```
+
+
+
+## Reaching the bridge directly
+
+For assertions the harness doesn't wrap, `bridge()` returns the `FakeBridge` itself. It exposes `assertNothingCalled()`,
+the recorded `calls` and `publishes`, `callsTo($method)`, and `lastPublish()`:
+
+```php
+$screen = Native::test(HapticsDemo::class)->tap('vibrate-card');
+
+expect($screen->bridge()->callsTo('Device.Vibrate'))->toHaveCount(1);
+```
diff --git a/resources/views/docs/mobile/4/testing/navigation.md b/resources/views/docs/mobile/4/testing/navigation.md
new file mode 100644
index 00000000..08e22e5c
--- /dev/null
+++ b/resources/views/docs/mobile/4/testing/navigation.md
@@ -0,0 +1,142 @@
+---
+title: Navigation & Flows
+order: 400
+---
+
+## Overview
+
+Screens navigate: a tap pushes a detail screen, a save replaces the current one, a back gesture pops. The testing
+suite lets you assert on those intentions, and — when you want to keep going — follow them onto the next screen and
+walk a whole flow, with each screen's state preserved exactly as it is on device.
+
+## Asserting on navigation
+
+After an interaction, assert on where the screen intends to go:
+
+- `assertNavigatedTo($uri)` — a forward push to `$uri`.
+- `assertReplacedWith($uri)` — the current screen was replaced with `$uri`.
+- `assertWentBack()` — a back navigation.
+- `assertExitedToWeb($uri)` — navigation left the native stack for a web URL.
+- `assertNoNavigation()` — the interaction stayed on this screen.
+
+```php
+it('navigates to a demo when a featured card is tapped', function () {
+ Native::visit('/')
+ ->tap('Scanner')
+ ->assertNavigatedTo('/media/scanner');
+});
+```
+
+## Following a flow
+
+`follow()` (alias `followNavigation()`) resolves the pending navigation through the route registry and returns a
+**new** harness mounted on the destination, carrying the intent's data, params, and layout.
+
+It mirrors the router precisely. On a forward navigate the current screen stays alive underneath — `goBack()` returns
+to it with its state intact and `onResume()` fired. On a replace it unmounts and drops out of the stack.
+
+```php
+use App\NativeComponents\GeolocationDemo;
+
+it('follows navigation from home onto the geolocation demo', function () {
+ Native::visit('/')
+ ->tap('Location')
+ ->assertNavigatedTo('/system/geolocation')
+ ->follow()
+ ->assertScreen(GeolocationDemo::class)
+ ->assertSee('Geolocation');
+});
+```
+
+`assertScreen($componentClass)` confirms which component the harness is currently driving — useful after a `follow()`
+or `goBack()` to pin down exactly where you are.
+
+## Going back with state preserved
+
+`goBack()` pops the current screen and returns the harness for the one below it — the live component, resumed exactly
+as the router resumes it. State the previous screen held before you navigated away is still there:
+
+```php
+use App\NativeComponents\GeolocationDemo;
+use App\NativeComponents\Home;
+
+it('returns to home with state preserved after visiting a demo', function () {
+ $home = Native::visit('/')
+ ->call('doubleTapped') // mutate state before navigating away
+ ->tap('Location');
+
+ $home->follow()
+ ->assertScreen(GeolocationDemo::class)
+ ->assertNavTitle('Geolocation')
+ ->goBack()
+ ->assertScreen(Home::class)
+ ->assertSet('gesture', 'Double-tapped!') // survived the push/pop
+ ->assertSee('Double-tapped!');
+});
+```
+
+## Chrome assertions
+
+When a screen returns `view()` with a layout, the suite renders that layout's chrome — the navigation bar and tab bar
+— just as it appears on device. Assert on it directly.
+
+- `assertNavTitle($title)` — the navigation bar shows this title.
+- `assertHasTabBar()` — the screen renders native tab chrome.
+- `assertTabBarVisible()` / `assertTabBarHidden()` — the tab bar's visibility on this screen.
+- `assertHasTab($label)` — a tab with this label exists.
+- `assertTabActive($label)` — the tab with this label is the active one.
+
+`visit()` resolves the route's layout automatically, so chrome is populated exactly as navigation would produce it:
+
+```php
+it('renders the hub inside tab chrome with the right tab active', function () {
+ Native::visit('/media')
+ ->assertHasTabBar()
+ ->assertNavTitle('Media')
+ ->assertHasTab('Home')
+ ->assertHasTab('Media')
+ ->assertHasTab('System')
+ ->assertTabActive('Media')
+ ->assertTabBarVisible();
+});
+```
+
+Detail screens using stack chrome carry a title but no tab bar:
+
+```php
+it('renders detail screens in stack chrome without a tab bar', function () {
+ Native::visit('/system/haptics')
+ ->assertNavTitle('Haptics')
+ ->assertMissingElement('native_root_tabs');
+});
+```
+
+
+
+## Walking a full flow
+
+Put it together — follow forward through the stack, assert the chrome tracks it, and pop back:
+
+```php
+use App\NativeComponents\MediaHub;
+use App\NativeComponents\ScannerDemo;
+
+it('walks hub → demo → back with chrome tracking the stack', function () {
+ $hub = Native::visit('/media')->assertTabActive('Media');
+
+ $scanner = $hub->tap('Scanner')
+ ->assertNavigatedTo('/media/scanner')
+ ->follow()
+ ->assertScreen(ScannerDemo::class)
+ ->assertNavTitle('Scanner');
+
+ $scanner->goBack()
+ ->assertScreen(MediaHub::class)
+ ->assertTabActive('Media');
+});
+```
diff --git a/resources/views/docs/mobile/4/the-basics/_index.md b/resources/views/docs/mobile/4/the-basics/_index.md
new file mode 100644
index 00000000..62b29f06
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/_index.md
@@ -0,0 +1,4 @@
+---
+title: The Basics
+order: 20
+---
diff --git a/resources/views/docs/mobile/4/the-basics/app-icon.md b/resources/views/docs/mobile/4/the-basics/app-icon.md
new file mode 100644
index 00000000..5b1bfdbb
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/app-icon.md
@@ -0,0 +1,25 @@
+---
+title: App Icons
+order: 300
+---
+
+NativePHP makes it easy to apply a custom app icon to your iOS and Android apps.
+
+## Supply your icon
+
+Place a single high-resolution icon file at: `public/icon.png`.
+
+### Requirements
+- Format: PNG
+- Size: 1024 × 1024 pixels
+- Background: Must not contain any transparencies.
+- GD PHP extension must be enabled, ensure it has enough memory (~2GB should be enough)
+
+This image will be automatically resized for all Android densities and used as the base iOS app icon.
+You must have the GD extension installed in your development machine's PHP environment for this to work.
+
+
diff --git a/resources/views/docs/mobile/4/the-basics/assets.md b/resources/views/docs/mobile/4/the-basics/assets.md
new file mode 100644
index 00000000..40eb46fe
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/assets.md
@@ -0,0 +1,53 @@
+---
+title: Assets
+order: 500
+---
+
+## Compiling CSS and JavaScript
+
+If you are using React, Vue or another JavaScript library, or Tailwind CSS, tools that requires your frontend to be
+built by build tooling like Vite, you will need to run your build process _before_ compiling the native application.
+
+For example, if you're using Vite with NPM to build a React application that is using Tailwind, to ensure that your
+latest styles and JavaScript are included, always run `npm run build` before running `php artisan native:run`.
+
+## Other files
+
+NativePHP will include all files from the root of your Laravel application. So you can store any files that you wish to
+make available to your application wherever makes the most sense for you.
+
+
+
+## Public files
+
+If your application receives files from the user — either generated by your application or imported from elsewhere,
+such as their photo gallery — you may wish to render these to the web view so they can be played or displayed as part
+of your app.
+
+For this to work, they must be in the `public` directory. But you may also wish to persist these files across app
+updates. For this reason, they are stored outside of the `public` directory in a persistent storage location and this
+folder is symlinked to `public/storage`.
+
+You can then access these files using the `mobile_public` disk:
+
+```php
+Storage::disk('mobile_public')->url('user_content.jpg');
+```
+
+
diff --git a/resources/views/docs/mobile/4/the-basics/events.md b/resources/views/docs/mobile/4/the-basics/events.md
new file mode 100644
index 00000000..de179ef1
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/events.md
@@ -0,0 +1,215 @@
+---
+title: Events
+order: 200
+---
+
+## Overview
+
+Many native mobile operations take time to complete and await user interaction. PHP isn't really set up to handle this
+sort of asynchronous behaviour; it is built to do its work, send a response and move on as quickly as possible.
+
+NativePHP for Mobile smooths over this disparity between the different paradigms using a simple event system that
+handles completion of asynchronous methods using a webhook-/websocket-style approach to notify your Laravel app.
+
+## Understanding Async vs Sync
+
+Not all actions are async. Some methods run immediately, and in some cases return a result straight away.
+
+Here are a few of the **synchronous** APIs:
+
+```php
+Haptics::vibrate();
+System::flashlight();
+Dialog::toast('Hello!');
+```
+Asynchronous actions trigger operations that may complete later. These return immediately, usually with a `bool` or
+`void`, allowing PHP's execution to finish. In many of these cases, the user interacts directly with a native component.
+
+When the user has completed their task and the native UI is dismissed, the app will emit an event that represents the
+outcome.
+
+The _type_ (the class name) of the event and its properties all help you to choose the appropriate action to take in
+response to the outcome.
+
+```php
+// These trigger operations and fire events when complete
+Camera::getPhoto(); // → PhotoTaken event
+Biometrics::prompt(); // → Completed event
+PushNotifications::enroll(); // → TokenGenerated event
+```
+
+## Basic Event Structure
+
+All events are standard [Laravel Event classes](https://laravel.com/docs/12.x/events#defining-events). The public
+properties of the events contain the pertinent data coming from the native app side.
+
+## Custom Events
+
+Almost every function that emits events can be customized to emit events that you define. This is a great way to ensure
+only the relevant listeners are executed when these events are fired.
+
+Events are simple PHP classes that receive some parameters. You can extend existing events for convenience.
+
+Let's see a complete example...
+
+### Define your custom event class
+
+```php
+namespace App\Events;
+
+use Native\Mobile\Events\Alert\ButtonPressed;
+
+class MyButtonPressedEvent extends ButtonPressed
+{}
+```
+
+### Pass this class to an async function
+
+```php
+use App\Events\MyButtonPressedEvent;
+
+Dialog::alert('Warning!', 'You are about to delete everything! Are you sure?', [
+ 'Cancel',
+ 'Do it!'
+ ])
+ ->event(MyButtonPressedEvent::class)
+```
+
+### Handle the event
+
+Here's an example handling a custom event class inside a Livewire component.
+
+```php
+use App\Events\MyButtonPressed;
+use Native\Mobile\Attributes\OnNative;
+
+#[OnNative(MyButtonPressed::class)]
+public function buttonPressed()
+{
+ // Do stuff
+}
+```
+
+## Event Handling
+
+All asynchronous methods follow the same pattern:
+
+1. **Call the method** to trigger the operation.
+2. **Listen for the appropriate events** to handle the result.
+3. **Update your UI** based on the outcome.
+
+All events get sent directly to JavaScript in the web view _and_ to your PHP application via a special route. This
+allows you to listen for these events in the context that best suits your application.
+
+### On the frontend
+
+Events are 'broadcast' to the frontend of your application via the web view through a custom `Native` helper. You can
+easily listen for these events through JavaScript in a few ways:
+
+- The globally available `Native.on()` helper
+- Directly importing the `On` function
+- The `#[OnNative()]` PHP attribute Livewire extension
+
+
+
+#### The `Native.on()` helper
+
+Register the event listener directly in JavaScript:
+
+```blade
+@@use(Native\Mobile\Events\Alert\ButtonPressed)
+
+
+```
+
+This approach is useful if you're not using any particular frontend JavaScript framework.
+
+#### The `On` import
+
+
+
+If you're using a SPA framework like Vue or React, it's more convenient to import the `On` function directly to
+register your event listeners. Here's an example using the amazing Vue:
+
+```js
+import { On, Events } from '#nativephp';
+import { onMounted } from 'vue';
+
+const handleButtonPressed = (payload: any) => {};
+
+onMounted(() => {
+ On(Events.Alert.ButtonPressed, handleButtonPressed);
+});
+```
+
+Note how we're also using the `Events` object above to simplify our use of built-in event names. For custom event
+classes, you will need to reference these by their full name:
+
+```js
+On('App\\Events\\MyButtonPressedEvent', handleButtonPressed);
+```
+
+In SPA land, don't forget to de-register your event handlers using the `Off` function too:
+
+```js
+import { Off, Events } from '#nativephp';
+import { onUnmounted } from 'vue';
+
+onUnmounted(() => {
+ Off(Events.Alert.ButtonPressed, handleButtonPressed);
+});
+```
+
+#### The `#[OnNative()]` attribute
+
+Livewire makes listening to 'broadcast' events simple. Just add the `#[OnNative()]` attribute attached to the Livewire
+component method you want to use as its handler:
+
+```php
+use Native\Mobile\Attributes\OnNative;
+use Native\Mobile\Events\Camera\PhotoTaken;
+
+#[OnNative(PhotoTaken::class)]
+public function handlePhoto(string $path)
+{
+ // Handle captured photo
+}
+```
+
+### On the backend
+
+You can also listen for these events on the PHP side as they are simultaneously passed to your Laravel application.
+
+Simply [add a listener](https://laravel.com/docs/12.x/events#registering-events-and-listeners) as you normally would:
+
+```php
+use App\Services\APIService;
+use Native\Mobile\Events\Camera\PhotoTaken;
+
+class UpdateAvatar
+{
+ public function __construct(private APIService $api) {}
+
+ public function handle(PhotoTaken $event): void
+ {
+ $imageData = base64_encode(
+ file_get_contents($event->path)
+ );
+
+ $this->api->updateAvatar($imageData);
+ }
+}
+```
diff --git a/resources/views/docs/mobile/4/the-basics/jump.md b/resources/views/docs/mobile/4/the-basics/jump.md
new file mode 100644
index 00000000..103ccad2
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/jump.md
@@ -0,0 +1,176 @@
+---
+title: Jump
+order: 55
+---
+
+[Jump](https://bifrost.nativephp.com/jump) is a companion mobile app that lets you preview your NativePHP app on a real
+device without ever opening Xcode or Android Studio. You write code on your machine; Jump renders it on your phone.
+
+Combined with the `native:jump` Artisan command, it gives you a fast, hot-reloading development loop that works on both
+iOS and Android with a single command.
+
+## How it works
+
+When you run `native:jump`, NativePHP starts three things on your machine:
+
+- **A PHP dev server** that serves a QR code page and proxies HTTP requests through to your Laravel app
+- **An `artisan serve` process** running your Laravel application (unless you opt out — see [Bring your own server](#bring-your-own-server))
+- **A WebSocket bridge** that the Jump app on your phone connects to, used to invoke native APIs and to deliver Vite
+ HMR updates
+
+When you scan the QR code with the Jump app, your phone connects back to your machine over Wi-Fi and starts rendering
+your Laravel app inside Jump's WebView. Calls to NativePHP APIs (e.g. `Camera::open()`) are sent over the bridge to the
+device, executed natively, and the result is sent back to PHP — exactly as it would behave in a packaged build.
+
+## Quick start
+
+1. Install Jump on your device from the
+ [App Store](https://apps.apple.com/us/app/bifrost-jump/id6757173334) or
+ [Google Play](https://play.google.com/store/apps/details?id=com.bifrosttech.jump). You'll need **Jump v2 or
+ later** to work with NativePHP Mobile v3.3.
+2. Make sure your phone and your computer are on the same Wi-Fi network.
+3. From your Laravel project, run:
+
+```shell
+php artisan native:jump
+```
+
+4. Scan the QR code shown in your terminal with the Jump app.
+
+Your app will load on your device. Any changes you make to Blade views, Livewire components or your CSS/JS bundle will
+reload automatically.
+
+
+
+## Network requirements
+
+Jump connects from your phone to your computer over your local network. For this to work:
+
+- Both devices need to be on the **same Wi-Fi network** (or one device tethered to the other)
+- Your firewall must allow inbound connections to the HTTP, WebSocket, bridge and Vite proxy ports (defaults: `3000`,
+ `3001`, `3002`, `3003`)
+- If you're on a public/guest network that isolates clients from each other, Jump won't be able to connect — switch to a
+ private network or use a personal hotspot
+
+NativePHP also advertises the dev server via mDNS so the Jump app can discover it automatically. If your network blocks
+multicast traffic, you can disable this with `--no-mdns` and scan the QR code instead.
+
+## Hot reload
+
+Jump uses Vite's HMR pipeline. When you run `npm run dev` alongside `native:jump`, the dev server's HMR WebSocket is
+proxied through Jump's own port (`3003` by default) so your phone can subscribe to updates without you having to
+reconfigure `vite.config.js` for network access.
+
+You don't need to do anything special — just start Vite the way you normally would and Jump will pick it up.
+
+## The Bridge
+
+In a normal mobile build, calls like `Camera::open()` execute in-process on the device. With Jump, PHP runs on your
+machine but the native APIs still live on the phone. NativePHP makes this transparent: when Jump is active, the
+`nativephp_call()` function dials the bridge port over TCP, the WebSocket server relays the call to the connected
+device, and the device replies with the result.
+
+This means you can develop and test the vast majority of native functionality — sensors, dialogs, camera, biometrics,
+file pickers and more — without ever building the app.
+
+
+
+### Bridge logs
+
+The bridge writes to `storage/logs/jump-bridge.log`. You can tail it to watch native calls in real time:
+
+```shell
+tail -f storage/logs/jump-bridge.log
+```
+
+The path is also printed under **Bridge log** in the `native:jump` terminal output.
+
+## Configuration
+
+The dev server is configured under the `server` key in your `config/nativephp.php` file:
+
+```php
+'server' => [
+ 'http_port' => env('NATIVEPHP_HTTP_PORT', 3000),
+ 'ws_port' => env('NATIVEPHP_WS_PORT', 8081),
+ 'service_name' => env('NATIVEPHP_SERVICE_NAME', 'NativePHP Server'),
+ 'open_browser' => env('NATIVEPHP_OPEN_BROWSER', true),
+],
+```
+
+See [Configuration](/docs/mobile/3/getting-started/configuration#development-server) for the full reference.
+
+Per-run options (ports, host, IP, mDNS) can all be overridden on the command line — see the
+[`native:jump` command reference](/docs/mobile/3/getting-started/commands#nativejump) for the full list of flags.
+
+### Multiple network interfaces
+
+If your machine has more than one IP address (e.g. Wi-Fi and Ethernet, or a VPN is active), `native:jump` will prompt
+you to choose which one to advertise in the QR code. Pick the IP that your phone can actually reach — usually the Wi-Fi
+one.
+
+You can also pre-select it with `--ip=`:
+
+```shell
+php artisan native:jump --ip=192.168.1.42
+```
+
+### Bring your own server
+
+By default `native:jump` starts an `artisan serve` for you. If you're already running your own server (for example
+[Herd](https://herd.laravel.com), Valet, Sail, or `php artisan serve` in another tab), pass `--no-serve` and tell Jump
+which port your server is on:
+
+```shell
+php artisan native:jump --no-serve --laravel-port=8000
+```
+
+You'll also need to export the bridge port when starting your own server, so that `nativephp_call()` inside Laravel
+dials the right TCP port. `native:jump` prints the exact command to copy:
+
+```shell
+JUMP_BRIDGE_PORT=3002 php artisan serve --port=8000
+```
+
+### Custom ports
+
+If something else on your machine is already using a port, NativePHP will automatically find the next available one and
+print a message. If you want to pin the ports yourself, all of them can be set explicitly:
+
+```shell
+php artisan native:jump \
+ --http-port=3000 \
+ --ws-port=3001 \
+ --bridge-port=3002 \
+ --vite-proxy-port=3003 \
+ --laravel-port=8000
+```
+
+## Stopping the server
+
+Press `Ctrl+C` in the terminal where `native:jump` is running. NativePHP will shut down the bridge, the PHP server and
+the managed `artisan serve` process cleanly.
+
+## When to use Jump vs `native:run`
+
+| Use Jump when... | Use `native:run` when... |
+|------------------|--------------------------|
+| You want the fastest possible iteration loop | You need to test a packaged build |
+| You don't have Xcode or Android Studio installed | You're testing release/bundle builds for store submission |
+| You're _not_ working on a Mac or don't have an Apple Developer account but want to test on a real iOS device | You're testing native code in plugins you're authoring |
+| You want to share a preview with a teammate over the same network | You need to test app start-up behaviour or bundled assets |
+
+For most day-to-day development, Jump is the fastest way to see changes on a real device. Once you're ready to ship,
+use [`native:package`](/docs/mobile/3/getting-started/commands#nativepackage) to build a signed binary.
diff --git a/resources/views/docs/mobile/4/the-basics/layouts.md b/resources/views/docs/mobile/4/the-basics/layouts.md
new file mode 100644
index 00000000..5f080ca7
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/layouts.md
@@ -0,0 +1,210 @@
+---
+title: Layouts
+order: 170
+---
+
+## Overview
+
+Layouts wrap the screens routed beneath them with shared chrome — a top nav bar, a bottom tab bar, or both — so
+individual screens stay focused on their content.
+
+A `NativeLayout` class declares which chrome to render, and the framework automatically wraps every screen registered
+under that layout with the result. Push a detail screen onto a tabs section and the chrome swaps from "tabs" to "stack"
+automatically; pop back and it swaps back.
+
+## Attaching a layout to a route
+
+Use `Route::native(...)->layout(...)` for a single screen, or `Route::nativeGroup(...)` for a set of screens that
+share the same chrome.
+
+```php
+use App\NativeComponents\Browse;
+use App\NativeComponents\Home;
+use App\NativeComponents\ItemDetail;
+use App\NativeComponents\Layouts\StackLayout;
+use App\NativeComponents\Layouts\TabsLayout;
+use App\NativeComponents\Profile;
+
+// Three screens that share a tab-bar layout
+Route::nativeGroup(TabsLayout::class, function () {
+ Route::native('/tabs', Home::class);
+ Route::native('/tabs/browse', Browse::class);
+ Route::native('/tabs/profile', Profile::class);
+});
+
+// One screen with a stack-style top bar (back chevron + title)
+Route::native('/item/{id}', ItemDetail::class)
+ ->layout(StackLayout::class);
+```
+
+A screen with no layout renders without chrome — useful for splash, onboarding, or full-bleed views.
+
+## Built-in layouts
+
+NativePHP doesn't ship layouts in the framework — you write your own (they're tiny, see below). The sample app
+includes two reference layouts you can copy as a starting point:
+
+- `App\NativeComponents\Layouts\StackLayout` - Back chevron + screen title. No bottom tabs.
+- `App\NativeComponents\Layouts\TabsLayout` - Title bar plus a 3-tab bottom nav.
+
+## Writing a custom layout
+
+Extend `Native\Mobile\Edge\Layouts\NativeLayout` and override `navBar()` and/or `tabBar()`. Returning `null` from a
+method means "don't render that chrome."
+
+```php
+namespace App\NativeComponents\Layouts;
+
+use Native\Mobile\Edge\Layouts\Builders\NavAction;
+use Native\Mobile\Edge\Layouts\Builders\NavBar;
+use Native\Mobile\Edge\Layouts\Builders\Tab;
+use Native\Mobile\Edge\Layouts\Builders\TabBar;
+use Native\Mobile\Edge\Layouts\NativeLayout;
+use Native\Mobile\Edge\NativeComponent;
+
+class SyncUpTabsLayout extends NativeLayout
+{
+ public function navBar(NativeComponent $screen): ?NavBar
+ {
+ return NavBar::make()
+ ->title($screen->navTitle())
+ ->subtitle('All caught up')
+ ->back()
+ ->backgroundColor('#0891b2')
+ ->textColor('#FFFFFF')
+ ->elevation(8)
+ ->action(NavAction::make('search')->icon('search')->press('openSearch'));
+ }
+
+ public function tabBar(NativeComponent $screen): ?TabBar
+ {
+ return TabBar::make()
+ ->dark()
+ ->activeColor('#0891b2')
+ ->labelVisibility('labeled')
+ ->add(Tab::link('Chats', '/syncup', icon: 'chat_bubble')->badge('2'))
+ ->add(Tab::link('Friends', '/syncup/friends', icon: 'person.3.fill')->news())
+ ->add(Tab::link('Profile', '/syncup/profile', icon: 'person'));
+ }
+}
+```
+
+The `$screen` parameter is the live `NativeComponent` instance for the current screen, so the layout can read
+properties or methods on it (such as `$screen->navTitle()`) to customize the chrome per screen.
+
+See the [Top Bar](../edge-components/top-bar) and [Bottom Navigation](../edge-components/bottom-nav) pages for the
+full builder API.
+
+## How chrome wraps the screen
+
+When a screen renders, the framework's `wrapWithChrome` flow:
+
+1. Looks up the layout class declared on the route.
+2. Calls `$layout->navBar($screen)` and `$layout->tabBar($screen)`.
+3. Merges in any `navigationOptions()` declared on the screen.
+4. Merges in any imperative state set via `$this->setNavBar([...])` / `$this->setTabBar([...])`.
+5. Wraps the screen content in a `Column` filling the screen, with the bars stacked above and below the content.
+6. Picks the right safe-area variant for the wrapper:
+ - **TabBar present** → wrapper uses `safeAreaTop()`; the bar handles its own bottom inset.
+ - **NavBar without TabBar** → wrapper uses `safeAreaBottom()`; the NavBar handles its own top inset.
+ - **No bars** → wrapper uses `safeArea()` for both edges.
+
+The resulting element tree looks like:
+
+```
+Column.fill().safeAreaTop() ← (or whichever variant applies)
+├─ TopBar ← navBar
+├─ (flex-grow: 1)
+└─ BottomNav ← tabBar
+```
+
+Don't apply `safe-area` to the root of a screen wrapped by a layout — the layout already handles it.
+
+## Per-screen NavBar contributions
+
+Screens can add actions or override the title without writing their own layout, by implementing
+`navigationOptions()`:
+
+```php
+use Native\Mobile\Edge\Layouts\Builders\NavAction;
+use Native\Mobile\Edge\Layouts\Builders\NavBarOptions;
+use Native\Mobile\Edge\NativeComponent;
+
+class ItemDetail extends NativeComponent
+{
+ public function navigationOptions(): ?NavBarOptions
+ {
+ return NavBarOptions::make()
+ ->title("Item #{$this->param('id')}")
+ ->action(NavAction::make('save')->icon('save')->press('save'));
+ }
+
+ public function save(): void
+ {
+ // ...
+ }
+}
+```
+
+Non-null fields on the returned `NavBarOptions` override the layout's defaults; null fields fall through. Actions
+are appended to whatever the layout already declared.
+
+## Per-screen titles
+
+Override `navTitle()` to give a screen its own title that the layout's `NavBar` can read:
+
+```php
+class Profile extends NativeComponent
+{
+ public function navTitle(): string
+ {
+ return 'My Profile';
+ }
+}
+```
+
+A `StackLayout` that calls `->title($screen->navTitle())` will then show "My Profile" automatically when this screen
+is on top.
+
+## Imperative state changes
+
+If you need to mutate the chrome at runtime — for example, to flip the title between "Edit" and "Done" — call
+`$this->setNavBar([...])` or `$this->setTabBar([...])`:
+
+```php
+class Notes extends NativeComponent
+{
+ public bool $editing = false;
+
+ public function toggleEdit(): void
+ {
+ $this->editing = ! $this->editing;
+
+ $this->setNavBar([
+ 'title' => $this->editing ? 'Editing' : 'Notes',
+ ]);
+ }
+}
+```
+
+Imperative state is merged onto the layout's NavBar at the next render. Supported keys mirror the `NavBar` builder
+methods: `title`, `subtitle`, `back`, `backgroundColor`, `textColor`, `elevation`.
+
+## Inline overrides
+
+A screen can put its own `` or `` at the root of its blade, and the framework
+will skip the layout-supplied chrome **for that slot only**. This is useful for one-off screens (e.g. a chat
+detail with a custom titled top bar) without dropping the layout entirely.
+
+@verbatim
+```blade
+
+ {{-- Override only the top bar — the layout's tab bar still renders --}}
+
+
+
+ {{-- ... --}}
+
+
+```
+@endverbatim
diff --git a/resources/views/docs/mobile/4/the-basics/native-functions.md b/resources/views/docs/mobile/4/the-basics/native-functions.md
new file mode 100644
index 00000000..bdde3966
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/native-functions.md
@@ -0,0 +1,90 @@
+---
+title: Native Functions
+order: 100
+---
+
+Our custom PHP extension enables tight integration with each platform, providing a consistent and performant abstraction
+that lets you focus on building your app. Build for both platforms while you develop on one.
+
+Native device functions are called directly from your PHP code, giving you access to platform-specific features while
+maintaining the productivity and familiarity of Laravel development.
+
+These functions are called from your PHP code using an ever-growing set of [Plugins](../plugins/). Plugins contain PHP classes that
+are also wrapped in Laravel Facades for ease of access and testing, such as:
+
+```php
+Native\Mobile\Facades\Biometrics
+Native\Mobile\Facades\Browser
+Native\Mobile\Facades\Camera
+```
+
+Each of these is covered in their respective plugin's documentation.
+
+
+## Run from anywhere
+
+All native APIs are called through PHP. This means that your application is not reliant upon a web view to
+function.
+
+However, when using a web view you may also interact with the native functions easily from JavaScript using
+our convenient `Native` library.
+
+This is especially useful if you're building applications with a SPA framework, like Vue or React, as you can simply
+import the functions you need and move a lot of work into the reactive part of your UI.
+
+### Install the Node plugin
+
+To use the `Native` JavaScript library, you must install the plugin in your `package.json` file. Add the following
+section to the JSON:
+
+```js
+{
+ "dependencies": {
+ ...
+ },
+ "imports": { // [tl! focus:start]
+ "#nativephp": "./vendor/nativephp/mobile/resources/dist/native.js"
+ } // [tl! focus:end]
+}
+```
+
+Run `npm install`, then in your JavaScript, simply import the relevant functions from the plugin:
+
+```js
+import { On, Off, Microphone, Events } from '#nativephp';
+import { onMounted, onUnmounted } from 'vue';
+
+const buttonClicked = () => {
+ Microphone.record();
+};
+
+const handleRecordingFinished = () => {
+ // Update the UI
+};
+
+onMounted(() => {
+ On(Events.Microphone.MicrophoneRecorded, handleRecordingFinished);
+});
+
+onUnmounted(() => {
+ Off(Events.Microphone.MicrophoneRecorded, handleRecordingFinished);
+});
+```
+
+The library is fully typed, so your IDE should be able to pick up the available properties and methods to provide you
+with inline hints and code completion support.
+
+For the most part, the JavaScript APIs mirror the PHP APIs. Any key differences are noted in each Plugin's docs.
+
+This approach uses the PHP interface under the hood, meaning the two implementations stay in lock-step with each other.
+So you'll never need to worry about whether an API is available only in one place or the other; they're all always
+available wherever you need them.
+
+
diff --git a/resources/views/docs/mobile/4/the-basics/native-ui.md b/resources/views/docs/mobile/4/the-basics/native-ui.md
new file mode 100644
index 00000000..4a349e77
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/native-ui.md
@@ -0,0 +1,58 @@
+---
+title: Native UI
+order: 150
+---
+
+## Overview
+
+Your app's UI is fully native — SwiftUI on iOS, Jetpack Compose on Android — and it's all driven by PHP.
+
+Two pieces work together to make that happen:
+
+- **[SuperNative](../super-native/introduction)** is the engine. Each screen is a PHP component class — think
+ Livewire, but for native views. It holds your screen's state and behavior, shares memory directly with the native
+ side, and re-renders the UI whenever your properties change.
+- **[EDGE](../edge-components/introduction)** (Element Definition and Generation Engine) is the component language.
+ It's the full suite of Blade components — layout containers, typography, forms, navigation chrome, overlays — that
+ your screens are built from.
+
+You write familiar Blade; your users get truly native UI that matches each platform's design guidelines, in both
+light and dark mode.
+
+## Living on the EDGE
+
+Every EDGE element is a Blade component under the `native:` namespace:
+
+@verbatim
+```blade
+
+
+ Welcome
+
+
+
+```
+@endverbatim
+
+We take that single definition and turn it into fully native UI that works beautifully across all the supported
+mobile OS versions. EDGE components are fully compatible with hot reloading, so you can iterate on your UI at
+runtime without recompiling your app.
+
+Browse the full catalogue in the [EDGE Components](../edge-components/introduction) section.
+
+## Structuring your app
+
+Once you're comfortable with the component syntax, two concepts organize your screens into an app:
+
+### Navigation
+
+[Navigation](navigation) works like a native navigation stack, because it is one. Register screens with
+`Route::native()`, then push, pop, and replace them from inside your components — with native transitions, route
+parameters, and data passing along the way.
+
+### Layouts
+
+[Layouts](layouts) wrap the screens routed beneath them with shared chrome — a top nav bar, a bottom tab bar, or
+both — so individual screens stay focused on their content. Declare the chrome once in a `NativeLayout` class,
+attach it to a route or group of routes, and the framework swaps between "tabs" and "stack" chrome automatically as
+users move around.
diff --git a/resources/views/docs/mobile/4/the-basics/navigation.md b/resources/views/docs/mobile/4/the-basics/navigation.md
new file mode 100644
index 00000000..a321332e
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/navigation.md
@@ -0,0 +1,150 @@
+---
+title: Navigation
+order: 160
+---
+
+## Overview
+
+Native screens form a stack, exactly like a native iOS `UINavigationController` or an Android Activity stack. From
+inside a `NativeComponent`, you push, pop, and replace screens using methods on `$this`.
+
+## Registering routes
+
+Native routes are registered similarly to Livewire routes, but instead of using `Route::livewire()` you use the
+`Route::native()` macro:
+
+```php
+use App\NativeComponents\Home;
+use App\NativeComponents\ItemDetail;
+
+Route::native('/', Home::class);
+Route::native('/item/{id}', ItemDetail::class);
+```
+
+You can put these routes anywhere that makes sense for your application, but you may find that creating a
+`routes/mobile.php` to keep them all together is a clean option.
+
+Route parameters work just like Laravel web routes — `{id}` matches a path segment and is exposed to the screen
+through `$this->param('id')`.
+
+See [Layouts](layouts) for how to attach shared chrome to a route or group of routes.
+
+## Pushing a new screen
+
+```php
+$this->navigate('/item/42');
+```
+
+By default a push slides in from the right. Pass arbitrary data along with the navigation:
+
+```php
+$this->navigate('/item/42', ['source' => 'home-feed']);
+```
+
+The next screen reads the data with `$this->data('source')`.
+
+## Going back
+
+```php
+$this->back();
+```
+
+Pops the current screen off the stack and returns to whatever was beneath it. The default transition is a slide-out
+to the right.
+
+## Replacing the current screen
+
+```php
+$this->replace('/login');
+```
+
+Pops the current screen and pushes a new one in its place — useful after sign-in / sign-out or for in-place tab
+swaps. The default transition is a fade.
+
+
+
+## Exiting to the web view
+
+```php
+$this->exitToWeb('/dashboard');
+```
+
+Tear down the native UI stack and load the given URL in the web view. Use this for screens that aren't part of the
+native experience.
+
+## Custom transitions
+
+Chain `transition()` after a navigation method to override the default animation:
+
+```php
+use Native\Mobile\Edge\Transition;
+
+$this->navigate('/item/42')->transition(Transition::SlideFromBottom);
+```
+
+Available cases:
+
+- `Transition::SlideFromRight` - default for `navigate()`
+- `Transition::SlideFromLeft` - default for `back()`
+- `Transition::SlideFromBottom` - modal-style presentation
+- `Transition::Fade` - default for `replace()`
+- `Transition::FadeFromBottom` - subtle vertical fade
+- `Transition::ScaleFromCenter` - zoom-in effect
+- `Transition::None` - swap with no animation
+
+## Reading params and data
+
+```php
+class ItemDetail extends NativeComponent
+{
+ public function mount(): void
+ {
+ $id = $this->param('id'); // from the route URI
+ $source = $this->data('source', 'unknown'); // from navigate()'s second arg
+
+ // ...
+ }
+}
+```
+
+Both accessors take an optional default that's returned when the key is missing.
+
+## Resolving named routes
+
+If you've named your routes you can use `route()` on the component to resolve them to URIs:
+
+```php
+$this->navigate($this->route('listing.show', ['id' => 5]));
+```
+
+This delegates to Laravel's URL generator with `absolute: false`.
+
+## Customizing device-back behavior
+
+By default, the device back button (Android) pops the navigation stack. Override `onBackPressed()` to add custom
+behavior:
+
+```php
+class CheckoutForm extends NativeComponent
+{
+ public bool $dirty = false;
+
+ public function onBackPressed(): void
+ {
+ if ($this->dirty) {
+ $this->showDiscardConfirmation();
+
+ return;
+ }
+
+ $this->back();
+ }
+}
+```
+
+If the back press should ultimately still pop, call `$this->back()` from your override.
diff --git a/resources/views/docs/mobile/4/the-basics/overview.md b/resources/views/docs/mobile/4/the-basics/overview.md
new file mode 100644
index 00000000..75b12003
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/overview.md
@@ -0,0 +1,56 @@
+---
+title: Overview
+order: 50
+---
+
+NativePHP for Mobile is made up of multiple parts:
+
+- A Laravel application (PHP)
+- The `nativephp/mobile` Composer package
+- A custom build of PHP with custom NativePHP extension
+- Native applications (Swift & Kotlin)
+
+## Your Laravel app
+
+You can build your Laravel application just as you normally would, for the most part, sprinkling native functionality
+in where desired by using NativePHP's built-in APIs.
+
+## `nativephp/mobile`
+
+The package is a pretty normal Composer package. It contains the PHP code needed to interface with the NativePHP
+extension, the tools to install and run your applications, and all the code for each native application - iOS and
+Android.
+
+## The PHP builds
+
+When you run the `native:install` Artisan command, the package will fetch the appropriate versions of the custom-built
+PHP binaries.
+
+NativePHP for Mobile currently bundles **PHP 8.4**. You should ensure that your application is built to work with this
+version of PHP.
+
+These custom PHP builds have been compiled specifically to target the mobile platforms and cannot be used in other
+contexts.
+
+They are compiled as embeddable C libraries and embedded _into_ the native application. In this way, PHP doesn't run as
+a separate process/service under a typical web server environment; essentially, the native application itself is
+extended with the capability to execute your PHP code.
+
+Your Laravel application is then executed directly by the native app, using the embedded PHP engine to run the code.
+This runs PHP as close to natively as it can get. It is very fast and efficient on modern hardware.
+
+## The native apps
+
+NativePHP ships one app for iOS and one for Android. When you run the `native:run` Artisan command, your Laravel app is
+packaged up and copied into one of these apps.
+
+To build for both platforms, you must run the `native:run` command twice, targeting each platform.
+
+Each native app "shell" runs a number of steps to prepare the environment each time your application is booted,
+including:
+
+- Checking to see if the bundled version of your Laravel app is newer than the installed version
+- Installing the newer version if necessary
+- Running migrations
+- Clearing caches
+- Creating storage symlinks
diff --git a/resources/views/docs/mobile/4/the-basics/positioning.md b/resources/views/docs/mobile/4/the-basics/positioning.md
new file mode 100644
index 00000000..1168e6f9
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/positioning.md
@@ -0,0 +1,63 @@
+---
+title: Positioning
+order: 190
+---
+
+## Overview
+
+Most layouts use the flex engine — children flow inside their parent column or row. For overlays like floating
+action buttons, badges that hang off the edge of an avatar, or a "skip" button pinned to a corner, the framework
+also supports CSS-style absolute positioning.
+
+## Tailwind classes
+
+@verbatim
+```blade
+
+ {{-- Pinned to the bottom-right corner of the parent --}}
+
+```
+@endverbatim
+
+- `absolute` - Take this element out of flex flow and position it relative to the nearest positioned ancestor
+- `relative` - Default; element flows normally
+- `top-[N]` - Inset from the parent's top edge (dp)
+- `right-[N]` - Inset from the parent's right edge (dp)
+- `bottom-[N]` - Inset from the parent's bottom edge (dp)
+- `left-[N]` - Inset from the parent's left edge (dp)
+
+## Anchor convention
+
+When `right-[N]` is set and `left-` is unset, the child anchors to the parent's right edge offset by N. Same for
+`bottom-`. This mirrors CSS `position: absolute` shorthand.
+
+If both `left-` and `right-` are set, the child stretches between them. Likewise for `top-` and `bottom-`.
+
+## Common pattern: floating action button
+
+A FAB pinned to the bottom-right of a screen:
+
+@verbatim
+```blade
+
+
+ {{-- main content --}}
+
+
+
+
+
+
+```
+@endverbatim
+
+Absolute children only occupy their placed bounds — siblings receive scroll and touch events normally.
+
+
diff --git a/resources/views/docs/mobile/4/the-basics/safe-area.md b/resources/views/docs/mobile/4/the-basics/safe-area.md
new file mode 100644
index 00000000..e7ed94ab
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/safe-area.md
@@ -0,0 +1,70 @@
+---
+title: Safe Area
+order: 180
+---
+
+## Overview
+
+Safe areas are the regions of the screen not obscured by device features or system UI — the notch and status bar at the
+top, the home indicator or gesture bar at the bottom. The framework lets you inset content past those regions with a
+single attribute.
+
+Safe areas take care of all of the necessary calculations to support almost every device, ensuring your UI doesn't get
+obscured regardless of the user's device or its orientation.
+
+## Component classes
+
+The simplest way to apply a safe area inset is with a class:
+
+@verbatim
+```blade
+
+ {{-- Insets both top and bottom --}}
+
+
+
+ {{-- Insets only the status-bar / notch --}}
+
+
+
+ {{-- Insets only the home-indicator zone --}}
+
+```
+@endverbatim
+
+- `safe-area` - Inset both top and bottom edges
+- `safe-area-top` - Inset only the top edge (status bar / notch)
+- `safe-area-bottom` - Inset only the bottom edge (home indicator)
+
+## PHP API
+
+The same variants are available as fluent methods when building elements in PHP:
+
+```php
+use Native\Mobile\Edge\Elements\Column;
+
+Column::make()->safeArea(); // both edges
+Column::make()->safeAreaTop(); // top only
+Column::make()->safeAreaBottom(); // bottom only
+```
+
+## Layouts already handle this for you
+
+When a screen is wrapped by a [layout](layouts), the framework's `wrapWithChrome` flow picks the right safe-area
+variant for the wrapper based on which chrome is present:
+
+| Chrome | Wrapper inset | Why |
+|------------------|------------------|------------------------------------------------------|
+| TabBar present | `safeAreaTop()` | The tab bar handles its own bottom inset internally |
+| NavBar only | `safeAreaBottom()` | The nav bar handles its own top inset |
+| No chrome | `safeArea()` | Wrapper handles both edges |
+| Both bars | (neither) | Each bar handles its own edge |
+
+
diff --git a/resources/views/docs/mobile/4/the-basics/splash-screens.md b/resources/views/docs/mobile/4/the-basics/splash-screens.md
new file mode 100644
index 00000000..1c803e4e
--- /dev/null
+++ b/resources/views/docs/mobile/4/the-basics/splash-screens.md
@@ -0,0 +1,18 @@
+---
+title: Splash Screens
+order: 400
+---
+
+NativePHP makes it easy to add custom splash screens to your iOS and Android apps.
+
+## Supply your Splash Screens
+
+Place the relevant files in the locations specified:
+
+- `public/splash.png` - for the Light Mode splash screen
+- `public/splash-dark.png` - for the Dark Mode splash screen
+
+### Requirements
+- Format: PNG
+- Minimum Size/Ratio: 1080 × 1920 pixels
+- GD PHP extension must be enabled, ensure it has enough memory (~2GB should be enough)
diff --git a/resources/views/plugin-show.blade.php b/resources/views/plugin-show.blade.php
index 35d3633c..54afacc7 100644
--- a/resources/views/plugin-show.blade.php
+++ b/resources/views/plugin-show.blade.php
@@ -107,7 +107,7 @@ class="text-2xl font-bold sm:text-3xl"
@if ($plugin->readme_html)
-
diff --git a/routes/web.php b/routes/web.php
index a1d99813..a78b88a9 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -72,15 +72,15 @@
Route::redirect('t-shirt', 'blog/nativephp-for-mobile-is-now-free');
Route::redirect('tshirt', 'blog/nativephp-for-mobile-is-now-free');
-// Redirect mobile v3 core plugin docs to plugin directory pages
-Route::get('docs/mobile/3/plugins/core/{page}', function (string $page) {
+// 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);
-})->where('page', '[a-z-]+');
+})->where('version', '[0-9]+')->where('page', '[a-z-]+');
-// Redirect old mobile v3 API docs to plugin directory pages
-Route::get('docs/mobile/3/apis/{page}', function (string $page) {
+// Redirect old mobile API docs to plugin directory pages
+Route::get('docs/mobile/{version}/apis/{page}', function (string $version, string $page) {
return redirect("/plugins/nativephp/mobile-{$page}", 301);
-})->where('page', '[a-z-]+');
+})->where('version', '[0-9]+')->where('page', '[a-z-]+');
// Webhook routes (must be outside web middleware for CSRF bypass)
Route::post('opencollective/contribution', [OpenCollectiveWebhookController::class, 'handle'])->name('opencollective.webhook');
@@ -210,23 +210,23 @@
->where('version', '[0-9]+')
->name('docs.show');
-// Forward platform requests without version to the latest version
+// Forward platform requests without version to the latest stable version
Route::get('docs/{platform}/{page?}', function (string $platform, $page = null) {
$page ??= 'getting-started/introduction';
- // Find the latest version for this platform
$docsPath = resource_path('views/docs/'.$platform);
if (! is_dir($docsPath)) {
abort(404);
}
- $versions = collect(scandir($docsPath))
- ->filter(fn ($dir) => is_numeric($dir))
- ->sort()
- ->values();
-
- $latestVersion = $versions->last() ?? '1';
+ $latestVersion = config("docs.latest_versions.{$platform}")
+ ?? collect(scandir($docsPath))
+ ->filter(fn ($dir) => is_numeric($dir))
+ ->reject(fn ($dir) => in_array((int) $dir, config("docs.prerelease_versions.{$platform}", [])))
+ ->sort()
+ ->last()
+ ?? '1';
return redirect("/docs/{$platform}/{$latestVersion}/{$page}", 301);
})
diff --git a/tests/Feature/DocsPrereleaseVersionTest.php b/tests/Feature/DocsPrereleaseVersionTest.php
new file mode 100644
index 00000000..ca4cd532
--- /dev/null
+++ b/tests/Feature/DocsPrereleaseVersionTest.php
@@ -0,0 +1,126 @@
+ 'test-token']);
+ Http::fake([
+ '*' => Http::response(['blocks' => []], 200),
+ ]);
+ }
+
+ public function test_prerelease_pages_show_the_beta_notice(): void
+ {
+ $response = $this->get('/docs/mobile/4/edge-components/button');
+
+ $response->assertOk();
+ $response->assertSee('pre-release documentation');
+ $response->assertSee('View the stable version (3.x)');
+ }
+
+ public function test_beta_notice_links_to_the_same_page_when_it_exists_on_stable(): void
+ {
+ // top-bar exists in v3 edge-components, so the notice should deep-link to it.
+ $this->get('/docs/mobile/4/edge-components/top-bar')
+ ->assertOk()
+ ->assertSee('/docs/mobile/3/edge-components/top-bar');
+
+ // button does not exist in v3, so the notice falls back to the introduction.
+ $this->get('/docs/mobile/4/edge-components/button')
+ ->assertOk()
+ ->assertSee('/docs/mobile/3/getting-started/introduction');
+ }
+
+ public function test_stable_pages_do_not_show_the_beta_notice(): void
+ {
+ $this->get('/docs/mobile/3/getting-started/introduction')
+ ->assertOk()
+ ->assertDontSee('pre-release documentation');
+ }
+
+ public function test_unversioned_docs_urls_redirect_to_the_stable_version(): void
+ {
+ $this->get('/docs/mobile')
+ ->assertRedirect('/docs/mobile/3/getting-started/introduction');
+
+ $this->get('/docs/mobile/the-basics/web-view')
+ ->assertRedirect('/docs/mobile/3/the-basics/web-view');
+ }
+
+ public function test_version_switcher_lists_newest_first_and_labels_beta(): void
+ {
+ $response = $this->get('/docs/mobile/4/getting-started/introduction');
+ $content = $response->getContent();
+
+ $response->assertOk();
+ $response->assertSee('Version 4.x (beta)');
+
+ // Newest version appears before older ones in the dropdown.
+ $this->assertMatchesRegularExpression(
+ '#
\s*