diff --git a/website/docs/_meta.json b/website/docs/v1/_meta.json
similarity index 100%
rename from website/docs/_meta.json
rename to website/docs/v1/_meta.json
diff --git a/website/docs/_nav.json b/website/docs/v1/_nav.json
similarity index 100%
rename from website/docs/_nav.json
rename to website/docs/v1/_nav.json
diff --git a/website/docs/android/_meta.json b/website/docs/v1/android/_meta.json
similarity index 100%
rename from website/docs/android/_meta.json
rename to website/docs/v1/android/_meta.json
diff --git a/website/docs/v1/android/api/plugin-configuration.md b/website/docs/v1/android/api/plugin-configuration.md
new file mode 100644
index 00000000..3defdb82
--- /dev/null
+++ b/website/docs/v1/android/api/plugin-configuration.md
@@ -0,0 +1,258 @@
+# Plugin Configuration (Android)
+
+The Voltra Expo config plugin accepts Android-specific configuration options in your `app.json` or `app.config.js`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "groupIdentifier": "group.your.bundle.identifier",
+ "android": {
+ "enableNotifications": true,
+ "widgets": [
+ {
+ "id": "weather",
+ "displayName": "Weather Widget",
+ "description": "Shows current weather conditions",
+ "targetCellWidth": 2,
+ "targetCellHeight": 2,
+ "initialStatePath": "./widgets/weather-initial.tsx",
+ "previewImage": "./assets/widgets/weather-preview.png"
+ }
+ ]
+ }
+ }
+ ]
+ ]
+ }
+}
+```
+
+## Android-Specific Configuration
+
+### `android.enableNotifications` (optional)
+
+Enables Android notification-related manifest plumbing used by Voltra features such as ongoing notifications.
+
+When enabled, the config plugin adds:
+
+- `android.permission.POST_NOTIFICATIONS`
+- `android.permission.POST_PROMOTED_NOTIFICATIONS`
+- `voltra.VoltraOngoingNotificationDismissedReceiver`
+
+This does not grant runtime notification permission automatically. Your app still needs to request notification permission on Android 13 and above.
+
+For setup and usage examples, see [Managing Android Ongoing Notifications](../development/managing-ongoing-notifications).
+
+### `android.widgets` (optional)
+
+Array of widget configurations for Home Screen widgets. Each widget will be available in the Android widget picker.
+
+**Widget Configuration Properties:**
+
+- `id`: Unique identifier for the widget (alphanumeric with underscores only)
+- `displayName`: Name shown in the widget picker
+- `description`: Description shown in the widget picker
+- `targetCellWidth`: Target widget width in grid cells (1-5, required)
+- `targetCellHeight`: Target widget height in grid cells (1-5, required)
+- `minCellWidth`: (optional) Minimum width in grid cells (defaults to targetCellWidth)
+- `minCellHeight`: (optional) Minimum height in grid cells (defaults to targetCellHeight)
+- `minWidth`: (optional) Minimum width in dp (overrides minCellWidth calculation)
+- `minHeight`: (optional) Minimum height in dp (overrides minCellHeight calculation)
+- `resizeMode`: (optional) Widget resize behavior (`"none"` | `"horizontal"` | `"vertical"` | `"horizontal|vertical"`, default: `"horizontal|vertical"`)
+- `widgetCategory`: (optional) Widget category (`"home_screen"` | `"keyguard"` | `"home_screen|keyguard"`, default: `"home_screen"`)
+- `initialStatePath`: (optional) Path to a file that exports initial widget state (see [Widget Pre-rendering](../development/widget-pre-rendering))
+- `previewImage`: (optional) Path to preview image for widget picker (PNG/JPG/WebP)
+- `previewLayout`: (optional) Path to custom XML layout for widget picker preview (Android 12+)
+- `serverUpdate`: (optional) Enable server-driven updates. See [Server-driven widgets](../development/server-driven-widgets) for full details.
+ - `url`: The Voltra SSR endpoint URL
+ - `intervalMinutes`: Update interval in minutes (default: `15`, minimum 15 per WorkManager)
+ - `refresh`: Show a native refresh button (default: `false`)
+
+## Widget Sizing
+
+### Grid Cells vs Density-Independent Pixels (dp)
+
+Android uses grid cells to define widget sizes. By default, the formula is:
+- **minWidth/minHeight (dp) = (cellCount × 70) - 30**
+
+**Example:**
+- 2 cells = (2 × 70) - 30 = **110 dp**
+- 4 cells = (4 × 70) - 30 = **250 dp**
+
+You can override this with explicit `minWidth` and `minHeight` in dp.
+
+### Standard Dimensions
+
+| Family | Cells | Default DP | Typical Use |
+|--------|-------|-----------|-------------|
+| Small | 2×1 | 110 × 40 | Quick glance info |
+| Medium | 2×2 | 110 × 110 | Main widget size |
+| Large | 4×2 | 250 × 110 | Rich content |
+| Extra Large | 4×4 | 250 × 250 | Complex layouts |
+
+## Widget Picker Previews
+
+When users add a widget to their home screen, Android displays a preview in the widget picker. Voltra supports three preview methods, with automatic fallback:
+
+### Preview Priority Chain
+
+1. **`previewLayout`** (Android 12+) - Custom XML layout for scalable preview
+2. **`previewImage`** (All versions) - Static image or auto-generated layout
+3. **Default** - System placeholder layout
+
+### Using `previewImage`
+
+Static preview image for all Android versions:
+
+```json
+{
+ "widgets": [
+ {
+ "id": "weather",
+ "displayName": "Weather Widget",
+ "targetCellWidth": 2,
+ "targetCellHeight": 2,
+ "previewImage": "./assets/widgets/weather-preview.png"
+ }
+ ]
+}
+```
+
+When only `previewImage` is specified, Voltra automatically generates a layout that displays the image with proper scaling.
+
+### Using `previewLayout`
+
+Custom XML layout for scalable previews (Android 12+):
+
+```json
+{
+ "widgets": [
+ {
+ "id": "todos",
+ "displayName": "Todo Widget",
+ "targetCellWidth": 2,
+ "targetCellHeight": 2,
+ "previewLayout": "./assets/widgets/todos-preview.xml"
+ }
+ ]
+}
+```
+
+**Example `todos-preview.xml`:**
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+The preview layout is rendered at the widget's target size and displayed in the widget picker.
+
+### Combined Preview Setup
+
+For best results across Android versions:
+
+```json
+{
+ "widgets": [
+ {
+ "id": "weather",
+ "displayName": "Weather Widget",
+ "targetCellWidth": 2,
+ "targetCellHeight": 2,
+ "previewImage": "./assets/widgets/weather-preview.png",
+ "previewLayout": "./assets/widgets/weather-preview.xml",
+ "initialStatePath": "./widgets/weather-initial.tsx"
+ }
+ ]
+}
+```
+
+This configuration:
+- Uses `previewLayout` on Android 12+ (scalable, accurate preview)
+- Falls back to `previewImage` on Android 11 and earlier
+- Shows actual widget content on home screen via `initialStatePath` (when available)
+
+## Widget Pre-rendering
+
+Use `initialStatePath` to provide pre-rendered widget state:
+
+```json
+{
+ "widgets": [
+ {
+ "id": "weather",
+ "displayName": "Weather Widget",
+ "targetCellWidth": 2,
+ "targetCellHeight": 2,
+ "initialStatePath": "./widgets/weather-initial.tsx"
+ }
+ ]
+}
+```
+
+When the app is built, Voltra pre-renders the widget at the specified path and bundles it as `voltra_initial_states.json`. The widget displays this content immediately when first added to the home screen, before any dynamic updates.
+
+See [Widget Pre-rendering](../development/widget-pre-rendering) for details on creating initial state files.
+
+## Example Configuration
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "groupIdentifier": "group.com.example.app",
+ "android": {
+ "enableNotifications": true,
+ "widgets": [
+ {
+ "id": "voltra",
+ "displayName": "Voltra Widget",
+ "description": "Voltra logo widget",
+ "minCellWidth": 2,
+ "minCellHeight": 2,
+ "targetCellWidth": 2,
+ "targetCellHeight": 2,
+ "resizeMode": "horizontal|vertical",
+ "widgetCategory": "home_screen",
+ "initialStatePath": "./widgets/android-voltra-widget-initial.tsx",
+ "previewImage": "./assets/voltra-icon.jpg"
+ },
+ {
+ "id": "interactive_todos",
+ "displayName": "Interactive Todos",
+ "description": "Quick todo list widget",
+ "targetCellWidth": 2,
+ "targetCellHeight": 2,
+ "previewLayout": "./assets/widgets/todos-preview.xml"
+ }
+ ]
+ }
+ }
+ ]
+ ]
+ }
+}
+```
diff --git a/website/docs/android/charts.md b/website/docs/v1/android/charts.md
similarity index 100%
rename from website/docs/android/charts.md
rename to website/docs/v1/android/charts.md
diff --git a/website/docs/android/components/_meta.json b/website/docs/v1/android/components/_meta.json
similarity index 100%
rename from website/docs/android/components/_meta.json
rename to website/docs/v1/android/components/_meta.json
diff --git a/website/docs/v1/android/components/interactive.md b/website/docs/v1/android/components/interactive.md
new file mode 100644
index 00000000..8887ef8e
--- /dev/null
+++ b/website/docs/v1/android/components/interactive.md
@@ -0,0 +1,97 @@
+# Interactive Controls (Android)
+
+User interface controls that respond to user interaction on Android widgets.
+
+### Button
+
+Standard button component. On Android, all buttons always open the application when clicked. You can provide a `deepLinkUrl` to open a specific screen.
+
+**Parameters:**
+
+- `enabled` (boolean, optional): Whether the button is enabled.
+- `deepLinkUrl` (string, optional): URL to open when the button is clicked. If not provided, the app will open to its main activity.
+
+Voltra also provides specialized button variants:
+
+#### FilledButton
+- `text` (string): Button label.
+- `enabled` (boolean, optional).
+- `deepLinkUrl` (string, optional).
+- `icon` (object, optional): `{ assetName: string }`.
+- `backgroundColor` (string, optional).
+- `contentColor` (string, optional).
+- `maxLines` (number, optional).
+
+#### OutlineButton
+- `text` (string): Button label.
+- `enabled` (boolean, optional).
+- `deepLinkUrl` (string, optional).
+- `icon` (object, optional): `{ assetName: string }`.
+- `contentColor` (string, optional).
+- `maxLines` (number, optional).
+
+#### CircleIconButton & SquareIconButton
+- `enabled` (boolean, optional).
+- `deepLinkUrl` (string, optional).
+- `icon` (object, optional): `{ assetName: string, base64: string }`.
+- `contentDescription` (string, optional).
+- `backgroundColor` (string, optional).
+- `contentColor` (string, optional).
+
+---
+
+### Clickable Components
+
+Most components support being clickable by setting the `pressable` prop (short name `prs` in raw elements) in their props.
+
+**Parameters:**
+- `pressable` (boolean): Set to `true` to make the component respond to clicks.
+- `deepLinkUrl` (string, optional): URL to open when clicked.
+
+---
+
+### Switch
+
+A toggle switch component. On Android, toggles always open the application when clicked.
+
+**Parameters:**
+
+- `checked` (boolean, optional): Current state of the switch.
+- `deepLinkUrl` (string, optional): URL to open when clicked.
+- `text` (string, optional): Label displayed next to the switch.
+- `thumbCheckedColor` (string, optional).
+- `thumbUncheckedColor` (string, optional).
+- `trackCheckedColor` (string, optional).
+- `trackUncheckedColor` (string, optional).
+- `maxLines` (number, optional): Maximum lines for the label.
+
+---
+
+### CheckBox
+
+Standard checkbox component. On Android, checkboxes always open the application when clicked.
+
+**Parameters:**
+
+- `checked` (boolean, optional).
+- `deepLinkUrl` (string, optional).
+- `text` (string, optional).
+- `checkedColor` (string, optional).
+- `uncheckedColor` (string, optional).
+- `maxLines` (number, optional).
+
+---
+
+### RadioButton
+
+Standard radio button component. On Android, radio buttons always open the application when clicked.
+
+**Parameters:**
+
+- `checked` (boolean, optional).
+- `enabled` (boolean, optional).
+- `deepLinkUrl` (string, optional).
+- `text` (string, optional).
+- `checkedColor` (string, optional).
+- `uncheckedColor` (string, optional).
+- `maxLines` (number, optional).
diff --git a/website/docs/v1/android/components/layout.md b/website/docs/v1/android/components/layout.md
new file mode 100644
index 00000000..55937ddf
--- /dev/null
+++ b/website/docs/v1/android/components/layout.md
@@ -0,0 +1,91 @@
+# Layout & Containers (Android)
+
+Components that arrange other elements or provide structural grouping using Jetpack Compose Glance primitives. See [Styling](../development/styling) for details on layout and spacing properties.
+
+### Column
+
+A vertical container that arranges its children in a column.
+
+**Parameters:**
+
+- `horizontalAlignment` (string, optional): `"start"`, `"center-horizontally"`, `"end"`.
+- `verticalAlignment` (string, optional): `"top"`, `"center-vertically"`, `"bottom"`.
+
+---
+
+### Row
+
+A horizontal container that arranges its children in a row.
+
+**Parameters:**
+
+- `horizontalAlignment` (string, optional): `"start"`, `"center-horizontally"`, `"end"`.
+- `verticalAlignment` (string, optional): `"top"`, `"center-vertically"`, `"bottom"`.
+
+---
+
+### Box
+
+A container that stacks its children on top of each other.
+
+**Parameters:**
+
+- `contentAlignment` (string, optional): Combined alignment. Supports `"top-start"`, `"top-center"`, `"top-end"`, `"center-start"`, `"center"`, `"center-end"`, `"bottom-start"`, `"bottom-center"`, `"bottom-end"`.
+
+---
+
+### Scaffold
+
+A top-level container that provides a standard layout structure for widgets.
+
+**Parameters:**
+
+- `backgroundColor` (string, optional): Background color for the scaffold.
+- `horizontalPadding` (number, optional): Horizontal padding in dp.
+
+---
+
+### TitleBar
+
+A component that displays a title bar with an optional icon.
+
+**Parameters:**
+
+- `title` (string): Title text to display.
+- `startIcon` (object): `{ assetName: string }`.
+- `textColor` (string, optional).
+- `iconColor` (string, optional).
+- `fontFamily` (string, optional).
+
+---
+
+### Spacer
+
+A component that provides fixed spacing between elements.
+
+**Parameters:**
+
+- `size` (number): Size of the spacer in dp.
+
+---
+
+### LazyColumn
+
+A scrollable vertical list that only renders visible items.
+
+**Parameters:**
+
+- `horizontalAlignment` (string, optional): `"start"`, `"center-horizontally"`, `"end"`.
+
+---
+
+### LazyVerticalGrid
+
+A scrollable grid of items.
+
+**Parameters:**
+
+- `columns` (number | `"adaptive"`): Number of columns or `"adaptive"` for an adaptive grid.
+- `minSize` (number, optional): Minimum size (in dp) for items in adaptive grid mode.
+- `horizontalAlignment` (string, optional): `"start"`, `"center-horizontally"`, `"end"`.
+- `verticalAlignment` (string, optional): `"top"`, `"center"`, `"bottom"`.
diff --git a/website/docs/v1/android/components/status.md b/website/docs/v1/android/components/status.md
new file mode 100644
index 00000000..a093f01d
--- /dev/null
+++ b/website/docs/v1/android/components/status.md
@@ -0,0 +1,27 @@
+# Data Visualization & Status (Android)
+
+Components for displaying data and status information on Android widgets.
+
+### LinearProgressIndicator
+
+A horizontal progress bar.
+
+**Parameters:**
+
+- `progress` (number, optional): Current progress value (0.0 to 1.0). If omitted, the indicator will be indeterminate.
+- `color` (string, optional): Color for the progress indicator.
+- `backgroundColor` (string, optional): Color for the background track.
+
+---
+
+### CircularProgressIndicator
+
+A circular progress indicator.
+
+**Parameters:**
+
+- `color` (string, optional): Color for the progress indicator.
+
+:::warning Indeterminate Only
+Due to Jetpack Compose Glance limitations, the `CircularProgressIndicator` on Android only supports **indeterminate** mode. The `progress` property is ignored.
+:::
diff --git a/website/docs/v1/android/components/visual.md b/website/docs/v1/android/components/visual.md
new file mode 100644
index 00000000..4ebca048
--- /dev/null
+++ b/website/docs/v1/android/components/visual.md
@@ -0,0 +1,45 @@
+# Visual Elements & Typography (Android)
+
+Static or decorative elements used to display content on Android widgets. See [Styling](../development/styling) for details on supported style properties.
+
+### Text
+
+Displays text content.
+
+**Parameters:**
+
+- `maxLines` (number, optional): Maximum number of lines to display.
+- `renderAsBitmap` (boolean, optional): Renders text as a bitmap image to enable [custom fonts](../development/custom-fonts). Requires `fontFamily` in the style prop.
+
+---
+
+### Image
+
+Displays bitmap images from the asset catalog or base64 encoded data.
+
+**Parameters:**
+
+- `source` (object, optional): Image source object.
+ - `assetName` (string): Reference to a pre-bundled image (drawable resource) or a [preloaded image](../development/image-preloading).
+ - `base64` (string): Base64 encoded image data.
+- `resizeMode` (string, optional): `"cover"`, `"contain"`, `"stretch"`, `"repeat"`, or `"center"`.
+- `contentScale` (string, optional): Glance-specific terminology for resize mode: `"crop"`, `"fit"`, `"fill-bounds"`.
+- `contentDescription` (string, optional): Accessibility description for the image.
+- `alpha` (number, optional): Opacity value from 0.0 to 1.0.
+- `tintColor` (string, optional): Color to tint the image with.
+- `fallback` (ReactNode, optional): Custom content rendered when the image is missing.
+
+**Styling the fallback:**
+
+To add a background color when an image is missing, use `backgroundColor` in the `style` prop:
+
+```jsx
+
+```
+
+:::tip Image Preloading
+For dynamic images from remote URLs, use the [Image Preloading](../development/image-preloading) API to cache them locally for use in widgets.
+:::
diff --git a/website/docs/android/development/_meta.json b/website/docs/v1/android/development/_meta.json
similarity index 100%
rename from website/docs/android/development/_meta.json
rename to website/docs/v1/android/development/_meta.json
diff --git a/website/docs/v1/android/development/custom-fonts.md b/website/docs/v1/android/development/custom-fonts.md
new file mode 100644
index 00000000..2d3692ef
--- /dev/null
+++ b/website/docs/v1/android/development/custom-fonts.md
@@ -0,0 +1,91 @@
+# Custom Fonts
+
+Android Glance only supports a handful of built-in font families (`monospace`, `serif`, `sans-serif`, `cursive`). Voltra works around this by rendering text as a bitmap with a custom `Typeface` loaded from `assets/fonts/`.
+
+## Setup
+
+### 1. Add font files to the plugin config
+
+List your font paths in the top-level `fonts` array. These can be local files or packages from `@expo-google-fonts`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "fonts": [
+ "node_modules/@expo-google-fonts/pacifico/400Regular/Pacifico_400Regular.ttf",
+ "./assets/fonts/MyCustomFont.ttf"
+ ],
+ "android": { "widgets": [...] }
+ }
+ ]
+ ]
+ }
+}
+```
+
+### 2. Run prebuild
+
+```bash
+npx expo prebuild
+```
+
+The plugin copies each font file to `android/app/src/main/assets/fonts/` automatically.
+
+### 3. Use `renderAsBitmap` on Text
+
+```tsx
+import { VoltraAndroid } from 'voltra/android'
+
+
+ Hello Voltra!
+
+```
+
+The `fontFamily` value should match the font filename **without the extension**.
+
+## How it works
+
+When `renderAsBitmap` is set and `fontFamily` is provided in the style:
+
+1. The font is loaded via `Typeface.createFromAsset()` (cached with an LRU cache)
+2. Text is drawn to an Android `Canvas` bitmap using `StaticLayout`
+3. The bitmap is displayed as a Glance `Image` with fixed dp dimensions
+
+This means the text is rasterized — it won't respond to system font size settings. Use it only when a custom typeface is needed.
+
+## Supported style properties
+
+When rendering as bitmap, the following text style properties are supported:
+
+| Property | Description |
+|----------|-------------|
+| `fontSize` | Font size in sp (scaled to device density) |
+| `fontFamily` | Font filename without extension |
+| `fontWeight` | `"normal"` or `"bold"` |
+| `color` | Text color |
+| `textAlign` | `"left"`, `"center"`, `"right"` |
+| `textDecorationLine` | `"underline"`, `"line-through"` |
+| `letterSpacing` | Letter spacing value |
+| `lineHeight` | Line spacing |
+
+## Built-in font families
+
+For built-in families you don't need `renderAsBitmap` — use `fontFamily` in style directly:
+
+- `monospace`
+- `serif`
+- `sans-serif`
+- `cursive`
+
+These are passed through to Glance's native `FontFamily` API.
diff --git a/website/docs/v1/android/development/developing-widgets.md b/website/docs/v1/android/development/developing-widgets.md
new file mode 100644
index 00000000..7384b370
--- /dev/null
+++ b/website/docs/v1/android/development/developing-widgets.md
@@ -0,0 +1,76 @@
+# Developing Android Widgets
+
+Voltra allows you to build Android Home Screen widgets using JSX and Jetpack Compose Glance primitives.
+
+## Glance Primitives
+
+On Android, you use `VoltraAndroid` components which map to Glance primitives:
+
+- **Column:** Vertical layout
+- **Row:** Horizontal layout
+- **Box:** Stacked layout
+- **Spacer:** Flexible spacing
+- **Text:** Displaying text
+- **Image:** Displaying images
+- **Scaffold:** Top-level container
+
+### Example Widget
+
+```tsx
+import { VoltraAndroid } from 'voltra'
+
+const WeatherWidget = ({ temperature, condition }) => (
+
+
+
+ {temperature}°C
+
+
+ {condition}
+
+
+
+)
+```
+
+## Update API
+
+To update a widget's content, use the `updateWidget` function from `voltra/client`:
+
+```typescript
+import { updateWidget } from 'voltra/client'
+
+await updateWidget('weather_widget', )
+```
+
+## Layout Constraints
+
+Unlike standard React Native or iOS Stacks, Android Glance layouts are more restrictive:
+- **Width/Height:** Use fixed numbers (dp), `"100%"` to fill available space, or `"auto"` to wrap content.
+- **Modifiers:** Most styling is handled via the `style` prop, which maps to Glance `Modifier`s. See [Styling](./styling) for full details.
+- **Alignment:** Use `verticalAlignment` and `horizontalAlignment` props on `Column` and `Row`.
+
+## Advanced Features
+
+- **[Querying Active Widgets](./querying-active-widgets):** Detect active widget instances and their sizes.
+- **[Testing and Previews](./testing-and-previews):** Preview layouts within your app.
+- **[Widget Picker Previews](../api/plugin-configuration#widget-picker-previews):** Configure how your widget appears in the Android widget picker.
+- **[Image Preloading](./image-preloading):** Cache remote images for use in widgets.
+- **[Widget Pre-rendering](./widget-pre-rendering):** Provide initial state for widgets before the app first runs.
+
+## Widget Picker Previews
+
+When users browse the widget picker to add your widget to their home screen, they see a preview. You can customize this preview using:
+
+- **`previewImage`:** Static image (PNG/JPG/WebP) that shows in the picker on all Android versions
+- **`previewLayout`:** Custom XML layout that renders a scalable preview on Android 12+
+
+See [Plugin Configuration - Widget Picker Previews](../api/plugin-configuration#widget-picker-previews) for configuration details and examples.
diff --git a/website/docs/v1/android/development/dynamic-colors.md b/website/docs/v1/android/development/dynamic-colors.md
new file mode 100644
index 00000000..e7fddbae
--- /dev/null
+++ b/website/docs/v1/android/development/dynamic-colors.md
@@ -0,0 +1,124 @@
+# Dynamic colors
+
+Voltra supports Android dynamic colors through semantic tokens exposed from `voltra/android`.
+
+These colors follow the current Android Material palette, so widgets can pick up wallpaper and theme changes without waiting for JavaScript to run again.
+
+## Importing dynamic colors
+
+```tsx
+import { AndroidDynamicColors, VoltraAndroid } from 'voltra/android'
+```
+
+`AndroidDynamicColors` includes these roles:
+
+- `primary`
+- `onPrimary`
+- `primaryContainer`
+- `onPrimaryContainer`
+- `secondary`
+- `onSecondary`
+- `secondaryContainer`
+- `onSecondaryContainer`
+- `tertiary`
+- `onTertiary`
+- `tertiaryContainer`
+- `onTertiaryContainer`
+- `error`
+- `errorContainer`
+- `onError`
+- `onErrorContainer`
+- `background`
+- `onBackground`
+- `surface`
+- `onSurface`
+- `surfaceVariant`
+- `onSurfaceVariant`
+- `outline`
+- `inverseOnSurface`
+- `inverseSurface`
+- `inversePrimary`
+- `widgetBackground`
+
+## Example
+
+```tsx
+import { AndroidDynamicColors, VoltraAndroid } from 'voltra/android'
+
+export function WeatherWidget() {
+ return (
+
+
+
+ 21°
+
+
+
+
+
+ )
+}
+```
+
+## Where you can use them
+
+You can use `AndroidDynamicColors.*` anywhere Android accepts a color value, including:
+
+- `style.backgroundColor`
+- `style.color`
+- `Image.tintColor`
+- button `backgroundColor` and `contentColor`
+- `TitleBar.textColor` and `TitleBar.iconColor`
+- switch, checkbox, and radio button colors
+- progress indicator colors
+- chart mark colors
+
+## Server-driven widgets
+
+Dynamic color tokens work in server-rendered Android widgets too.
+
+```tsx
+import { AndroidDynamicColors, VoltraAndroid } from 'voltra/android'
+
+const content = (
+
+
+ Server-rendered widget
+
+
+)
+```
+
+The same `AndroidDynamicColors.*` values work whether the widget is rendered in-app or returned from your server.
+
+## Migration notes
+
+Voltra no longer uses the old Android dynamic palette snapshot approach.
+
+- Use `AndroidDynamicColors.*` for Android widgets that should react to system palette changes.
+- Keep using literal colors when you want a fixed color.
+- There is no `useAndroidDynamicColorPalette()` or `getAndroidDynamicColorPalette()` API anymore.
diff --git a/website/docs/v1/android/development/image-preloading.md b/website/docs/v1/android/development/image-preloading.md
new file mode 100644
index 00000000..7998acad
--- /dev/null
+++ b/website/docs/v1/android/development/image-preloading.md
@@ -0,0 +1,96 @@
+# Image Preloading (Android)
+
+Android widgets have limitations when it comes to displaying remote images directly. The image preloading API allows you to download images to the app's cache directory, making them available to your widgets via a local `FileProvider`.
+
+## Overview
+
+The image preloading system on Android works by:
+
+1. Downloading images from URLs to the internal app cache.
+2. Making these images available to Voltra widgets via the `assetName` property.
+3. Providing APIs to reload widgets when new images are ready.
+
+## API Reference
+
+### `preloadImages(images: PreloadImageOptions[]): Promise`
+
+Downloads images to the Android cache for use in Widgets.
+
+```typescript
+type PreloadImageOptions = {
+ url: string // The URL to download the image from
+ key: string // The assetName to use when referencing this image
+ method?: 'GET' | 'POST' | 'PUT' // HTTP method (default: 'GET')
+ headers?: Record // Optional HTTP headers
+}
+
+type PreloadImagesResult = {
+ succeeded: string[] // Keys of successfully downloaded images
+ failed: { key: string; error: string }[] // Failed downloads with error messages
+}
+```
+
+**Example:**
+
+```typescript
+import { preloadImages } from 'voltra/android'
+
+const result = await preloadImages([
+ {
+ url: 'https://example.com/album-art.jpg',
+ key: 'current-album',
+ headers: { Authorization: 'Bearer token' },
+ },
+])
+
+if (result.succeeded.includes('current-album')) {
+ // Images are ready to be used in widgets
+}
+```
+
+### `reloadWidgets(widgetIds?: string[]): Promise`
+
+Reloads Android widgets to pick up newly preloaded images. If no `widgetIds` are provided, all active widgets will be reloaded.
+
+```typescript
+import { reloadWidgets } from 'voltra/android'
+
+// Reload all widgets
+await reloadWidgets()
+
+// Reload specific widgets
+await reloadWidgets(['weather_widget'])
+```
+
+### `clearPreloadedImages(keys?: string[]): Promise`
+
+Removes preloaded images from the Android cache. If no `keys` are provided, all preloaded images will be cleared.
+
+```typescript
+import { clearPreloadedImages } from 'voltra/android'
+
+// Clear specific images
+await clearPreloadedImages(['current-album'])
+
+// Clear all preloaded images
+await clearPreloadedImages()
+```
+
+## Usage in Android Widgets
+
+Once images are preloaded, reference them using the `assetName` property in the `VoltraAndroid.Image` component:
+
+```tsx
+import { VoltraAndroid } from 'voltra'
+
+function MusicWidget({ albumKey }) {
+ return (
+
+
+
+ )
+}
+```
diff --git a/website/docs/v1/android/development/images.md b/website/docs/v1/android/development/images.md
new file mode 100644
index 00000000..e4539db4
--- /dev/null
+++ b/website/docs/v1/android/development/images.md
@@ -0,0 +1,122 @@
+# Images
+
+Voltra provides three different approaches for including images in your Android widgets, each with different trade-offs and use cases:
+
+- **Build-time asset copying**: Best for static icons and assets known at build time
+- **Runtime preloading**: Best for dynamic images from remote URLs
+- **Base64 encoding**: Best for small, generated images
+
+## Build-time asset copying
+
+Place images in the `/assets/voltra-android/` directory and they'll be automatically processed and copied to the Android drawable resources during build.
+
+```
+project-root/
+├── assets/
+│ └── voltra-android/
+│ ├── logo.png
+│ ├── weather/
+│ │ ├── sunny.svg
+│ │ └── rainy.xml
+│ └── background.webp
+```
+
+Here's how build-time asset copying works:
+
+1. Images in `/assets/voltra-android/` are automatically detected during build (run `npx expo prebuild` to apply changes).
+2. Filenames are sanitized to be compatible with Android resource naming rules (lowercase, underscores only).
+3. SVGs are automatically converted to Android Vector Drawables (XML).
+4. Images are copied to `res/drawable/` in the native Android project.
+
+### Naming & Sanitization
+
+Android drawable resources have strict naming conventions. Voltra automatically handles this for you:
+
+- **Sanitization:** Uppercase letters are converted to lowercase. Hyphens and other special characters are replaced with underscores.
+- **Flattening:** Subdirectories are flattened into the resource name to avoid conflicts.
+
+**Examples:**
+
+| Source File | Android Resource Name |
+| :--- | :--- |
+| `assets/voltra-android/Logo.png` | `logo` |
+| `assets/voltra-android/icons/My-Icon.png` | `icons_my_icon` |
+| `assets/voltra-android/weather/sunny.svg` | `weather_sunny` |
+
+### Supported Formats
+
+- **Bitmap:** `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`
+- **Vector:** `.svg` (converted to Vector Drawable), `.xml` (native Vector Drawable)
+
+### Usage
+
+Reference these images using their sanitized name in the `assetName` property. You do not need to include the file extension.
+
+```tsx
+import { VoltraAndroid } from 'voltra'
+
+// assets/voltra-android/logo.png -> "logo"
+
+
+// assets/voltra-android/weather/sunny.svg -> "weather_sunny"
+
+```
+
+## Runtime preloading
+
+For dynamic images from remote URLs, use Voltra's image preloading API to cache images locally.
+
+The image preloading system works by:
+
+1. Downloading images from URLs to the app's internal cache.
+2. Making images available to widgets via a local content provider.
+3. Providing APIs to reload widgets when new images are ready.
+
+Once images are preloaded, reference them using the key you provided:
+
+```tsx
+import { VoltraAndroid } from 'voltra'
+
+function ProfileWidget({ user }) {
+ return (
+
+
+
+ {user.name}
+
+
+ )
+}
+```
+
+For detailed API documentation, see [Image Preloading](./image-preloading).
+
+## Base64 encoding
+
+You can also embed images directly as base64-encoded strings. This is useful for small, generated images or when you want to avoid file management for very simple assets.
+
+```tsx
+
+```
+
+## Comparison table
+
+| Approach | When Known | Dynamic | Setup Required | Performance |
+| :--- | :--- | :--- | :--- | :--- |
+| **Build-time** | Build time | No | File placement | Best (Native Resource) |
+| **Preloading** | Runtime | Yes | Preload API call | Good (Cached File) |
+| **Base64** | Runtime/Build | No | None | Fair (Memory intensive if large) |
diff --git a/website/docs/v1/android/development/managing-ongoing-notifications.md b/website/docs/v1/android/development/managing-ongoing-notifications.md
new file mode 100644
index 00000000..06dc5482
--- /dev/null
+++ b/website/docs/v1/android/development/managing-ongoing-notifications.md
@@ -0,0 +1,524 @@
+# Managing Android Ongoing Notifications
+
+:::warning Experimental API
+Android ongoing notifications are **experimental**. The API may change in future releases.
+:::
+
+Voltra supports Android ongoing notifications for local, app-driven status updates such as deliveries, rides, workouts, or timers.
+
+Use this API when you want to:
+
+- start a persistent notification from your app
+- update its content over time
+- stop it when the task ends
+- add action buttons that open deep links in your app
+
+Voltra also supports remote updates if your app receives push notifications in the background and forwards the payload to the ongoing notification APIs.
+
+## Server-side rendering support
+
+Voltra already provides a server-side API for converting JSX into the semantic payload used by Android ongoing notifications.
+
+Use these APIs only in server-side or backend code. Do not import them from your React Native app runtime.
+
+Use `voltra/android/server`.
+
+The main renderer APIs are:
+
+- `renderAndroidOngoingNotificationPayloadToJson()` returns an object
+- `renderAndroidOngoingNotificationPayload()` returns a JSON string
+
+This API only renders the payload. Your server still needs to send that payload through your push provider, and your app still needs a background task that calls `upsertAndroidOngoingNotification()` or `stopAndroidOngoingNotification()` when the push arrives.
+
+## Before you start
+
+### 1. Enable notification manifest support
+
+Add `android.enableNotifications` to the Voltra Expo plugin config:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "android": {
+ "enableNotifications": true
+ }
+ }
+ ]
+ ]
+ }
+}
+```
+
+This adds the Android manifest entries required by Voltra's notification features.
+
+See [Plugin Configuration](../api/plugin-configuration#androidenablenotifications-optional) for details.
+
+### 2. Create a notification channel
+
+`channelId` is required when starting an ongoing notification, and the channel must already exist.
+
+If you use `expo-notifications`, you can create a channel like this:
+
+```tsx
+import * as Notifications from 'expo-notifications'
+
+await Notifications.setNotificationChannelAsync('delivery_updates', {
+ name: 'Delivery updates',
+ importance: Notifications.AndroidImportance.DEFAULT,
+})
+```
+
+### 3. Request notification permission on Android 13+
+
+On Android 13 and above, posting notifications requires runtime permission.
+
+```tsx
+import {
+ hasAndroidNotificationPermission,
+ requestAndroidNotificationPermission,
+} from 'voltra/android/client'
+
+const granted =
+ (await hasAndroidNotificationPermission()) || (await requestAndroidNotificationPermission())
+
+if (!granted) {
+ // Show your own UI explaining why notifications are needed.
+}
+```
+
+### 4. If you want remote updates, register a background notification task
+
+The playground app uses `expo-notifications` together with `expo-task-manager` to process real push notifications and update ongoing notifications in the background.
+
+Register a background task early in app startup:
+
+```tsx
+import * as Notifications from 'expo-notifications'
+import * as TaskManager from 'expo-task-manager'
+
+const TASK_NAME = 'voltra-ongoing-notification-task'
+
+TaskManager.defineTask(TASK_NAME, async ({ data, error }) => {
+ if (error) {
+ return
+ }
+
+ // Read your push payload and call Voltra APIs here.
+})
+
+await Notifications.registerTaskAsync(TASK_NAME)
+```
+
+The example app does this during startup so that incoming pushes can update or stop an ongoing notification even when the app is backgrounded.
+
+## Starting a notification
+
+Voltra provides two built-in layouts:
+
+- `AndroidOngoingNotification.Progress`
+- `AndroidOngoingNotification.BigText`
+
+### Progress notification
+
+```tsx
+import {
+ AndroidOngoingNotification,
+ startAndroidOngoingNotification,
+} from 'voltra/android/client'
+
+const result = await startAndroidOngoingNotification(
+ ,
+ {
+ notificationId: 'order-123',
+ channelId: 'delivery_updates',
+ deepLinkUrl: 'myapp://orders/123',
+ }
+)
+
+if (result.ok) {
+ console.log('Started:', result.notificationId)
+}
+```
+
+### Big text notification
+
+```tsx
+import {
+ AndroidOngoingNotification,
+ startAndroidOngoingNotification,
+} from 'voltra/android/client'
+
+await startAndroidOngoingNotification(
+ ,
+ {
+ notificationId: 'match-42',
+ channelId: 'sports_updates',
+ }
+)
+```
+
+## Updating a notification
+
+Use the same `notificationId` to update an existing notification.
+
+```tsx
+import {
+ AndroidOngoingNotification,
+ updateAndroidOngoingNotification,
+} from 'voltra/android/client'
+
+await updateAndroidOngoingNotification(
+ 'order-123',
+
+)
+```
+
+`updateAndroidOngoingNotification()` returns a result object. If the notification no longer exists, it returns `reason: 'not_found'` or `reason: 'dismissed'`.
+
+## Starting or updating with one call
+
+If your app may re-enter the same flow multiple times, `upsertAndroidOngoingNotification()` can be easier than separate start/update logic.
+
+```tsx
+import {
+ AndroidOngoingNotification,
+ upsertAndroidOngoingNotification,
+} from 'voltra/android/client'
+
+const result = await upsertAndroidOngoingNotification(
+ ,
+ {
+ notificationId: 'workout-1',
+ channelId: 'fitness_updates',
+ }
+)
+
+if (result.ok) {
+ console.log(result.action) // 'started' or 'updated'
+}
+```
+
+This API is especially useful for remote updates, where the same incoming push may need to create the notification the first time and update it later.
+
+## Stopping a notification
+
+```tsx
+import { stopAndroidOngoingNotification } from 'voltra/android/client'
+
+await stopAndroidOngoingNotification('order-123')
+```
+
+To dismiss every active Voltra ongoing notification at once:
+
+```tsx
+import { endAllAndroidOngoingNotifications } from 'voltra/android/client'
+
+await endAllAndroidOngoingNotifications()
+```
+
+## Hook API
+
+For React screens and flows, use `useAndroidOngoingNotification()`.
+
+```tsx
+import { AndroidOngoingNotification } from 'voltra/android'
+import { useAndroidOngoingNotification } from 'voltra/android/client'
+
+function DeliveryNotification({ orderId, etaMinutes }) {
+ const { start, update, end, isActive } = useAndroidOngoingNotification(
+ ,
+ {
+ notificationId: `order-${orderId}`,
+ channelId: 'delivery_updates',
+ deepLinkUrl: `myapp://orders/${orderId}`,
+ autoStart: true,
+ autoUpdate: true,
+ }
+ )
+
+ return null
+}
+```
+
+The hook returns:
+
+- `start()`
+- `update()`
+- `end()`
+- `isActive`
+
+Use `autoStart` to create the notification when the component mounts, and `autoUpdate` to refresh it when the JSX content changes.
+
+## Action buttons
+
+You can add action buttons as children of `Progress` or `BigText`.
+
+```tsx
+import { AndroidOngoingNotification } from 'voltra/android/client'
+
+
+
+
+
+```
+
+Action buttons currently:
+
+- open the provided deep link
+- can be used with `Progress` and `BigText`
+- support an optional `icon`
+
+```tsx
+
+```
+
+Android may not show action icons in the standard notification UI, so treat them as optional enhancement rather than a guaranteed visual element.
+
+## Remote updates
+
+Voltra can apply remote ongoing-notification updates if your app receives a push notification and handles it in a background task.
+
+The end-to-end flow is:
+
+1. Your server renders Voltra JSX into an Android ongoing-notification payload.
+2. Your server sends a high-priority push notification.
+3. The push `data` contains a `voltraOngoingNotification` object.
+4. Your background task parses that object.
+5. The task calls `upsertAndroidOngoingNotification()` or `stopAndroidOngoingNotification()`.
+
+### 1. Render the payload on your server
+
+Use `renderAndroidOngoingNotificationPayloadToJson()` when preparing a payload on your server or in app tooling:
+
+```tsx
+import {
+ AndroidOngoingNotification,
+ renderAndroidOngoingNotificationPayloadToJson,
+} from 'voltra/android/server'
+
+const payload = renderAndroidOngoingNotificationPayloadToJson(
+
+
+
+)
+```
+
+Then send that payload inside a push message.
+
+If your push provider expects strings for nested payload data, use `renderAndroidOngoingNotificationPayload()` instead and send the JSON string directly.
+
+### 2. Send the payload through your push provider
+
+The playground app expects `data.voltraOngoingNotification` to contain:
+
+- `notificationId`: the stable notification identifier
+- `operation`: `'upsert'` or `'stop'`
+- `options`: start options such as `channelId`, `smallIcon`, `deepLinkUrl`, `requestPromotedOngoing`, or `fallbackBehavior`
+- `payload`: the Voltra semantic payload for `'upsert'`
+
+Example Expo push request:
+
+```json
+{
+ "to": "ExponentPushToken[project-token]",
+ "priority": "high",
+ "data": {
+ "voltraOngoingNotification": "{\"notificationId\":\"order-123\",\"operation\":\"upsert\",\"options\":{\"channelId\":\"delivery_updates\",\"deepLinkUrl\":\"myapp://orders/123\",\"requestPromotedOngoing\":true},\"payload\":{\"v\":1,\"kind\":\"progress\",\"title\":\"Driver is approaching\",\"text\":\"2 stops away\",\"value\":80,\"max\":100}}"
+ }
+}
+```
+
+The playground app accepts either an object or a JSON string for `data.voltraOngoingNotification`. Stringifying it is often the safest option when sending through push providers.
+
+To stop the notification remotely, send the same `notificationId` with `operation: "stop"` and omit `payload`.
+
+### 3. Apply the payload in your background task
+
+```tsx
+import * as Notifications from 'expo-notifications'
+import * as TaskManager from 'expo-task-manager'
+import {
+ stopAndroidOngoingNotification,
+ upsertAndroidOngoingNotification,
+} from 'voltra/android/client'
+
+const TASK_NAME = 'voltra-ongoing-notification-task'
+
+const parseMessage = (value: unknown) => {
+ if (typeof value === 'string') {
+ try {
+ return JSON.parse(value)
+ } catch {
+ return null
+ }
+ }
+
+ return value
+}
+
+TaskManager.defineTask(TASK_NAME, async ({ data, error }) => {
+ if (error) {
+ return
+ }
+
+ const message = parseMessage(data?.voltraOngoingNotification)
+ if (!message || typeof message !== 'object') {
+ return
+ }
+
+ const notificationId = typeof message.notificationId === 'string' ? message.notificationId : null
+ if (!notificationId) {
+ return
+ }
+
+ if (message.operation === 'stop') {
+ await stopAndroidOngoingNotification(notificationId)
+ return
+ }
+
+ if (!message.payload || !message.options?.channelId) {
+ return
+ }
+
+ await upsertAndroidOngoingNotification(message.payload, {
+ ...message.options,
+ notificationId,
+ })
+})
+
+await Notifications.registerTaskAsync(TASK_NAME)
+```
+
+### Channel setup for remote updates
+
+Your background task should ensure that the target notification channel exists before calling `upsertAndroidOngoingNotification()`. The playground app creates the channel on startup and also ensures it exists again inside the background handler.
+
+### Important notes
+
+- Voltra does include a server-side JSX-to-payload renderer for Android ongoing notifications.
+- Remote updates depend on your push provider and app-level background notification setup.
+- Voltra provides the ongoing-notification rendering and lifecycle APIs, but your app is responsible for receiving the push and invoking those APIs.
+- `upsertAndroidOngoingNotification()` is the easiest entry point for remote updates because it can create or update the notification with the same payload path.
+- If your push provider serializes nested objects as strings, parse `data.voltraOngoingNotification` before passing it to Voltra.
+
+## Main tap behavior
+
+Use `deepLinkUrl` in the start or update options to control what happens when the user taps the main notification body:
+
+```tsx
+await startAndroidOngoingNotification(content, {
+ notificationId: 'order-123',
+ channelId: 'delivery_updates',
+ deepLinkUrl: 'myapp://orders/123',
+})
+```
+
+This is separate from action button deep links.
+
+## Status and capability helpers
+
+Use these helpers to adapt your UI to the device state:
+
+```tsx
+import {
+ canPostPromotedAndroidNotifications,
+ getAndroidOngoingNotificationCapabilities,
+ getAndroidOngoingNotificationStatus,
+ openAndroidNotificationSettings,
+} from 'voltra/android/client'
+
+const status = getAndroidOngoingNotificationStatus('order-123')
+const capabilities = getAndroidOngoingNotificationCapabilities()
+const canPostPromoted = canPostPromotedAndroidNotifications()
+
+if (!capabilities.notificationsEnabled) {
+ await openAndroidNotificationSettings()
+}
+```
+
+Useful values include:
+
+- `status.isActive`
+- `status.isDismissed`
+- `capabilities.notificationsEnabled`
+- `capabilities.supportsPromotedNotifications`
+- `capabilities.canPostPromotedNotifications`
+- `capabilities.canRequestPromotedOngoing`
+
+## Promoted ongoing notifications
+
+If your app wants to request promoted ongoing presentation when the device supports it, pass `requestPromotedOngoing: true`:
+
+```tsx
+await startAndroidOngoingNotification(content, {
+ notificationId: 'ride-44',
+ channelId: 'ride_updates',
+ requestPromotedOngoing: true,
+})
+```
+
+You can also set `fallbackBehavior` if promoted presentation is unavailable:
+
+```tsx
+await startAndroidOngoingNotification(content, {
+ notificationId: 'ride-44',
+ channelId: 'ride_updates',
+ requestPromotedOngoing: true,
+ fallbackBehavior: 'standard',
+})
+```
+
+Check device support first with `getAndroidOngoingNotificationCapabilities()` if you want to tailor the UX.
+
+## Current limitations
+
+- Remote updates require your own push delivery and background task integration.
+- Your app must create the Android notification channel before starting a notification.
+- Notification permission still needs to be requested by your app on Android 13+.
+- Action buttons open deep links. They are not a JavaScript event system.
diff --git a/website/docs/v1/android/development/querying-active-widgets.md b/website/docs/v1/android/development/querying-active-widgets.md
new file mode 100644
index 00000000..624c19f9
--- /dev/null
+++ b/website/docs/v1/android/development/querying-active-widgets.md
@@ -0,0 +1,36 @@
+# Querying Active Widgets
+
+On Android, you can detect every active instance of your widgets currently placed on the Home Screen. This is particularly useful for Android since each widget instance can have different dimensions and a unique `widgetId`.
+
+## getActiveWidgets API
+
+The `getActiveWidgets` function returns a promise that resolves to an array of all active widget instances for your app.
+
+```typescript
+import { getActiveWidgets } from 'voltra/android'
+
+async function checkAndroidWidgets() {
+ const activeWidgets = await getActiveWidgets()
+
+ console.log(`Found ${activeWidgets.length} active widget instances`)
+
+ activeWidgets.forEach(widget => {
+ console.log(`- Widget Name: ${widget.name}`)
+ console.log(` ID: ${widget.widgetId}`)
+ console.log(` Size: ${widget.width}x${widget.height}dp`)
+ })
+}
+```
+
+### WidgetInfo Object
+
+Each object in the returned array contains:
+
+| Property | Type | Description |
+| :--- | :--- | :--- |
+| `name` | `string` | The unique ID of the widget as defined in your Expo config plugin (e.g., `"weather"`). |
+| `widgetId` | `number` | The unique system identifier for this specific widget instance. |
+| `providerClassName` | `string` | The full class name of the widget provider (e.g., `".widget.VoltraWidget_weatherReceiver"`). |
+| `label` | `string` | The human-readable label shown in the Android widget picker. |
+| `width` | `number` | The current width of the widget instance in dp. |
+| `height` | `number` | The current height of the widget instance in dp. |
diff --git a/website/docs/v1/android/development/server-driven-widgets.md b/website/docs/v1/android/development/server-driven-widgets.md
new file mode 100644
index 00000000..9740c2d9
--- /dev/null
+++ b/website/docs/v1/android/development/server-driven-widgets.md
@@ -0,0 +1,289 @@
+# Server-driven widgets
+
+Server-driven widgets allow your Android Home Screen widgets to periodically fetch fresh content from a remote server—without the user opening the app. This is powered by WorkManager, which handles scheduling, retries, and network constraints automatically.
+
+Before you start, make sure the widget is registered in the Voltra plugin config and plan to rebuild the native app after adding or changing server-driven widget settings.
+
+Android semantic color tokens from [`AndroidDynamicColors`](./dynamic-colors) work in server-rendered widgets too, so your backend can return dynamic Material roles instead of fixed hex values.
+
+## How it works
+
+1. You configure a `serverUpdate` URL in your Android widget's plugin config
+2. WorkManager runs a periodic background task at the configured interval
+3. Your server renders Voltra JSX components into a JSON payload
+4. The worker parses the payload and pushes a `RemoteViews` update to the widget
+
+Your app doesn't need to be running. WorkManager handles everything in the background.
+
+## Plugin configuration
+
+Add the `serverUpdate` option to your Android widget in `app.json` or `app.config.js`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "android": {
+ "widgets": [
+ {
+ "id": "dynamic_weather",
+ "displayName": "Dynamic Weather",
+ "description": "Weather with live server updates",
+ "targetCellWidth": 2,
+ "targetCellHeight": 1,
+ "serverUpdate": {
+ "url": "https://api.example.com/widgets/render",
+ "intervalMinutes": 60
+ }
+ }
+ ]
+ }
+ }
+ ]
+ ]
+ }
+}
+```
+
+**`serverUpdate` options:**
+
+- `url`: The Voltra SSR endpoint that returns widget JSON. Voltra appends `widgetId`, `platform`, and `theme` query parameters automatically (e.g. `?widgetId=dynamic_weather&platform=android&theme=dark`).
+- `intervalMinutes`: How often the widget fetches updates. Defaults to `15`. The minimum effective interval is 15 minutes (WorkManager requirement).
+- `refresh`: Whether to show a native refresh button in the top-right corner of the widget. When tapped, triggers an immediate server fetch. Defaults to `false`.
+
+After updating plugin configuration, run `npx expo prebuild` if you're using Continuous Native Generation, then rebuild the app so the generated native widget code picks up the new server update settings.
+
+:::note
+On the Android emulator, use `10.0.2.2` instead of `localhost` to reach the host machine. Real devices need the host's LAN IP address.
+:::
+
+## Building the server
+
+Voltra provides widget server handlers for the common runtime styles. Use `createWidgetUpdateHandler()` for Fetch-compatible runtimes, `createWidgetUpdateNodeHandler()` for `node:http`, and `createWidgetUpdateExpressHandler()` for Express-style handlers. All three share the same request parsing, platform validation, token validation, and response serialization.
+
+```tsx
+import { createServer } from 'node:http'
+import React from 'react'
+import { createWidgetUpdateNodeHandler } from 'voltra/server'
+import { AndroidDynamicColors, VoltraAndroid } from 'voltra/android'
+
+const handler = createWidgetUpdateNodeHandler({
+ renderAndroid: async (req) => {
+ // req.widgetId — the widget requesting an update
+ // req.platform — always "android" for Android widget requests
+ // req.theme — the system color scheme ("light" or "dark")
+ // req.token — the auth token (if credentials were set)
+
+ const weather = await fetchWeatherData()
+
+ const content = (
+
+
+
+ {weather.temp}°
+
+
+ {weather.condition}
+
+
+
+ )
+
+ // Return size breakpoints for different widget sizes
+ return [
+ { size: { width: 200, height: 100 }, content },
+ { size: { width: 200, height: 200 }, content },
+ { size: { width: 300, height: 200 }, content },
+ ]
+ },
+
+ // Also handle iOS requests from the same endpoint
+ renderIos: async (req) => {
+ // ...
+ return null // or return iOS variants
+ },
+
+ validateToken: async (token) => {
+ return token === 'valid-token'
+ },
+})
+
+createServer(handler).listen(3333)
+```
+
+The handler responds to GET requests with these query parameters:
+
+| Parameter | Description |
+|-----------|-------------|
+| `widgetId` | The widget identifier (required) |
+| `platform` | The requesting platform. Must be `android` or `ios` (required). |
+| `family` | The widget family/size (iOS only — absent for Android) |
+| `theme` | The system color scheme (`light` or `dark`) |
+
+The `User-Agent` header is set to `VoltraWidget/ (Android/)`.
+
+## Authentication
+
+Widgets on Android are part of the main app binary, so the WorkManager background worker can access credential storage directly. Voltra encrypts credentials at rest using **Google Tink** (AES-256-GCM with Android Keystore-backed key management) and persists them in Jetpack DataStore.
+
+### Setting credentials
+
+Call `setWidgetServerCredentials` after the user logs in:
+
+```typescript
+import { setWidgetServerCredentials } from 'voltra/client'
+
+await setWidgetServerCredentials({
+ token: userAccessToken,
+ headers: {
+ 'X-App-Version': '1.0.0',
+ },
+})
+```
+
+The `token` is required and is sent as `Authorization: Bearer ` on every server request. Any additional `headers` are also included. If your widget endpoint does not require authentication, skip `setWidgetServerCredentials()` entirely.
+
+### Clearing credentials
+
+Call `clearWidgetServerCredentials` when the user logs out:
+
+```typescript
+import { clearWidgetServerCredentials } from 'voltra/client'
+
+await clearWidgetServerCredentials()
+```
+
+All widgets are automatically reloaded after credentials are cleared, so they revert to their default/unauthenticated state immediately.
+
+## Refresh button
+
+Server-driven widgets can display a native refresh button that lets users trigger an immediate update on demand. Enable it in your widget config:
+
+```json
+{
+ "serverUpdate": {
+ "url": "https://api.example.com/widgets/render",
+ "intervalMinutes": 60,
+ "refresh": true
+ }
+}
+```
+
+When enabled, a small circular button (↻) appears in the top-right corner of the widget. Tapping it performs an inline HTTP fetch, generates new `RemoteViews`, and pushes the update directly to the widget—all without waiting for the next WorkManager cycle.
+
+:::note
+The refresh callback bypasses Glance's `update()` method (which doesn't reliably trigger `provideGlance()`) and instead uses `GlanceRemoteViews.compose()` to generate `RemoteViews` that are pushed directly via `AppWidgetManager.updateAppWidget()`.
+:::
+
+## Resize handling
+
+Your server should return all size variants in every response. When the user resizes a widget on the home screen, Voltra re-renders from cached data—no network request is made. The `RemoteViews(sizeMapping)` mechanism automatically picks the closest matching variant.
+
+## Triggering manual refreshes
+
+You can force-refresh server-driven widgets outside of the regular interval:
+
+```typescript
+import { reloadWidgets } from 'voltra/client'
+
+// Reload specific widgets (triggers an immediate WorkManager fetch)
+await reloadWidgets(['dynamic_weather'])
+
+// Reload all widgets
+await reloadWidgets()
+```
+
+For server-driven widgets, this enqueues an immediate one-time WorkManager request to fetch fresh content. For local-only widgets, it re-renders from cached data.
+
+## Initial state
+
+Server-driven widgets still need content to display before the first server fetch completes. Use `initialStatePath` to provide a pre-rendered default:
+
+```json
+{
+ "id": "dynamic_weather",
+ "displayName": "Dynamic Weather",
+ "description": "Weather with live server updates",
+ "targetCellWidth": 2,
+ "targetCellHeight": 1,
+ "initialStatePath": "./widgets/android/weather-initial.tsx",
+ "serverUpdate": {
+ "url": "https://api.example.com/widgets/render",
+ "intervalMinutes": 60
+ }
+}
+```
+
+See [Widget pre-rendering](./widget-pre-rendering) for details on creating initial state files.
+
+:::tip
+Provide a meaningful initial state (e.g. "Loading..." or placeholder content) rather than leaving it empty. The user sees this until the first server fetch succeeds.
+:::
+
+## Cross-platform server
+
+A single server can handle both iOS and Android requests using `createWidgetUpdateHandler`:
+
+```tsx
+const handler = createWidgetUpdateHandler({
+ renderIos: async (req) => {
+ // Return WidgetVariants (systemSmall, systemMedium, etc.)
+ return { systemSmall: Hello }
+ },
+ renderAndroid: async (req) => {
+ // Return AndroidWidgetVariants (size breakpoints)
+ return [{ size: { width: 200, height: 100 }, content: Hello }]
+ },
+ validateToken: async (token) => {
+ // Shared token validation for both platforms
+ return verifyJwt(token)
+ },
+})
+```
+
+The handler uses the required `platform` query parameter to route requests to the correct render function.
+
+If you're serving the endpoint from Node or Express, use `createWidgetUpdateNodeHandler()` or `createWidgetUpdateExpressHandler()` instead.
+
+## Architecture overview
+
+```
+┌─────────────────┐ setWidgetServerCredentials() ┌─────────────────────────┐
+│ React Native │ ─────────────────────────────► │ EncryptedSharedPrefs │
+│ (main app) │ └─────────────────────────┘
+└─────────────────┘ │
+ │ reads token
+ ▼
+┌─────────────────┐ GET ?widgetId=X&platform=android&theme=Y ┌──────────────────┐
+│ WorkManager │ ─────────────────────────────► │ Your Server │
+│ (background) │ ◄───────────────────────────── │ (Voltra SSR) │
+└─────────────────┘ JSON payload └──────────────────┘
+ │
+ ▼
+ AppWidgetManager
+ (RemoteViews update)
+ │
+ ▼
+ Home Screen Widget
+```
+
+WorkManager handles scheduling, network constraints, and retries. The background worker reads credentials from encrypted storage, makes the HTTP request, parses the response, generates `RemoteViews`, and pushes the update via `AppWidgetManager`.
+
+## Error handling and retries
+
+WorkManager automatically handles failures with exponential backoff. After 5 consecutive failed attempts, the worker gives up to avoid infinite retry loops. The next periodic run will start fresh.
+
+- **Network unavailable:** The request is deferred until connectivity is restored (via `NetworkType.CONNECTED` constraint).
+- **Server errors (non-2xx):** The worker retries with exponential backoff, up to 3 attempts.
+- **Empty response:** The worker retries with exponential backoff, up to 3 attempts.
+- **Parse errors:** If the JSON is stored but parsing fails, the data is still saved so Glance can attempt to use it later. This counts as a success since the data is persisted.
diff --git a/website/docs/v1/android/development/styling.md b/website/docs/v1/android/development/styling.md
new file mode 100644
index 00000000..adb04052
--- /dev/null
+++ b/website/docs/v1/android/development/styling.md
@@ -0,0 +1,112 @@
+# Styling
+
+You can style Voltra components on Android using React Native-style `style` props. These properties are automatically converted to Jetpack Compose Glance modifiers.
+
+For Android system-aware colors, use [`AndroidDynamicColors`](./dynamic-colors) from `voltra/android` instead of snapshotting palette values in JavaScript.
+
+:::warning Glance Limitations
+Android widgets are built using **Jetpack Compose Glance**, which has a significantly more limited styling API compared to standard Compose or SwiftUI. Many common React Native style properties are either not supported or have limited support.
+:::
+
+## Supported Properties
+
+The following React Native style properties are supported on Android:
+
+### Layout
+
+- `width`, `height` - Fixed dimensions (number values in dp) or `"100%"` to fill available space.
+- `flex`, `flexGrow` - Flex weight. When > 0, the component will take up a proportional amount of space in its parent container (maps to `.defaultWeight()` in Glance).
+- `padding` - Uniform padding on all edges.
+- `paddingTop`, `paddingBottom`, `paddingLeft`, `paddingRight` - Individual edge padding.
+- `paddingHorizontal`, `paddingVertical` - Horizontal and vertical padding.
+- `visibility` - Controls component visibility (`"visible"`, `"hidden"`, or `"invisible"`).
+
+### Visual Style
+
+- `backgroundColor` - Background color (hex strings, color names, or `AndroidDynamicColors.*` tokens).
+- `borderRadius` - Corner radius value. **Note:** Requires Android 12+ (API 31). On older versions, this property is ignored.
+
+### Text
+
+- `fontSize` - Font size in sp.
+- `fontWeight` - Supports `"normal"` and `"bold"`.
+- `fontFamily` - Font family name. Built-in values: `"monospace"`, `"serif"`, `"sans-serif"`, `"cursive"`. For custom fonts, see [Custom Fonts](./custom-fonts).
+- `color` - Text color (literal colors or `AndroidDynamicColors.*`).
+- `textDecorationLine` - Supports `"underline"` and `"line-through"`.
+- `textAlign` - Alignment of text within the component (`"left"`, `"center"`, `"right"`).
+- `numberOfLines` - Limits the number of lines displayed.
+
+### Image Specific
+
+In addition to general styles, `Image` components support:
+
+- `resizeMode` (or `contentScale`) - `"cover"`, `"contain"`, `"stretch"`, or `"center"`.
+- `alpha` - Opacity of the image (0.0 to 1.0).
+- `tintColor` - Applies a color filter to the image.
+
+## Dynamic colors
+
+Android widgets can use semantic Material color roles that resolve through native `GlanceTheme.colors.*` values during rendering.
+
+```tsx
+import { AndroidDynamicColors, VoltraAndroid } from 'voltra/android'
+
+const element = (
+
+
+ Android Widget Text
+
+
+)
+```
+
+This is the preferred approach when you want widgets to follow Android's dynamic palette even when the app is not running. See [Dynamic Colors](./dynamic-colors) for the full role list and server-rendering behavior.
+
+## Limitations
+
+The following properties are **NOT supported** on Android due to Glance limitations:
+
+- **Margins:** `margin`, `marginTop`, etc. are currently ignored. Use `padding` on parent containers or `Spacer` components instead.
+- **Borders:** `borderWidth` and `borderColor` are not yet implemented.
+- **Shadows:** `shadowColor`, `shadowOffset`, `shadowOpacity`, and `shadowRadius` are not supported.
+- **Positioning:** Absolute positioning (`top`, `left`, `zIndex`) is not supported. Use stack alignments and spacers.
+- **Transforms:** `transform` (rotate, scale, etc.) is not supported.
+- **Opacity:** The general `style.opacity` property is not supported (except for the `alpha` prop on `Image`).
+- **Dimensions:** `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, and `aspectRatio` are not supported.
+- **Text Effects:** `letterSpacing`, `fontVariant`, and custom `lineHeight` are not supported.
+
+## Example
+
+```tsx
+import { Voltra } from 'voltra'
+
+const element = (
+
+
+ Android Widget Text
+
+
+)
+```
diff --git a/website/docs/v1/android/development/testing-and-previews.md b/website/docs/v1/android/development/testing-and-previews.md
new file mode 100644
index 00000000..ad3dae44
--- /dev/null
+++ b/website/docs/v1/android/development/testing-and-previews.md
@@ -0,0 +1,77 @@
+# Testing and Previews (Android)
+
+Voltra provides multiple ways to preview your Android widgets:
+
+1. **In-App Previews** - Preview layouts within your development app using `VoltraWidgetPreview`
+2. **Widget Picker Previews** - Customize what users see in the Android widget picker when adding your widget
+
+This page covers in-app previews for development. For widget picker previews, see [Plugin Configuration - Widget Picker Previews](../api/plugin-configuration#widget-picker-previews).
+
+## VoltraWidgetPreview
+
+The `VoltraWidgetPreview` component renders Voltra Android JSX content at the exact dimensions of standard Android widget sizes.
+
+### Usage
+
+```tsx
+import { VoltraAndroid } from 'voltra/android'
+import { VoltraWidgetPreview } from 'voltra/android/client'
+
+export function MyWidgetPreview() {
+ return (
+
+
+
+ My Awesome Widget
+
+
+ This is how it looks on the home screen!
+
+
+
+ )
+}
+```
+
+### Supported Families
+
+Android widgets use responsive sizing. Voltra provides several standard families based on typical grid dimensions:
+
+| Family | DP Dimensions | Typical Grid Size |
+| :--- | :--- | :--- |
+| `small` | 150 x 100 | 2x1 |
+| `mediumSquare` | 200 x 200 | 2x2 |
+| `mediumWide` | 250 x 150 | 3x2 |
+| `mediumTall` | 150 x 250 | 2x3 |
+| `large` | 300 x 200 | 4x2 |
+| `extraLarge` | 350 x 300 | 4x4 |
+
+## VoltraView (Android)
+
+If you need more control or want to test custom dimensions, you can use the low-level `VoltraView` component.
+
+```tsx
+import { VoltraView } from 'voltra/android/client'
+
+
+
+ Custom Preview
+
+
+```
+
+## Accuracy
+
+The Android preview components use the **actual native Glance renderers** under the hood. When you provide JSX to `VoltraWidgetPreview`, it is converted to a native `RemoteViews` object and rendered using the same logic that Android uses on the home screen.
+
+This ensures that:
+- Layout constraints are respected.
+- Styling (colors, fonts, spacing) is accurate.
+- Component mapping is identical to the production widget.
+
+:::info
+While layout and styling are accurate, some home-screen specific behaviors (like actual widget resizing by the user) are not simulated by the preview component.
+:::
diff --git a/website/docs/v1/android/development/widget-pre-rendering.md b/website/docs/v1/android/development/widget-pre-rendering.md
new file mode 100644
index 00000000..f9b72617
--- /dev/null
+++ b/website/docs/v1/android/development/widget-pre-rendering.md
@@ -0,0 +1,58 @@
+# Widget Pre-rendering (Android)
+
+Widget pre-rendering allows you to provide a meaningful initial state for your Android widgets before they are updated by the app for the first time.
+
+## Configuration
+
+Add `initialStatePath` to your widget configuration in the `android` section of the Voltra plugin in `app.json`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "android": {
+ "widgets": [
+ {
+ "id": "weather",
+ "name": "Weather Widget",
+ "initialStatePath": "./widgets/weather-android-initial.tsx"
+ }
+ ]
+ }
+ }
+ ]
+ ]
+ }
+}
+```
+
+## Implementation
+
+Create a file at the specified `initialStatePath` that exports a default Voltra component (or a React element). For Android, this should use `VoltraAndroid` primitives.
+
+```tsx
+import { VoltraAndroid } from 'voltra'
+
+const InitialWeatherWidget = (
+
+
+ Loading weather...
+
+
+)
+
+export default InitialWeatherWidget
+```
+
+## Build Process
+
+During the build process (`npx expo prebuild`), Voltra executes these initial state files in a Node.js environment to generate the static layouts that will be displayed when the widget is first added to the home screen.
+
+## Limitations
+
+- **Environment**: The code runs in Node.js during build time, not on the device.
+- **Imports**: Ensure you only import from `voltra` and avoid any browser or React Native specific APIs that aren't supported in Node.js.
+- **Static Content**: The initial state should represent a "loading" or "offline" state, as it won't have access to dynamic runtime data until the app runs.
diff --git a/website/docs/v1/android/introduction.md b/website/docs/v1/android/introduction.md
new file mode 100644
index 00000000..7c1b496c
--- /dev/null
+++ b/website/docs/v1/android/introduction.md
@@ -0,0 +1,71 @@
+# Android Introduction
+
+:::warning Experimental Support
+Android support is **experimental**. Although it should work just fine, the API may change. Stay vigilant.
+:::
+
+Voltra brings the power of JSX-based UI to Android Home Screen widgets. Using Jetpack Compose Glance under the hood, Voltra allows you to define Android widgets using a set of primitives that map directly to Glance components.
+
+## Widgets on Android
+
+Android widgets have different layout and styling rules compared to iOS Live Activities. While iOS uses SwiftUI-based primitives (VStack, HStack, etc.), Android uses Jetpack Compose Glance primitives (Column, Row, Box).
+
+Voltra abstracts these differences where possible, but provides platform-specific namespaces to ensure your UI looks and behaves correctly on each platform.
+
+Voltra also exposes Android-specific semantic dynamic colors through `AndroidDynamicColors`, which lets widgets follow the current Material palette without requiring a JavaScript re-render. See [Dynamic Colors](./development/dynamic-colors).
+
+Voltra also supports Android ongoing notifications for app-driven, persistent status updates. See [Managing Android Ongoing Notifications](./development/managing-ongoing-notifications).
+
+### Simple Android Widget
+
+```tsx
+import { VoltraAndroid } from 'voltra'
+
+const MyWidget = () => (
+
+
+ Android Widget
+
+
+ Powered by Voltra & Glance
+
+
+)
+```
+
+## Key Differences
+
+- **Primitives:** Use `VoltraAndroid.Column`, `VoltraAndroid.Row`, and `VoltraAndroid.Box` instead of stacks.
+- **Alignment:** Android uses specific alignment props like `verticalAlignment` and `horizontalAlignment`.
+- **Sizing:** Use `"100%"` for full size or `"auto"` for wrapping content.
+
+## Testing and Previews
+
+You can preview your Android widgets directly in your app using the `VoltraWidgetPreview` component. This allows for fast iteration without needing to constantly check the home screen.
+
+Learn more in the [Testing and Previews guide](./development/testing-and-previews).
+
+## Next Steps
+
+Check out the [Setup guide](./setup) to set up Voltra for Android.
+
+For notification-based experiences, see [Managing Android Ongoing Notifications](./development/managing-ongoing-notifications).
diff --git a/website/docs/v1/android/setup.mdx b/website/docs/v1/android/setup.mdx
new file mode 100644
index 00000000..02904037
--- /dev/null
+++ b/website/docs/v1/android/setup.mdx
@@ -0,0 +1,53 @@
+import { PackageManagerTabs } from '@rspress/core/theme'
+
+# Android Setup
+
+Once you have [installed Voltra](../getting-started/installation), you need to configure the Expo plugin for Android.
+
+## 1. Configure the Expo Plugin
+
+Add the Voltra plugin to your `app.json` or `app.config.js`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "android": {
+ "widgets": [
+ {
+ "id": "my_widget",
+ "name": "My First Widget",
+ "description": "A simple Voltra widget"
+ }
+ ]
+ }
+ }
+ ]
+ ]
+ }
+}
+```
+
+## 2. Prebuild for Android
+
+Update your native Android project:
+
+
+
+## 3. Run the app
+
+```bash
+npx expo run:android
+```
+
+Your widget should now be available in the Android widget picker!
diff --git a/website/docs/v1/getting-started/_meta.json b/website/docs/v1/getting-started/_meta.json
new file mode 100644
index 00000000..d4204e28
--- /dev/null
+++ b/website/docs/v1/getting-started/_meta.json
@@ -0,0 +1,17 @@
+[
+ {
+ "type": "file",
+ "name": "introduction",
+ "label": "Introduction"
+ },
+ {
+ "type": "file",
+ "name": "installation",
+ "label": "Installation"
+ },
+ {
+ "type": "file",
+ "name": "prior-art",
+ "label": "Prior Art"
+ }
+]
diff --git a/website/docs/v1/getting-started/installation.mdx b/website/docs/v1/getting-started/installation.mdx
new file mode 100644
index 00000000..31f89e5e
--- /dev/null
+++ b/website/docs/v1/getting-started/installation.mdx
@@ -0,0 +1,18 @@
+import { PackageManagerTabs } from '@rspress/core/theme'
+
+# Installation
+
+To use Voltra in your project, you only need to install one package.
+
+## 1. Install the package
+
+Add Voltra to your Expo project:
+
+
+
+## 2. Next Steps
+
+After installing the package, you need to configure the Voltra Expo plugin for each platform you want to support.
+
+- [Configure iOS Setup](../ios/setup)
+- [Configure Android Setup](../android/setup)
diff --git a/website/docs/v1/getting-started/introduction.md b/website/docs/v1/getting-started/introduction.md
new file mode 100644
index 00000000..c9fe60f1
--- /dev/null
+++ b/website/docs/v1/getting-started/introduction.md
@@ -0,0 +1,65 @@
+# Introduction
+
+Voltra is a library that brings new "platforms" to React Native. Up until now, creating features like iOS Live Activities, Dynamic Island layouts, or Android Home Screen Widgets required writing native code in Swift or Kotlin.
+
+Voltra changes this by providing a JavaScript-based API and JSX components that get automatically converted to native primitives (SwiftUI on iOS, Jetpack Compose Glance on Android).
+
+## Why Voltra?
+
+- **React Native Everywhere:** Extend your React Native app with native platform features using the same JSX syntax you already know.
+- **No Native Code Required:** Build complex widget layouts and live activities without touching Xcode or Android Studio for UI code.
+- **Unified Components:** Use a shared set of components that render idiomatically on both iOS and Android.
+- **Real-time Updates:** Stream updates to your activities and widgets via push notifications (APNS/FCM) from any JavaScript runtime.
+
+## How it works
+
+Voltra works by serializing your JSX components into a lightweight JSON format that the native platform extensions can interpret. This enables features like hot reloading during development and server-side rendering for push updates.
+
+Here's how simple it is to create a live activity:
+
+```tsx
+import { startLiveActivity } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+const activityUI = (
+
+
+ Driver en route
+ Building A · Lobby pickup
+
+ Contact driver
+
+
+)
+
+// Start the live activity
+await startLiveActivity({
+ lockScreen: activityUI,
+})
+```
+
+If you prefer using the hook API (`useLiveActivity`), you'll get live reloads for live activities, with changes appearing in milliseconds without manual restarts.
+
+## Server-side updates via push notifications
+
+Voltra also supports server-side updates through push notifications. You can use Voltra's server-side rendering to convert JSX into JSON payloads that you send to devices via Apple's Push Notification Service (APNS) or Firebase Cloud Messaging (FCM). This enables real-time updates without keeping your app running.
+
+The same components you use in your app work on the server:
+
+```tsx
+import { renderLiveActivityToString } from 'voltra/server'
+import { Voltra } from 'voltra'
+
+// Render JSX to JSON payload on your server
+const payload = renderLiveActivityToString({
+ lockScreen: (
+
+
+ Driver arrived
+ Ready for pickup
+
+ ),
+})
+```
+
+Ready to get started? Head over to the [Installation](./installation) guide, or explore platform-specific guides for [iOS](/ios/introduction) and [Android](/android/introduction).
diff --git a/website/docs/getting-started/prior-art.md b/website/docs/v1/getting-started/prior-art.md
similarity index 100%
rename from website/docs/getting-started/prior-art.md
rename to website/docs/v1/getting-started/prior-art.md
diff --git a/website/docs/index.md b/website/docs/v1/index.md
similarity index 100%
rename from website/docs/index.md
rename to website/docs/v1/index.md
diff --git a/website/docs/ios/_meta.json b/website/docs/v1/ios/_meta.json
similarity index 100%
rename from website/docs/ios/_meta.json
rename to website/docs/v1/ios/_meta.json
diff --git a/website/docs/ios/api/_meta.json b/website/docs/v1/ios/api/_meta.json
similarity index 100%
rename from website/docs/ios/api/_meta.json
rename to website/docs/v1/ios/api/_meta.json
diff --git a/website/docs/v1/ios/api/configuration.md b/website/docs/v1/ios/api/configuration.md
new file mode 100644
index 00000000..dc3d9b0f
--- /dev/null
+++ b/website/docs/v1/ios/api/configuration.md
@@ -0,0 +1,120 @@
+# Configuration
+
+Voltra provides several configuration options to control Live Activity behavior, lifecycle, and appearance. These options can be used when starting, updating, or stopping Live Activities.
+
+For Expo plugin configuration options (like `groupIdentifier`, `enablePushNotifications`, `deploymentTarget`, and `widgets`), see the [Plugin Configuration](./plugin-configuration) documentation.
+
+## Dismissal Policy
+
+Voltra supports configuring how Live Activities behave after they end. You can control the dismissal timing using the `dismissalPolicy` option:
+
+### Dismissal Policy Options
+
+- **`'immediate'`** (default): The Live Activity is dismissed immediately when it ends
+- **`{ after: number }`**: The Live Activity remains visible for the specified number of seconds after ending, then automatically dismisses
+
+### Usage Examples
+
+**Immediate dismissal (default behavior):**
+
+```typescript
+import { startLiveActivity } from 'voltra/client'
+
+await startLiveActivity(variants, {
+ dismissalPolicy: 'immediate', // or omit for default
+})
+```
+
+**Delayed dismissal (keep visible for 30 seconds after ending):**
+
+```typescript
+await startLiveActivity(variants, {
+ dismissalPolicy: { after: 30 },
+})
+```
+
+**Update dismissal policy for active Live Activities:**
+
+```typescript
+import { updateLiveActivity } from 'voltra/client'
+
+await updateLiveActivity(activityId, variants, {
+ dismissalPolicy: { after: 60 },
+})
+```
+
+**Set dismissal policy when ending a Live Activity:**
+
+```typescript
+import { stopLiveActivity } from 'voltra/client'
+
+await stopLiveActivity(activityId, {
+ dismissalPolicy: { after: 10 },
+})
+```
+
+The dismissal policy applies to both programmatic ending (`stopLiveActivity`) and natural ending (when timers reach their end time). This gives you fine-grained control over the user experience when Live Activities conclude.
+
+## Additional Configuration Options
+
+Voltra provides additional configuration options to control Live Activity behavior and appearance.
+
+### Stale Date
+
+The `staleDate` option allows you to specify when a Live Activity should be considered stale and automatically dismissed by the system.
+
+```typescript
+import { startLiveActivity } from 'voltra/client'
+
+// Dismiss the Live Activity after 1 hour
+await startLiveActivity(variants, {
+ staleDate: Date.now() + 60 * 60 * 1000, // 1 hour from now
+})
+```
+
+**Note:** If you provide a `staleDate` in the past, it will be ignored and the Live Activity will use default behavior.
+
+### Relevance Score
+
+The `relevanceScore` option helps iOS prioritize which Live Activities to display when space is limited. Higher scores (closer to 1.0) indicate more important activities.
+
+```typescript
+import { startLiveActivity } from 'voltra/client'
+
+// High priority Live Activity (e.g., active delivery)
+await startLiveActivity(variants, {
+ relevanceScore: 0.8,
+})
+
+// Low priority Live Activity (e.g., background task)
+await startLiveActivity(variants, {
+ relevanceScore: 0.2,
+})
+```
+
+**Valid range:** 0.0 to 1.0 (default: 0.0)
+
+### Channel ID (broadcast push, iOS 18+)
+
+The `channelId` option subscribes the Live Activity to a broadcast channel for server-side updates. When provided, the activity receives updates via broadcast push notifications instead of individual device tokens—one server notification updates all activities on that channel. Requires `enablePushNotifications: true` and the Broadcast Capability enabled in your Apple Developer account.
+
+```typescript
+import { startLiveActivity } from 'voltra/client'
+
+await startLiveActivity(variants, {
+ activityName: 'match-123',
+ channelId: 'CTrNsYq/Ee8AALLzHQaVlA==', // From APNs channel management
+})
+```
+
+For full broadcast setup, see [Server-side updates - Broadcast push notifications](../development/server-side-updates.md#broadcast-push-notifications-ios-18).
+
+These options can be used together with dismissal policy and other configuration options:
+
+```typescript
+await startLiveActivity(variants, {
+ dismissalPolicy: { after: 30 },
+ staleDate: Date.now() + 2 * 60 * 60 * 1000, // 2 hours
+ relevanceScore: 0.7,
+})
+```
diff --git a/website/docs/v1/ios/api/plugin-configuration.md b/website/docs/v1/ios/api/plugin-configuration.md
new file mode 100644
index 00000000..b7558041
--- /dev/null
+++ b/website/docs/v1/ios/api/plugin-configuration.md
@@ -0,0 +1,125 @@
+# Plugin configuration
+
+The Voltra Expo config plugin accepts several configuration options in your `app.json` or `app.config.js`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "groupIdentifier": "group.your.bundle.identifier",
+ "enablePushNotifications": true,
+ "deploymentTarget": "18.0",
+ "targetName": "MyAppLiveActivity",
+ "widgets": [
+ {
+ "id": "weather",
+ "displayName": "Weather Widget",
+ "description": "Shows current weather conditions",
+ "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"],
+ "initialStatePath": "./widgets/weather-initial.tsx"
+ }
+ ]
+ }
+ ]
+ ]
+ }
+}
+```
+
+## Configuration options
+
+### `groupIdentifier` (optional)
+
+App Group identifier for sharing data between your app and the widget extension. Required if you want to:
+
+- Forward component events (like button taps) from Live Activities to your JavaScript code
+- Share images between your app and the extension
+- Use image preloading features
+
+**Format:** Must start with `group.` (e.g., `group.your.bundle.identifier`)
+
+### `enablePushNotifications` (optional)
+
+Enable server-side updates for Live Activities via Apple Push Notification Service (APNS). When enabled, you can update Live Activities even when your app is in the background or terminated.
+
+**Type:** `boolean`
+**Default:** `false`
+
+### `deploymentTarget` (optional)
+
+iOS deployment target version for the widget extension. If not provided, defaults to `17.0`. This allows the widget extension to have its own deployment target independent of the main app.
+
+**Type:** `string`
+**Default:** `"17.0"`
+**Example:** `"18.0"`
+
+**Note:** Code signing settings (development team, provisioning profiles) are automatically synchronized from the main app target, but the deployment target can be set independently.
+
+### `targetName` (optional)
+
+Custom target name for the widget extension. If not provided, defaults to `{AppName}LiveActivity` where `AppName` is your app's sanitized name.
+
+This is useful when:
+- Migrating from other Live Activity solutions (e.g., `@bacons/apple-targets`)
+- Matching existing provisioning profiles or credentials
+- Using a specific naming convention for your organization
+
+**Type:** `string`
+**Default:** `"{AppName}LiveActivity"`
+**Example:** `"widget"`, `"MyAppLiveActivity"`
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "groupIdentifier": "group.your.bundle.identifier",
+ "targetName": "widget"
+ }
+ ]
+ ]
+ }
+}
+```
+
+### `widgets` (optional)
+
+Array of widget configurations for Home Screen widgets. Each widget will be available in the iOS widget gallery.
+
+**Widget Configuration Properties:**
+
+- `id`: Unique identifier for the widget (alphanumeric with underscores only)
+- `displayName`: Name shown in the widget gallery
+- `description`: Description shown in the widget gallery
+- `supportedFamilies`: Array of supported widget sizes (`systemSmall`, `systemMedium`, `systemLarge`)
+- `initialStatePath`: (optional) Path to a file that exports initial widget state (see [Widget Pre-rendering](../development/widget-pre-rendering))
+- `serverUpdate`: (optional) Enable server-driven updates. See [Server-driven widgets](../development/server-driven-widgets) for full details.
+ - `url`: The Voltra SSR endpoint URL
+ - `intervalMinutes`: Update interval in minutes (default: `15`)
+ - `refresh`: Show a native refresh button (default: `false`, requires iOS 17+)
+
+**Example:**
+
+```json
+{
+ "widgets": [
+ {
+ "id": "weather",
+ "displayName": "Weather Widget",
+ "description": "Current weather conditions",
+ "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"],
+ "initialStatePath": "./widgets/weather-initial.tsx",
+ "serverUpdate": {
+ "url": "https://api.example.com/widgets/render",
+ "intervalMinutes": 30,
+ "refresh": true
+ }
+ }
+ ]
+}
+```
diff --git a/website/docs/ios/charts.md b/website/docs/v1/ios/charts.md
similarity index 100%
rename from website/docs/ios/charts.md
rename to website/docs/v1/ios/charts.md
diff --git a/website/docs/ios/components/_meta.json b/website/docs/v1/ios/components/_meta.json
similarity index 100%
rename from website/docs/ios/components/_meta.json
rename to website/docs/v1/ios/components/_meta.json
diff --git a/website/docs/v1/ios/components/interactive.md b/website/docs/v1/ios/components/interactive.md
new file mode 100644
index 00000000..d4338b1f
--- /dev/null
+++ b/website/docs/v1/ios/components/interactive.md
@@ -0,0 +1,177 @@
+# Interactive Controls (iOS)
+
+User interface controls that respond to user interaction in Live Activities.
+
+---
+
+## Button
+
+An interactive button component that triggers in-app events via interaction intents.
+
+**Parameters:**
+
+- `buttonStyle` (string, optional): Visual style of the button:
+ - `"automatic"` - System-determined style
+ - `"bordered"` - Bordered style
+ - `"borderedProminent"` - Bordered with prominent fill
+ - `"plain"` - Plain style without border
+ - `"borderless"` - Borderless style
+
+**Apple Documentation:** [Button](https://developer.apple.com/documentation/swiftui/button)
+
+**Availability:** iOS 17.0+ (interaction intents)
+
+### Usage
+
+Buttons fire interaction events that you can handle in your app:
+
+```tsx
+
+ Play Music
+
+```
+
+Handle the event:
+
+```typescript
+import { addVoltraListener } from 'voltra/client'
+
+const subscription = addVoltraListener('interaction', (event) => {
+ if (event.identifier === 'play-button') {
+ // Handle play action
+ }
+})
+```
+
+### Examples
+
+**Styled button:**
+
+```tsx
+
+ Save Changes
+
+```
+
+**Button with icon:**
+
+```tsx
+
+
+
+ Delete
+
+
+```
+
+**Compact button:**
+
+```tsx
+
+
+
+```
+
+---
+
+## Link
+
+A navigable link component that opens a URL when tapped. Uses SwiftUI's native Link for semantic navigation.
+
+**Parameters:**
+
+- `destination` (string, required): URL to navigate to when tapped. Supports both absolute URLs and relative paths.
+
+**Apple Documentation:** [Link](https://developer.apple.com/documentation/swiftui/link)
+
+**Availability:** iOS 14.0+
+
+### URL Normalization
+
+Link automatically normalizes URLs using your app's URL scheme:
+
+- Absolute URLs: Used as-is (`"myapp://orders/123"`, `"https://example.com"`)
+- Relative with `/`: `"/settings"` → `"myapp://settings"`
+- Relative without `/`: `"help"` → `"myapp://help"`
+
+### Examples
+
+**Link with absolute URL:**
+
+```tsx
+
+
+
+
+ Order #123
+ Tap to view details
+
+
+
+```
+
+**Link with relative path:**
+
+```tsx
+
+
+
+ Open Settings
+
+
+```
+
+**External link:**
+
+```tsx
+
+
+
+ Visit Support Site
+
+
+```
+
+### When to use Link vs Button
+
+| Feature | Link | Button |
+|---------|------|--------|
+| **Use Case** | Navigation to URLs | In-app actions/events |
+| **Visual** | Unstyled (custom via children) | Button styling (bordered, prominent, etc.) |
+| **iOS Version** | 14.0+ | 17.0+ |
+| **Tap Behavior** | Opens URL | Fires interaction event |
+| **Mechanism** | SwiftUI Link | AppIntents (VoltraInteractionIntent) |
+
+**Recommendation:** Use `Link` for navigation (e.g., list items, cards that open URLs). Use `Button` for actions that your app needs to handle (e.g., play/pause, save, delete).
+
+---
+
+## Toggle
+
+Toggles a boolean state via an intent. Fires an interaction event when changed.
+
+**Parameters:**
+
+- `defaultValue` (boolean, optional): Initial toggle state (default: `false`)
+
+**Apple Documentation:** [Toggle](https://developer.apple.com/documentation/swiftui/toggle)
+
+**Availability:** iOS 17.0+
+
+**Example:**
+
+```tsx
+
+```
+
+Handle toggle events:
+
+```typescript
+import { addVoltraListener } from 'voltra/client'
+
+const subscription = addVoltraListener('interaction', (event) => {
+ if (event.identifier === 'notifications-toggle') {
+ // Handle toggle state change
+ }
+})
+```
diff --git a/website/docs/ios/components/layout.md b/website/docs/v1/ios/components/layout.md
similarity index 100%
rename from website/docs/ios/components/layout.md
rename to website/docs/v1/ios/components/layout.md
diff --git a/website/docs/v1/ios/components/overview.md b/website/docs/v1/ios/components/overview.md
new file mode 100644
index 00000000..7a0c9541
--- /dev/null
+++ b/website/docs/v1/ios/components/overview.md
@@ -0,0 +1,51 @@
+# Components Overview (iOS)
+
+Voltra provides SwiftUI primitives with JSX bindings, allowing developers to create rich, interactive Live Activities using React/JSX syntax. These components connect web development workflows with native iOS Live Activity rendering.
+
+## Getting Started
+
+All Voltra components are available through the main `Voltra` namespace:
+
+```tsx
+import Voltra from 'voltra'
+
+const MyComponent = () => {
+ // Use any component
+ return (
+
+ Hello Live Activity!
+
+ Tap me
+
+
+ )
+}
+```
+
+## Component Categories
+
+Voltra organizes its components into categories:
+
+### Layout & Containers
+
+Components that arrange other elements or provide structural grouping. These include stacks (VStack, HStack, ZStack), spacers, and container components like GroupBox and GlassContainer.
+
+[See all layout & container components →](./layout)
+
+### Visual Elements & Typography
+
+Static or decorative elements used to display content. This category includes Text, Label, Image, Symbol, and visual effects like LinearGradient, Mask, and Divider.
+
+[See all visual elements & typography components →](./visual)
+
+### Data Visualization & Status
+
+Components for displaying data and status information. This includes progress indicators (LinearProgressView, CircularProgressView), gauges, and timers for showing dynamic information in Live Activities.
+
+[See all data visualization & status components →](./status)
+
+### Interactive Controls & Navigation
+
+User interface controls that respond to user interaction and navigation. This category includes Button and Toggle components for interactive Live Activities, plus Link for semantic URL navigation.
+
+[See all interactive control & navigation components →](./interactive)
diff --git a/website/docs/ios/components/status.md b/website/docs/v1/ios/components/status.md
similarity index 100%
rename from website/docs/ios/components/status.md
rename to website/docs/v1/ios/components/status.md
diff --git a/website/docs/ios/components/visual.md b/website/docs/v1/ios/components/visual.md
similarity index 100%
rename from website/docs/ios/components/visual.md
rename to website/docs/v1/ios/components/visual.md
diff --git a/website/docs/ios/development/_meta.json b/website/docs/v1/ios/development/_meta.json
similarity index 100%
rename from website/docs/ios/development/_meta.json
rename to website/docs/v1/ios/development/_meta.json
diff --git a/website/docs/v1/ios/development/developing-live-activities.md b/website/docs/v1/ios/development/developing-live-activities.md
new file mode 100644
index 00000000..9319d306
--- /dev/null
+++ b/website/docs/v1/ios/development/developing-live-activities.md
@@ -0,0 +1,229 @@
+# Developing Live Activities
+
+Voltra provides APIs that make building and testing Live Activities easier during development.
+
+## Supported variants
+
+Live Activities in iOS can appear in different contexts, and Voltra supports defining UI variants for each of these contexts. For detailed information about Live Activity design guidelines, see the [Apple Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/live-activities).
+
+### Lock Screen
+
+The `lockScreen` variant defines how your Live Activity appears on the lock screen. It can be either a ReactNode directly, or an object with content and optional styling:
+
+```typescript
+const variants = {
+ lockScreen: (
+
+ Your content here
+
+ ),
+}
+```
+
+To customize the system Lock Screen chrome, pass an object with `content` and `activityBackgroundTint`:
+
+```typescript
+const variants = {
+ lockScreen: {
+ activityBackgroundTint: '#101828',
+ content: (
+
+ Your content here
+
+ ),
+ },
+}
+```
+
+`activityBackgroundTint` is applied via SwiftUI's `activityBackgroundTint(...)` modifier on iOS. Voltra currently accepts the same color formats handled by the native iOS parser:
+
+- Hex colors such as `#RGB`, `#RGBA`, `#RRGGBB`, and `#RRGGBBAA`
+- `rgb(...)` and `rgba(...)`
+- `hsl(...)` and `hsla(...)`
+- Named colors: `red`, `orange`, `yellow`, `green`, `mint`, `teal`, `cyan`, `blue`, `indigo`, `purple`, `pink`, `brown`, `white`, `gray`, `black`, `clear`, `transparent`, `primary`, `secondary`
+
+Use `clear` or `transparent` to make the Live Activity background transparent.
+
+If the string cannot be parsed as one of those formats, iOS ignores the tint.
+
+### Dynamic Island
+
+The `island` variant defines how your Live Activity appears in the Dynamic Island (available on iPhone 14 Pro and later). The Dynamic Island has three display states:
+
+- **Minimal**: A compact pill-shaped view that appears when the activity is in the background
+- **Compact**: A slightly larger view with leading and trailing regions
+- **Expanded**: A full-width view with center, leading, trailing, and bottom regions
+
+```typescript
+const variants = {
+ island: {
+ keylineTint: '#10B981', // Optional tint color for the Dynamic Island keyline
+ minimal: ,
+ compact: {
+ leading: Order,
+ trailing: Confirmed,
+ },
+ expanded: {
+ center: Order Confirmed,
+ leading: ,
+ trailing: ETA: 15 min,
+ bottom: Your order is being prepared,
+ },
+ },
+}
+```
+
+`keylineTint` uses the same iOS color parser and accepted formats as `activityBackgroundTint`.
+
+### Supplemental Activity Families (iOS 18+, watchOS 11+)
+
+The `supplementalActivityFamilies` variant defines how your Live Activity appears on Apple Watch Smart Stack and CarPlay displays. This variant is optional and works seamlessly with your existing lock screen and Dynamic Island variants.
+
+```typescript
+const variants = {
+ lockScreen: (
+
+ {/* iPhone lock screen content */}
+
+ ),
+ island: {
+ /* Dynamic Island variants for iPhone */
+ },
+ supplementalActivityFamilies: {
+ small: (
+
+ 12 min
+ ETA
+
+ ),
+ },
+}
+```
+
+If `supplementalActivityFamilies.small` is not provided, Voltra will automatically construct it from your Dynamic Island `compact` variant by combining the leading and trailing content in an HStack.
+
+## Limitations
+
+### Animations and Live Updates
+
+There are specific constraints on how content can animate or update:
+
+- **Continuous Animations**: Custom continuous animations, such as rotating icons or elements moving along a path, are not supported.
+- **Smooth Updates**: Per-second "live" updates are only supported by specific components designed for this purpose:
+ - `Timer`: For countdowns and stopwatches.
+ - `LinearProgressView`: When used with `timerInterval`.
+- **Styling Trade-offs**: To enable smooth, system-driven animations (like a progress bar filling up in real-time), certain components may ignore custom styling properties (e.g., custom heights or thumb components) and fallback to standard system appearances.
+
+All other components only update their visual state when a new activity state is pushed from your application.
+
+## useLiveActivity
+
+For React development, Voltra provides the `useLiveActivity` hook for integration with the component lifecycle and automatic updates during development.
+
+:::warning
+Unfortunately, iOS suspends background apps after approximately 30 seconds. This means that if you navigate away from your app (for example, to check the Dynamic Island or lock screen), live reload and auto-update functionality will be paused.
+:::
+
+```typescript
+import { useLiveActivity } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+function OrderLiveActivity({ orderId, status }) {
+ const variants = {
+ lockScreen: (
+
+
+ {status === 'confirmed' ? 'Order Confirmed' : 'Order Ready'}
+
+
+ {status === 'confirmed' ? 'Your order is being prepared' : 'Your order is ready for pickup'}
+
+ {status === 'ready' && (
+
+ I'm Here
+
+ )}
+
+ ),
+ }
+
+ const { start, update, end, isActive } = useLiveActivity(variants, {
+ activityName: `order-${orderId}`,
+ autoStart: true, // Automatically start when component mounts
+ autoUpdate: true, // Automatically update when variants change
+ deepLinkUrl: `myapp://order/${orderId}`,
+ })
+
+ // Manual control if needed
+ const handleCancelOrder = async () => {
+ await end()
+ }
+
+ return (
+
+ Live Activity: {isActive ? 'Active' : 'Inactive'}
+
+
+ )
+}
+```
+
+## VoltraView Component
+
+For testing and development, Voltra provides a `VoltraView` component that renders Voltra JSX components directly in your React Native app. This is useful for:
+
+- Testing component layouts before deploying to Live Activities
+- Handling user interactions in development
+- Previewing how your Live Activity will look
+
+```tsx
+import { VoltraView } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+function MyComponent() {
+ const handleInteraction = (event: VoltraInteractionEvent) => {
+ console.log('User interacted with:', event.identifier)
+ console.log('Payload:', event.payload)
+ }
+
+ return (
+
+
+ Test Live Activity
+ This is how it will look
+
+ Test Button
+
+
+
+ )
+}
+```
+
+**Props:**
+
+- `id`: Unique identifier for the view (used for event filtering)
+- `children`: Voltra JSX components to render
+- `style`: React Native style for the container
+- `onInteraction`: Callback for user interactions with buttons/toggles
+
+```
+
+**Hook Options:**
+
+- `activityName`: Name of the Live Activity
+- `autoStart`: Automatically start when component mounts
+- `autoUpdate`: Automatically update when variants change
+- `deepLinkUrl`: URL to open when Live Activity is tapped
+
+**Hook Returns:**
+
+- `start()`: Start the Live Activity
+- `update()`: Update the Live Activity
+- `end()`: Stop the Live Activity
+- `isActive`: Boolean indicating if the Live Activity is currently active
+```
diff --git a/website/docs/v1/ios/development/developing-widgets.md b/website/docs/v1/ios/development/developing-widgets.md
new file mode 100644
index 00000000..46f96529
--- /dev/null
+++ b/website/docs/v1/ios/development/developing-widgets.md
@@ -0,0 +1,275 @@
+# Developing Widgets
+
+Voltra provides APIs that make building and testing Home Screen widgets easier during development.
+
+## VoltraWidgetPreview component
+
+`VoltraWidgetPreview` is a **React Native component** for testing and developing Voltra widget content. It renders Voltra JSX components at the exact dimensions of specific iOS widget families. This is useful for:
+
+- Testing component layouts before deploying to widgets
+- Previewing how your widget will look across different sizes
+- Developing and iterating on widget content within your React Native app
+
+:::note
+`VoltraWidgetPreview` is a regular React Native component. Use it in your React Native screens, not inside Voltra components.
+:::
+
+```tsx
+import { ScrollView, View } from 'react-native'
+import { VoltraWidgetPreview } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+function MyWidgetContent() {
+ return (
+
+ Weather Widget
+ Sunny, 72°F
+
+ )
+}
+
+// Preview different widget sizes in your React Native screen
+function WidgetTestingScreen() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+```
+
+**Props:**
+
+- `family`: Widget family size to preview (systemSmall, systemMedium, systemLarge, etc.)
+- `style`: Additional React Native styles to apply
+- `id`: Unique identifier for the view (used for event filtering)
+- `onInteraction`: Callback for user interactions with buttons/toggles
+
+## updateWidget API
+
+The `updateWidget` function allows you to update home screen widget content with new data.
+
+:::warning
+Widget updates are throttled to around an update per minute. iOS limits how frequently widgets can be refreshed to preserve battery life and system performance.
+:::
+
+```typescript
+import { updateWidget } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+await updateWidget('weather', {
+ systemSmall: 72°F,
+ systemMedium: (
+
+ 72°F
+
+ Sunny
+ High: 78° Low: 65°
+
+
+ ),
+ systemLarge: (
+
+ Weather
+ 72°F - Sunny
+ High: 78° Low: 65°
+
+ ),
+}, { deepLinkUrl: 'myapp://weather' })
+```
+
+**Parameters:**
+
+- `widgetId`: The widget identifier (as defined in your config plugin)
+- `variants`: An object mapping widget families to specific content
+- `options.deepLinkUrl`: URL to open when the widget is tapped
+
+## scheduleWidget API
+
+For widgets that need to change throughout the day, `scheduleWidget` lets you batch multiple updates in advance. iOS will automatically display each entry at its scheduled time—even when your app isn't running.
+
+:::tip
+This is perfect for weather forecasts, calendar events, news rotation, or any content that changes on a predictable schedule.
+:::
+
+```typescript
+import { scheduleWidget } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+// Schedule weather updates throughout the day
+await scheduleWidget('weather', [
+ {
+ date: new Date('2026-01-16T09:00:00'),
+ variants: {
+ systemSmall: Morning: 65°F ☀️,
+ systemMedium: Good morning! 65°F and sunny
+ }
+ },
+ {
+ date: new Date('2026-01-16T15:00:00'),
+ variants: {
+ systemSmall: Afternoon: 72°F ☀️,
+ systemMedium: Afternoon: 72°F and sunny
+ }
+ },
+ {
+ date: new Date('2026-01-16T21:00:00'),
+ variants: {
+ systemSmall: Evening: 68°F 🌙,
+ systemMedium: Good evening! 68°F and clear
+ }
+ }
+])
+```
+
+**Parameters:**
+
+- `widgetId`: The widget identifier (as defined in your config plugin)
+- `entries`: Array of timeline entries, each containing:
+ - `date`: When this content should be displayed
+ - `variants`: Widget content for different size families
+ - `deepLinkUrl` (optional): URL to open when tapping this specific entry
+
+**With deep links per entry:**
+
+```typescript
+await scheduleWidget('news', [
+ {
+ date: new Date('2026-01-16T08:00:00'),
+ variants: {
+ systemSmall: Morning Headlines
+ },
+ deepLinkUrl: '/news/morning'
+ },
+ {
+ date: new Date('2026-01-16T20:00:00'),
+ variants: {
+ systemSmall: Evening Edition
+ },
+ deepLinkUrl: '/news/evening'
+ }
+])
+```
+
+:::warning iOS System Constraints
+iOS controls when widgets actually update based on battery level, widget visibility, and system load. While you can schedule entries at any interval, iOS typically enforces a minimum of ~15 minutes between updates. Entries scheduled more frequently may be delayed or coalesced.
+:::
+
+**Best practices:**
+
+- Schedule entries at realistic intervals (15+ minutes apart)
+- Don't schedule hundreds of entries—iOS has a daily refresh budget
+- Use `updateWidget` for immediate one-time updates
+- Use `scheduleWidget` for predictable, recurring content changes
+
+## Widget configuration via Expo plugin
+
+Widgets are configured through the Voltra Expo config plugin. Add the widget configuration to your `app.json` or `app.config.js`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "widgets": [
+ {
+ "id": "weather",
+ "displayName": "Weather Widget",
+ "description": "Current weather conditions",
+ "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"]
+ }
+ ]
+ }
+ ]
+ ]
+ }
+}
+```
+
+**Widget Configuration Properties:**
+
+- `id`: Unique identifier for the widget (alphanumeric with underscores only)
+- `displayName`: Name shown in the widget gallery
+- `description`: Description shown in the widget gallery
+- `supportedFamilies`: Array of supported widget sizes (defaults to systemSmall, systemMedium, systemLarge)
+
+## Fallback logic for variants
+
+When a widget size doesn't have specific content defined, Voltra automatically falls back to other available variants in this order:
+
+1. `systemMedium` (preferred fallback for home screen widgets)
+2. `systemSmall`
+3. `systemLarge`
+4. `systemExtraLarge`
+5. `accessoryRectangular`
+6. `accessoryCircular`
+7. `accessoryInline`
+
+This ensures your widget always displays content even when a specific size variant isn't provided. For example, if you only define `systemSmall` and `systemLarge`, a `systemMedium` widget will use the `systemSmall` content.
+
+```typescript
+// Only defining systemSmall and systemLarge
+await updateWidget('minimal', {
+ systemSmall: 72°F,
+ systemLarge: Weather: 72°F - Sunny,
+})
+
+// systemMedium will automatically use systemSmall content as fallback
+```
+
+## Additional widget APIs
+
+### getActiveWidgets
+
+Detect which widgets are currently installed on the user's home screen:
+
+```typescript
+import { getActiveWidgets } from 'voltra/client'
+
+const widgets = await getActiveWidgets()
+// [{ name: 'weather', family: 'systemSmall', kind: '...' }]
+```
+
+See [Querying Active Widgets](./querying-active-widgets) for more details.
+
+### reloadWidgets
+
+Force widget timelines to refresh their content after updating shared resources like preloaded images:
+
+```typescript
+import { reloadWidgets } from 'voltra/client'
+
+// Reload specific widgets
+await reloadWidgets(['weather', 'calendar'])
+
+// Reload all widgets
+await reloadWidgets()
+```
+
+### clearWidget / clearAllWidgets
+
+Remove stored widget data, causing widgets to show their placeholder state:
+
+```typescript
+import { clearWidget, clearAllWidgets } from 'voltra/client'
+
+// Clear specific widget
+await clearWidget('weather')
+
+// Clear all widgets
+await clearAllWidgets()
+```
diff --git a/website/docs/v1/ios/development/events.md b/website/docs/v1/ios/development/events.md
new file mode 100644
index 00000000..1450d4dd
--- /dev/null
+++ b/website/docs/v1/ios/development/events.md
@@ -0,0 +1,167 @@
+# Events
+
+Voltra emits several types of events that allow your app to respond to changes in Live Activities and user interactions.
+
+## Overview
+
+Voltra provides four main event types:
+
+- **Activity state changes**: Notifications when a Live Activity's state changes (e.g., dismissed, ended)
+- **Push tokens**: Tokens needed for server-side updates via push notifications
+- **Push-to-start tokens**: Tokens for starting Live Activities remotely (iOS 17.2+)
+- **User interactions**: Events triggered when users interact with buttons or toggles in your Live Activity
+
+## Activity state changes
+
+Voltra emits events whenever a Live Activity's state changes. This allows you to track the lifecycle of your Live Activities and respond accordingly.
+
+### Listening for state changes
+
+Use `addVoltraListener` with the `'stateChange'` event type to subscribe to state change events:
+
+```typescript
+import { addVoltraListener } from 'voltra/client'
+
+const subscription = addVoltraListener('stateChange', (event) => {
+ console.log('Activity name:', event.activityName)
+ console.log('New state:', event.activityState)
+
+ // Handle different states
+ if (event.activityState === 'dismissed') {
+ // User dismissed the Live Activity
+ } else if (event.activityState === 'ended') {
+ // Live Activity ended
+ } else if (event.activityState === 'active') {
+ // Live Activity is active
+ }
+})
+
+// Don't forget to clean up
+subscription.remove()
+```
+
+### Activity states
+
+The `activityState` field can have the following values:
+
+- `'active'`: The Live Activity is currently active and visible
+- `'dismissed'`: The user manually dismissed the Live Activity
+- `'pending'`: The Live Activity is pending activation
+- `'stale'`: The Live Activity has become stale
+- `'ended'`: The Live Activity has ended
+
+### Event structure
+
+Each state change event contains:
+
+- `activityName`: The name of the Live Activity (as specified when starting it)
+- `activityState`: The new state of the Live Activity
+
+## Push tokens
+
+To enable server-side updates for your Live Activities, you need to obtain push tokens. Voltra emits events when these tokens become available.
+
+### Activity push tokens
+
+Activity push tokens are used to update existing Live Activities via push notifications. Listen for these tokens using `addVoltraListener` with the `'activityTokenReceived'` event type:
+
+```typescript
+import { addVoltraListener } from 'voltra/client'
+
+const subscription = addVoltraListener('activityTokenReceived', (event) => {
+ console.log('Activity name:', event.activityName)
+ console.log('Push token:', event.pushToken)
+
+ // Send the token to your server
+ sendTokenToServer({
+ activityName: event.activityName,
+ pushToken: event.pushToken,
+ })
+})
+
+subscription.remove()
+```
+
+For more information about using push tokens for server-side updates, see the [server-side updates guide](./server-side-updates.md).
+
+### Push-to-start tokens
+
+Push-to-start tokens (available on iOS 17.2+) allow you to start Live Activities remotely via push notifications. Listen for these tokens using `addVoltraListener` with the `'activityPushToStartTokenReceived'` event type:
+
+```typescript
+import { addVoltraListener } from 'voltra/client'
+
+const subscription = addVoltraListener('activityPushToStartTokenReceived', (event) => {
+ console.log('Push-to-start token:', event.pushToStartToken)
+
+ // Send the token to your server
+ sendTokenToServer({
+ pushToStartToken: event.pushToStartToken,
+ })
+})
+
+subscription.remove()
+```
+
+For more information about using push tokens for starting Live Activity remotely, see the [server-side updates guide](./server-side-updates.md).
+
+## User interactions
+
+When users interact with buttons or toggles in your Live Activity, Voltra emits events that allow your app to respond to these interactions. This works even when your app isn't running, thanks to Apple's AppIntents framework.
+
+### Listening for interactions
+
+Subscribe to interaction events using `addVoltraListener` with the `'interaction'` event type:
+
+```typescript
+import { addVoltraListener } from 'voltra/client'
+
+const subscription = addVoltraListener('interaction', (event) => {
+ console.log('Component interacted:', event.identifier)
+ console.log('Payload:', event.payload)
+
+ // Handle the interaction based on the identifier
+ if (event.identifier === 'contact-driver') {
+ // Open contact screen
+ } else if (event.identifier === 'notifications-toggle') {
+ // Handle toggle state change
+ }
+})
+
+subscription.remove()
+```
+
+For detailed information about handling interactions, including component identifiers, deep linking, and app lifecycle considerations, see the [interactions guide](./interactions.md).
+
+## Best practices
+
+### Set up listeners early
+
+Initialize your event listeners as early as possible in your app lifecycle (e.g., in your root component or app entry point). This ensures they're ready when events occur, especially when your app is launched from a terminated state due to a Live Activity interaction.
+
+### Clean up subscriptions
+
+Always clean up your event subscriptions to prevent memory leaks. If you're using React hooks, return a cleanup function from `useEffect`:
+
+```typescript
+import { useEffect } from 'react'
+import { addVoltraListener } from 'voltra/client'
+
+function MyComponent() {
+ useEffect(() => {
+ const subscription = addVoltraListener('stateChange', (event) => {
+ // Handle event
+ })
+
+ return () => {
+ subscription.remove()
+ }
+ }, [])
+
+ return null
+}
+```
+
+### Handle app launch scenarios
+
+When your app is launched from a terminated state (e.g., due to a Live Activity interaction), make sure your event listeners are set up before processing any queued events. Voltra handles event queuing automatically when using App Groups.
diff --git a/website/docs/ios/development/flexbox-layout.md b/website/docs/v1/ios/development/flexbox-layout.md
similarity index 100%
rename from website/docs/ios/development/flexbox-layout.md
rename to website/docs/v1/ios/development/flexbox-layout.md
diff --git a/website/docs/v1/ios/development/image-preloading.md b/website/docs/v1/ios/development/image-preloading.md
new file mode 100644
index 00000000..70834f79
--- /dev/null
+++ b/website/docs/v1/ios/development/image-preloading.md
@@ -0,0 +1,163 @@
+# Image Preloading
+
+This page provides detailed API documentation for Voltra's image preloading system. For an overview of all image handling approaches in Live Activities, see the [Images](images) documentation.
+
+Live Activities have strict size limits (4KB per update), which makes displaying remote images challenging. The image preloading API downloads images to shared App Group storage, making them available to both your app and Live Activities.
+
+## Overview
+
+The image preloading system works by:
+
+1. Downloading images from URLs to App Group shared storage
+2. Validating that images are under the 4KB ActivityKit limit
+3. Making images available to Live Activities via the `assetName` property
+4. Providing APIs to reload existing Live Activities when new images are available
+
+## API Reference
+
+### `preloadImages(images: PreloadImageOptions[]): Promise`
+
+Downloads images to App Group storage for use in Live Activities.
+
+```typescript
+type PreloadImageOptions = {
+ url: string // The URL to download the image from
+ key: string // The assetName to use when referencing this image
+ method?: 'GET' | 'POST' | 'PUT' // HTTP method (default: 'GET')
+ headers?: Record // Optional HTTP headers
+}
+
+type PreloadImagesResult = {
+ succeeded: string[] // Keys of successfully downloaded images
+ failed: { key: string; error: string }[] // Failed downloads with error messages
+}
+```
+
+**Example:**
+
+```typescript
+import { preloadImages } from 'voltra/client'
+
+const result = await preloadImages([
+ {
+ url: 'https://example.com/album-art.jpg',
+ key: 'current-song-artwork',
+ headers: { Authorization: 'Bearer token' },
+ },
+])
+
+console.log('Succeeded:', result.succeeded)
+console.log('Failed:', result.failed)
+```
+
+### `reloadLiveActivities(activityNames?: string[]): Promise`
+
+Reloads Live Activities to pick up newly preloaded images. If no `activityNames` are provided, all active Live Activities will be reloaded.
+
+```typescript
+import { reloadLiveActivities } from 'voltra/client'
+
+// Reload all Live Activities
+await reloadLiveActivities()
+
+// Reload specific activities
+await reloadLiveActivities(['music-player', 'order-tracker'])
+```
+
+### `clearPreloadedImages(keys?: string[]): Promise`
+
+Removes preloaded images from App Group storage. If no `keys` are provided, all preloaded images will be cleared.
+
+```typescript
+import { clearPreloadedImages } from 'voltra/client'
+
+// Clear specific images
+await clearPreloadedImages(['album-art', 'profile-pic'])
+
+// Clear all preloaded images
+await clearPreloadedImages()
+```
+
+## Usage in Live Activities
+
+Once images are preloaded, reference them using the `assetName` property:
+
+```typescript
+import { Voltra } from 'voltra'
+
+function MusicPlayerLiveActivity({ song }) {
+ return {
+ lockScreen: (
+
+
+
+ {song.title}
+
+
+ {song.artist}
+
+
+ )
+ }
+}
+```
+
+## Size Validation
+
+ActivityKit enforces a 4KB limit per Live Activity update. Images are validated both before and after download:
+
+- **Content-Length header**: If available, images larger than 4KB are rejected before download
+- **Actual size**: After download, the actual data size is validated
+- **Image validation**: Ensures the downloaded data is a valid image format
+
+Images that exceed 4KB will result in a preload failure with a descriptive error message.
+
+## Error Handling
+
+The `preloadImages` function provides detailed error information:
+
+```typescript
+const result = await preloadImages([
+ { url: 'https://example.com/large-image.jpg', key: 'big-image' },
+ { url: 'https://invalid-url.com/image.jpg', key: 'broken-url' },
+])
+
+// Handle results
+if (result.failed.length > 0) {
+ result.failed.forEach(({ key, error }) => {
+ console.error(`Failed to preload ${key}: ${error}`)
+ })
+}
+
+// Only proceed if all images succeeded
+if (result.succeeded.length === 2) {
+ await reloadLiveActivities()
+}
+```
+
+## Common Error Messages
+
+- `"Image 'key-name' is too large: 5120 bytes (max 4096 bytes for Live Activities)"` - Image exceeds 4KB limit
+- `"Invalid image data for 'key-name'"` - Downloaded data is not a valid image
+- `"HTTP error: 404"` - Server returned an error status code
+- `"App Group not configured"` - Missing App Group configuration in the config plugin
+
+## Configuration
+
+Image preloading requires App Group configuration in your Expo config plugin:
+
+```json
+{
+ "plugins": [
+ [
+ "voltra",
+ {
+ "groupIdentifier": "group.your.app"
+ }
+ ]
+ ]
+}
+```
diff --git a/website/docs/v1/ios/development/images.md b/website/docs/v1/ios/development/images.md
new file mode 100644
index 00000000..fba0145d
--- /dev/null
+++ b/website/docs/v1/ios/development/images.md
@@ -0,0 +1,93 @@
+# Images
+
+Live Activities have strict size limits (4KB per update), making image handling a critical optimization area. Voltra provides three different approaches for including images in your Live Activities, each with different trade-offs and use cases:
+
+- **Base64 encoding**: Best for small, static images (< 1KB)
+- **Build-time asset copying**: Best for medium-sized images that are known at build time
+- **Runtime preloading**: Best for dynamic images from remote URLs
+
+## Base64 encoding
+
+For extremely small images, you can embed them directly as base64-encoded strings in your JSX.
+
+```tsx
+
+```
+
+## Build-time asset copying
+
+Place images in the `/assets/voltra/` directory and they'll be automatically copied to the iOS extension bundle during build.
+
+```
+project-root/
+├── assets/
+│ └── voltra/
+│ ├── logo.png
+│ ├── icon-star.png
+│ └── background-pattern.png
+```
+
+Here's how build-time asset copying works:
+
+1. Images in `/assets/voltra/` are automatically detected during build
+2. Each image is validated to be under 4KB (ActivityKit limit)
+3. Images are copied to the Live Activity extension's `Assets.xcassets`
+4. Xcode generates proper `.imageset` directories and metadata
+
+You can then reference these images using their assetName:
+
+```tsx
+
+
+```
+
+## Runtime preloading
+
+For dynamic images from remote URLs, use Voltra's image preloading API to cache images in App Group shared storage.
+
+The image preloading system works by:
+
+1. Downloading images from URLs to App Group shared storage
+2. Validating that images are under the 4KB ActivityKit limit
+3. Making images available to Live Activities via the `assetName` property
+4. Providing APIs to reload existing Live Activities when new images are available
+
+Once images are preloaded, reference them using the `assetName` property:
+
+```typescript
+import { Voltra } from 'voltra'
+
+function MusicPlayerLiveActivity({ song }) {
+ return {
+ lockScreen: (
+
+
+
+ {song.title}
+
+
+ {song.artist}
+
+
+ )
+ }
+}
+```
+
+For detailed API documentation, see [Image Preloading](image-preloading).
+
+## Comparison table
+
+| Approach | Image Size | When Known | Dynamic | Setup Required | Payload Impact |
+| ---------- | ---------- | ---------- | ------- | ---------------- | ------------------- |
+| Base64 | < 1KB | Build time | No | None | High (encoded size) |
+| Build-time | 1KB - 4KB | Build time | No | File placement | Low (filename only) |
+| Preloading | 1KB - 4KB | Runtime | Yes | App Group config | Low (filename only) |
diff --git a/website/docs/v1/ios/development/interactions.md b/website/docs/v1/ios/development/interactions.md
new file mode 100644
index 00000000..9cf0abe4
--- /dev/null
+++ b/website/docs/v1/ios/development/interactions.md
@@ -0,0 +1,184 @@
+# Interactions
+
+Voltra components leverage Apple's ActivityKit to provide interactive Live Activities.
+
+## Interaction capabilities
+
+ActivityKit provides a limited set of interactions with Live Activities. The only interactable elements are:
+
+- **Live Activity view**: Tapping anywhere on the Live Activity itself can launch your app via a deep link, allowing users to access more detailed information or perform specific actions.
+
+- **Links**: Navigation elements that open URLs when tapped. Links use SwiftUI's native Link component and work across all Live Activity contexts (iOS 14.0+).
+
+- **Buttons**: Interactive buttons that trigger in-app events. Buttons are implemented using AppIntents and work on both the Lock Screen and Dynamic Island (iOS 17.0+).
+
+- **Toggles**: Interactive toggle switches that allow users to change boolean states. Like buttons, toggles use AppIntents and are supported across all Live Activity contexts (iOS 17.0+).
+
+These interactions are powered by Apple's AppIntents framework and SwiftUI's Link component, which enable Live Activities to communicate with your app even when it's not running.
+
+## Handling interactions
+
+When a user interacts with a button or toggle in your Live Activity, Voltra automatically emits an event containing the identifier of the component that was interacted with. This allows your app to respond appropriately to specific user actions.
+
+### Listening for events
+
+To handle interactions, subscribe to Voltra UI events using the `addVoltraListener` function with the `'interaction'` event type:
+
+```typescript
+import { addVoltraListener } from 'voltra/client'
+
+const subscription = addVoltraListener('interaction', (event) => {
+ console.log('Component interacted:', event.identifier)
+ console.log('Component type:', event.componentType)
+
+ // Handle the interaction based on the identifier
+ if (event.identifier === 'contact-driver') {
+ // Open contact screen
+ } else if (event.identifier === 'toggle') {
+ // Handle toggle state change
+ }
+})
+
+// Don't forget to clean up
+subscription.remove()
+```
+
+### Event structure
+
+Each interaction event contains:
+
+- `identifier`: The unique identifier of the component that was interacted with (as specified in your JSX via the `id` prop)
+- `componentType`: The type of component that triggered the event (e.g., "button", "toggle")
+
+### Component identifiers
+
+To receive interaction events, make sure your interactive components have unique identifiers:
+
+```typescript
+
+ Contact driver
+
+
+```
+
+If you don't provide an explicit `id` prop, Voltra will automatically generate a deterministic identifier based on the component's position in the tree. However, for reliable event handling, it's recommended to use explicit identifiers.
+
+## Deep linking
+
+### Activity-level deep links
+
+When users tap on the Live Activity itself (not on a button or link), your app can be launched via a deep link. Configure the deep link URL when starting a Live Activity:
+
+```typescript
+import { useLiveActivity } from 'voltra/client'
+
+const { start } = useLiveActivity(
+ {
+ lockScreen: ,
+ },
+ {
+ activityName: 'activity-detail',
+ deepLinkUrl: '/voltraui/activity-detail',
+ }
+)
+```
+
+The deep link URL can be:
+
+- A full URL with a scheme (e.g., `myapp://activity-detail`)
+- A path that will be prefixed with your app's URL scheme (e.g., `/activity-detail`)
+
+When the Live Activity is tapped, your app will be launched (or brought to the foreground) and the deep link will be handled by your routing system.
+
+### Component-level deep links
+
+Individual components can also open URLs when tapped, providing granular navigation within your Live Activity.
+
+#### Link Component
+
+The `Link` component provides semantic navigation to URLs. It uses SwiftUI's native Link and works on iOS 14.0+:
+
+```tsx
+
+
+
+ Order #123
+
+
+```
+
+Links support URL normalization:
+- Absolute URLs: `"myapp://path"`, `"https://example.com"`
+- Relative paths: `"/settings"` → `"myapp://settings"`
+- Simple paths: `"help"` → `"myapp://help"`
+
+For styled navigation buttons, wrap a Link with custom children to achieve button-like appearance while maintaining URL navigation functionality.
+
+## Limitations
+
+While Live Activities provide powerful interaction capabilities, there are some limitations to be aware of:
+
+- **Limited interactable elements**: Only buttons and toggles are supported as interactive components. Other UI elements like text fields, sliders, or custom controls are not available in Live Activities.
+
+- **iOS version requirements**: Interactive buttons and toggles require iOS 17.0+. On iOS 16.x, these components will render but will not be interactive.
+
+- **No real-time updates from interactions**: When a user interacts with a button or toggle, the Live Activity UI doesn't update automatically. You'll need to update the Live Activity content manually using the `update` function if you want to reflect state changes.
+
+- **Event delivery timing**: Events are delivered asynchronously. There may be a slight delay between the user's interaction and your event handler being called, especially if the app needs to be launched.
+
+## Example
+
+Here's a complete example showing how to handle interactions:
+
+```typescript
+import { useEffect } from 'react'
+import { useLiveActivity } from 'voltra/client'
+import { Voltra } from 'voltra'
+import { addVoltraListener } from 'voltra/client'
+
+function MyLiveActivity() {
+ const { start, update } = useLiveActivity(
+ {
+ lockScreen: (
+
+ Music Player
+
+ Play
+
+
+ Pause
+
+
+
+ ),
+ },
+ {
+ activityName: 'music-player',
+ deepLinkUrl: '/music-player',
+ }
+ )
+
+ useEffect(() => {
+ const subscription = addVoltraListener('interaction', (event) => {
+ switch (event.identifier) {
+ case 'play-button':
+ // Start playback
+ console.log('Play button tapped')
+ break
+ case 'pause-button':
+ // Pause playback
+ console.log('Pause button tapped')
+ break
+ case 'shuffle-toggle':
+ // Toggle shuffle mode
+ console.log('Shuffle toggled')
+ break
+ }
+ })
+
+ return () => subscription.remove()
+ }, [])
+
+ return null
+}
+```
diff --git a/website/docs/v1/ios/development/managing-live-activities-locally.md b/website/docs/v1/ios/development/managing-live-activities-locally.md
new file mode 100644
index 00000000..fbce9b58
--- /dev/null
+++ b/website/docs/v1/ios/development/managing-live-activities-locally.md
@@ -0,0 +1,293 @@
+# Managing Live Activities locally
+
+Live Activities are dynamic interfaces that display real-time information on iOS devices. Voltra provides a comprehensive set of APIs for managing the complete lifecycle of Live Activities directly from your React Native app, without requiring server-side infrastructure.
+
+## Overview
+
+Managing Live Activities locally involves four main phases:
+
+1. **Starting** a Live Activity with initial content and configuration
+2. **Updating** the content and configuration as data changes
+3. **Monitoring** state changes and user interactions
+4. **Stopping** the Live Activity when it's no longer needed
+
+Voltra offers both imperative APIs for direct control and React hooks for seamless integration with your components.
+
+## Imperative APIs
+
+The imperative APIs provide direct, programmatic control over Live Activities. These are the core functions you'll use to manage Live Activity lifecycles.
+
+### Starting Live Activities
+
+Use `startLiveActivity()` to create and display a new Live Activity.
+
+```typescript
+import { startLiveActivity } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+const variants = {
+ lockScreen: (
+
+
+ Order Confirmed
+
+
+ Your order is being prepared
+
+
+ ),
+ // Define compact, minimal, and expanded variants for Dynamic Island
+ compact: Order confirmed,
+ minimal: ,
+ expanded: (
+
+ Order Confirmed
+
+ Your order is being prepared
+
+
+ ),
+}
+
+const activityId = await startLiveActivity(variants, {
+ activityName: 'order-123', // Optional: for re-binding on app restart
+ deepLinkUrl: 'myapp://order/123', // Optional: URL to open when tapped
+ channelId: 'CTrNsYq/Ee8AALLzHQaVlA==', // Optional: broadcast channel (iOS 18+)
+ dismissalPolicy: { after: 30 },
+ staleDate: Date.now() + 60 * 60 * 1000, // 1 hour
+ relevanceScore: 0.8,
+})
+```
+
+**Parameters:**
+
+- `variants`: A `LiveActivityVariants` object defining the UI for different display contexts
+- `options`: Configuration options (see Configuration Options section below)
+
+**Returns:** A promise that resolves to the Live Activity ID (string)
+
+### Updating Live Activities
+
+Use `updateLiveActivity()` to modify the content and configuration of an active Live Activity.
+
+```typescript
+import { updateLiveActivity } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+const updatedVariants = {
+ lockScreen: (
+
+
+ Order Ready
+
+
+ Your order is ready for pickup
+
+
+ I'm Here
+
+
+ ),
+ compact: Ready for pickup,
+ minimal: ,
+}
+
+await updateLiveActivity(activityId, updatedVariants, {
+ dismissalPolicy: { after: 300 }, // 5 minutes
+ relevanceScore: 1.0, // High priority now
+})
+```
+
+**Parameters:**
+
+- `activityId`: The ID returned from `startLiveActivity()`
+- `variants`: Updated UI variants
+- `options`: Updated configuration options
+
+### Stopping Live Activities
+
+Use `stopLiveActivity()` to end a Live Activity.
+
+```typescript
+import { stopLiveActivity } from 'voltra/client'
+
+await stopLiveActivity(activityId, {
+ dismissalPolicy: { after: 10 }, // Keep visible for 10 seconds after ending
+})
+```
+
+**Parameters:**
+
+- `activityId`: The ID of the Live Activity to stop
+- `options`: Final configuration options
+
+### Checking Live Activity status
+
+Use `isLiveActivityActive()` to check if a specific Live Activity is currently active.
+
+```typescript
+import { isLiveActivityActive } from 'voltra/client'
+
+if (isLiveActivityActive('order-123')) {
+ console.log('Live Activity is active')
+} else {
+ console.log('Live Activity is not active')
+}
+```
+
+**Parameters:**
+
+- `activityName`: The name of the Live Activity to check (same as `activityName` used when starting the activity)
+
+**Returns:** Boolean indicating whether the Live Activity is currently active
+
+### Utility functions
+
+#### Platform detection
+
+```typescript
+import { isGlassSupported, isHeadless } from 'voltra/client'
+
+// Check if the device supports Liquid Glass (iOS 26+)
+if (isGlassSupported()) {
+ // Use Liquid Glass features
+}
+
+// Check if app was launched in background
+if (isHeadless()) {
+ // App was launched in background (e.g., from Live Activity interaction)
+ // Perform background tasks without UI
+}
+```
+
+#### Ending all Live Activities
+
+Use `endAllLiveActivities()` to immediately end all active Live Activities in your app.
+
+```typescript
+import { endAllLiveActivities } from 'voltra/client'
+
+// End all Live Activities (useful for cleanup or logout scenarios)
+await endAllLiveActivities()
+```
+
+**Note:** This function ends all Live Activities immediately without applying any dismissal policies. Use this for bulk cleanup scenarios rather than individual activity management.
+
+## Development tools
+
+For development-friendly APIs that provide automatic updates and easier testing, see the [Development documentation](./developing-live-activities.md).
+
+## Event handling
+
+Live Activities emit events that you can listen to for state changes and user interactions. See the [Events API documentation](./events.md) for detailed information on subscribing to and handling Live Activity events.
+
+## Configuration options
+
+Voltra provides several configuration options to control Live Activity behavior, lifecycle, and appearance. These options can be used with `startLiveActivity()`, `updateLiveActivity()`, and `stopLiveActivity()`.
+
+### Dismissal policy
+
+Controls how Live Activities behave after they end.
+
+**Options:**
+
+- **`'immediate'`** (default): Live Activity is dismissed immediately when it ends
+- **`{ after: number }`**: Live Activity remains visible for the specified number of seconds after ending, then automatically dismisses
+
+**Examples:**
+
+```typescript
+// Immediate dismissal (default)
+await startLiveActivity(variants, {
+ dismissalPolicy: 'immediate',
+})
+
+// Keep visible for 30 seconds after ending
+await startLiveActivity(variants, {
+ dismissalPolicy: { after: 30 },
+})
+
+// Update dismissal timing for active Live Activities
+await updateLiveActivity(activityId, variants, {
+ dismissalPolicy: { after: 300 }, // 5 minutes
+})
+
+// Set dismissal timing when stopping
+await stopLiveActivity(activityId, {
+ dismissalPolicy: { after: 10 },
+})
+```
+
+The dismissal policy applies to both programmatic ending and natural ending (when timers expire).
+
+### Stale date
+
+Specifies when a Live Activity should be considered stale and automatically dismissed by iOS.
+
+```typescript
+// Dismiss after 1 hour
+await startLiveActivity(variants, {
+ staleDate: Date.now() + 60 * 60 * 1000,
+})
+
+// Dismiss after 2 hours
+await startLiveActivity(variants, {
+ staleDate: Date.now() + 2 * 60 * 60 * 1000,
+})
+```
+
+**Note:** If you provide a `staleDate` in the past, it will be ignored.
+
+### Relevance score
+
+Helps iOS prioritize which Live Activities to display when space is limited.
+
+**Range:** 0.0 to 1.0 (default: 0.0)
+
+```typescript
+// High priority (e.g., active delivery)
+await startLiveActivity(variants, {
+ relevanceScore: 0.8,
+})
+
+// Low priority (e.g., background task)
+await startLiveActivity(variants, {
+ relevanceScore: 0.2,
+})
+```
+
+Live Activities can receive updates even when your app is in the background or terminated, but they cannot execute JavaScript code. For real-time updates from backgrounded apps, use server-side push notifications.
+
+### Channel ID (broadcast push, iOS 18+)
+
+Subscribes the Live Activity to a broadcast channel for server-side updates. When provided, the activity receives updates via broadcast push notifications instead of individual device tokens—one server notification updates all activities on that channel.
+
+```typescript
+// For shared content (e.g., live sports, flight status)
+await startLiveActivity(variants, {
+ activityName: 'match-123',
+ channelId: 'CTrNsYq/Ee8AALLzHQaVlA==', // From APNs channel management
+})
+```
+
+Requires `enablePushNotifications: true` in the Voltra plugin and the Broadcast Capability enabled in your Apple Developer account. See [Server-side updates](./server-side-updates.md#broadcast-push-notifications-ios-18) for details.
+
+## Best practices
+
+### Activity lifecycle management
+
+- Always provide meaningful `activityId` values for re-binding on app restart
+- Clean up Live Activities when they're no longer relevant
+- Use appropriate dismissal policies based on your use case
+
+### Performance considerations
+
+- Keep UI variants lightweight and avoid complex component trees
+- Use appropriate relevance scores to ensure important activities are visible
+- Set reasonable stale dates to prevent accumulation of outdated activities
+
+### User experience
+
+- Provide deep link URLs for navigation when Live Activities are tapped
+- Use meaningful compact and minimal variants for Dynamic Island
+- Consider dismissal timing carefully - users may want to see final states
diff --git a/website/docs/v1/ios/development/performance.md b/website/docs/v1/ios/development/performance.md
new file mode 100644
index 00000000..65d3fb00
--- /dev/null
+++ b/website/docs/v1/ios/development/performance.md
@@ -0,0 +1,112 @@
+# Performance
+
+Voltra provides automatic optimizations to help you create efficient Live Activities, but following these best practices will ensure optimal performance and stay within ActivityKit's payload size limits.
+
+## Element deduplication
+
+Reuse JSX elements by creating them once, storing them in variables, and reusing them across your JSX tree. Voltra automatically detects duplicate element references and stores them only once in the payload, using lightweight references (`{ $r: index }`) for subsequent occurrences.
+
+```tsx
+import { Voltra } from 'voltra'
+
+// ✅ Good: Create element once and reuse
+const sharedButton = Click me
+const sharedIcon =
+
+const variants = {
+ lockScreen: (
+
+ Order Status
+ {sharedButton}
+ {sharedIcon}
+
+ ),
+ island: {
+ minimal: (
+
+ {sharedIcon}
+ {sharedButton}
+
+ ),
+ },
+}
+```
+
+**Payload result:**
+
+```json
+{
+ "v": 2,
+ "e": [
+ { "t": 1, "p": { "onPress": "action" }, "c": "Click me" },
+ { "t": 2, "p": { "name": "star.fill" } }
+ ],
+ "ls": {
+ "t": 11,
+ "c": [{ "t": 0, "c": "Order Status" }, { "$r": 0 }, { "$r": 1 }]
+ },
+ "isl_min": {
+ "t": 12,
+ "c": [{ "$r": 1 }, { "$r": 0 }]
+ }
+}
+```
+
+**Benefits:**
+
+- Eliminates redundant element serialization
+- Can significantly reduce payload size for complex UIs
+- Works across different Live Activity variants
+- Automatic - no additional code required
+
+```tsx
+// ❌ Avoid: Creating separate element instances
+const variants = {
+ lockScreen: (
+
+ Order Status
+ Click me // Duplicate instance
+ // Duplicate instance
+
+ ),
+ island: {
+ minimal: (
+
+ // Another duplicate instance
+ Click me // Another duplicate instance
+
+ ),
+ },
+}
+```
+
+## Image optimization
+
+Images can significantly impact payload size. For comprehensive guidance on handling images in Live Activities, see the [Images](images) documentation, which covers:
+
+- Base64 encoding for small static images
+- Build-time asset copying for medium-sized images
+- Runtime preloading for dynamic images
+
+Choose the appropriate approach based on your image size, when it's known, and whether it changes dynamically.
+
+## Reuse style objects
+
+Reuse style objects and avoid inline styles when possible:
+
+```tsx
+// ✅ Good: Shared style objects
+const buttonStyle = { padding: 8, borderRadius: 6 }
+const textStyle = { fontSize: 14, color: '#666' }
+
+
+ Title
+ Action
+
+
+// ❌ Avoid: Inline styles everywhere
+
+ Title
+ Action
+
+```
diff --git a/website/docs/v1/ios/development/querying-active-widgets.md b/website/docs/v1/ios/development/querying-active-widgets.md
new file mode 100644
index 00000000..b6edd4a9
--- /dev/null
+++ b/website/docs/v1/ios/development/querying-active-widgets.md
@@ -0,0 +1,41 @@
+# Querying Active Widgets
+
+Voltra allows you to detect which widgets the user has currently placed on their Home Screen. This is useful for:
+
+- Determining if you need to update a specific widget
+- Showing a list of active widgets in your app settings
+- Optimizing background updates by only targeting installed widgets
+
+## getActiveWidgets API
+
+The `getActiveWidgets` function returns a promise that resolves to an array of all active widget configurations for your app.
+
+:::warning
+There may be a slight delay in the data returned by this API. iOS caches widget configurations, and it might take a few moments for the system to reflect recent additions or removals of widgets on the Home Screen.
+:::
+
+```typescript
+import { getActiveWidgets } from 'voltra/client'
+
+async function checkWidgets() {
+ const activeWidgets = await getActiveWidgets()
+
+ console.log(`User has ${activeWidgets.length} widgets installed`)
+
+ activeWidgets.forEach(widget => {
+ console.log(`- Widget Name: ${widget.name}`)
+ console.log(` Family: ${widget.family}`)
+ console.log(` Kind: ${widget.kind}`)
+ })
+}
+```
+
+### WidgetInfo Object
+
+Each object in the returned array contains:
+
+| Property | Type | Description |
+| :--- | :--- | :--- |
+| `name` | `string` | The unique ID of the widget as defined in your Expo config plugin (e.g., `"weather"`). |
+| `kind` | `string` | The internal identifier string used by iOS (e.g., `"Voltra_Widget_weather"`). |
+| `family` | `WidgetFamily` | The size of the widget instance (e.g., `"systemSmall"`, `"systemMedium"`, etc.). |
diff --git a/website/docs/v1/ios/development/server-driven-widgets.md b/website/docs/v1/ios/development/server-driven-widgets.md
new file mode 100644
index 00000000..8318380e
--- /dev/null
+++ b/website/docs/v1/ios/development/server-driven-widgets.md
@@ -0,0 +1,278 @@
+# Server-driven widgets
+
+Server-driven widgets allow your Home Screen widgets to periodically fetch fresh content from a remote server—without the user opening the app. This is ideal for widgets that display dynamic data like weather, news, stock prices, or live scores.
+
+Before you start, make sure the widget is registered in the Voltra plugin config and plan to rebuild the native app after adding or changing server-driven widget settings.
+
+## How it works
+
+1. You configure a `serverUpdate` URL in your widget's plugin config
+2. iOS WidgetKit calls your server at the configured interval
+3. Your server renders Voltra JSX components into a JSON payload
+4. The widget extension parses the payload and updates the widget
+
+The entire lifecycle is managed by the OS timeline system. Your app doesn't need to be running.
+
+## Plugin configuration
+
+Add the `serverUpdate` option to your widget in `app.json` or `app.config.js`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "widgets": [
+ {
+ "id": "dynamic_weather",
+ "displayName": "Dynamic Weather",
+ "description": "Weather with live server updates",
+ "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"],
+ "serverUpdate": {
+ "url": "https://api.example.com/widgets/render",
+ "intervalMinutes": 30
+ }
+ }
+ ]
+ }
+ ]
+ ]
+ }
+}
+```
+
+**`serverUpdate` options:**
+
+- `url`: The Voltra SSR endpoint that returns widget JSON. Voltra appends `widgetId`, `platform`, `family`, and `theme` query parameters automatically (e.g. `?widgetId=dynamic_weather&platform=ios&family=systemSmall&theme=dark`).
+- `intervalMinutes`: How often the widget fetches updates. Defaults to `15`. iOS WidgetKit may throttle requests; the minimum effective interval is ~15 minutes.
+- `refresh`: Whether to show a native refresh button in the top-right corner of the widget. When tapped, triggers an immediate server fetch. Defaults to `false`. Requires iOS 17+.
+
+After updating plugin configuration, run `npx expo prebuild` if you're using Continuous Native Generation, then rebuild the app so the generated native files and widget extension pick up the new server update settings.
+
+## Building the server
+
+Voltra provides widget server handlers for the common runtime styles. Use `createWidgetUpdateHandler()` for Fetch-compatible runtimes, `createWidgetUpdateNodeHandler()` for `node:http`, and `createWidgetUpdateExpressHandler()` for Express-style handlers. All three share the same request parsing, platform validation, token validation, and response serialization.
+
+```tsx
+import { createServer } from 'node:http'
+import React from 'react'
+import { createWidgetUpdateNodeHandler, Voltra } from 'voltra/server'
+
+const handler = createWidgetUpdateNodeHandler({
+ renderIos: async (req) => {
+ // req.widgetId — the widget requesting an update
+ // req.platform — always "ios" for iOS widget requests
+ // req.family — the widget size ("systemSmall", "systemMedium", etc.)
+ // req.theme — the system color scheme ("light" or "dark")
+ // req.token — the auth token (if credentials were set)
+
+ const weather = await fetchWeatherData()
+
+ return {
+ systemSmall: (
+
+ {weather.temp}°
+ {weather.condition}
+
+ ),
+ systemMedium: (
+
+ {weather.temp}°
+
+ {weather.condition}
+
+ H: {weather.high}° L: {weather.low}°
+
+
+
+ ),
+ }
+ },
+
+ validateToken: async (token) => {
+ // Return true if the token is valid, false to reject with 401
+ return token === 'valid-token'
+ },
+})
+
+createServer(handler).listen(3333, () => {
+ console.log('Widget server running on http://localhost:3333')
+})
+```
+
+The handler responds to GET requests with these query parameters:
+
+| Parameter | Description |
+|-----------|-------------|
+| `widgetId` | The widget identifier (required) |
+| `platform` | The requesting platform. Must be `ios` for iOS widgets (required). |
+| `family` | The widget family/size (iOS only) |
+| `theme` | The system color scheme (`light` or `dark`) |
+
+The `Authorization: Bearer ` header is automatically extracted and passed to `validateToken` and `renderIos`. The `User-Agent` header is set to `VoltraWidget/1.0 (iOS/)`.
+
+For Fetch-native runtimes, use `createWidgetUpdateHandler()` instead of the Node adapter:
+
+```tsx
+import { createWidgetUpdateHandler, Voltra } from 'voltra/server'
+
+export const GET = createWidgetUpdateHandler({
+ renderIos: async (req) => ({
+ systemSmall: {req.widgetId},
+ }),
+})
+```
+
+## Authentication
+
+Widgets run in a separate extension process and can't access your app's network layer or auth state. Voltra solves this by storing credentials in the **Shared Keychain**, which is accessible by both the main app and the widget extension.
+
+### Setting credentials
+
+Call `setWidgetServerCredentials` after the user logs in:
+
+```typescript
+import { setWidgetServerCredentials } from 'voltra/client'
+
+await setWidgetServerCredentials({
+ token: userAccessToken,
+ headers: {
+ 'X-App-Version': '1.0.0',
+ },
+})
+```
+
+The `token` is required and is sent as `Authorization: Bearer ` on every server request. Any additional `headers` are also included. If your widget endpoint does not require authentication, skip `setWidgetServerCredentials()` entirely.
+
+### Clearing credentials
+
+Call `clearWidgetServerCredentials` when the user logs out:
+
+```typescript
+import { clearWidgetServerCredentials } from 'voltra/client'
+
+await clearWidgetServerCredentials()
+```
+
+All widget timelines are automatically reloaded after credentials are cleared, so widgets revert to their default/unauthenticated state immediately.
+
+### Keychain group
+
+For credentials to be shared between the main app and the widget extension, both must belong to the same Keychain Access Group. This is configured via the `keychainGroup` plugin option:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "keychainGroup": "$(AppIdentifierPrefix)com.example.shared",
+ "widgets": [...]
+ }
+ ]
+ ]
+ }
+}
+```
+
+If you don't specify `keychainGroup` but any widget has `serverUpdate` configured, Voltra automatically derives a default: `$(AppIdentifierPrefix)`.
+
+## Refresh button
+
+Server-driven widgets can display a native refresh button that lets users trigger an immediate update on demand. Enable it in your widget config:
+
+```json
+{
+ "serverUpdate": {
+ "url": "https://api.example.com/widgets/render",
+ "intervalMinutes": 30,
+ "refresh": true
+ }
+}
+```
+
+When enabled, a small circular button (↻) appears in the top-right corner of the widget. Tapping it triggers `reloadTimelines(ofKind:)` via an `AppIntent`, which causes WidgetKit to immediately fetch fresh content from your server.
+
+:::note
+The refresh button requires iOS 17+ (`AppIntent` API). On older iOS versions, the button is not shown.
+:::
+
+### Fetch coalescing
+
+When WidgetKit reloads timelines, it may call `getTimeline` multiple times for each supported family (e.g. `systemSmall`, `systemMedium`). To avoid redundant network requests, Voltra coalesces fetches within a 3-second window per widget. Only the first call triggers a server fetch; subsequent calls within the window use cached data and `selectContentForFamily` picks the correct family-specific content.
+
+## Triggering manual refreshes
+
+You can force-refresh server-driven widgets outside of the regular interval:
+
+```typescript
+import { reloadWidgets } from 'voltra/client'
+
+// Reload specific widgets
+await reloadWidgets(['dynamic_weather'])
+
+// Reload all widgets
+await reloadWidgets()
+```
+
+This triggers an immediate timeline refresh, which causes WidgetKit to call your server for new content.
+
+## Initial state
+
+Server-driven widgets still need content to display before the first server fetch completes (e.g. when the widget is first added to the Home Screen). Use `initialStatePath` to provide a pre-rendered default:
+
+```json
+{
+ "id": "dynamic_weather",
+ "displayName": "Dynamic Weather",
+ "description": "Weather with live server updates",
+ "supportedFamilies": ["systemSmall", "systemMedium"],
+ "initialStatePath": "./widgets/ios/weather-initial.tsx",
+ "serverUpdate": {
+ "url": "https://api.example.com/widgets/render",
+ "intervalMinutes": 30
+ }
+}
+```
+
+See [Widget pre-rendering](./widget-pre-rendering) for details on creating initial state files.
+
+:::tip
+Provide a meaningful initial state (e.g. "Loading..." or placeholder content) rather than leaving it empty. The user sees this until the first server fetch succeeds.
+:::
+
+## Architecture overview
+
+```
+┌─────────────────┐ setWidgetServerCredentials() ┌──────────────────┐
+│ React Native │ ──────────────────────────────────► │ Shared Keychain │
+│ (main app) │ └──────────────────┘
+└─────────────────┘ │
+ │ reads token
+ ▼
+┌─────────────────┐ GET ?widgetId=X&platform=ios&family=Y&theme=Z ┌──────────────────┐
+│ WidgetKit │ ──────────────────────────────────► │ Your Server │
+│ (extension) │ ◄────────────────────────────────── │ (Voltra SSR) │
+└─────────────────┘ JSON payload └──────────────────┘
+ │
+ ▼
+ Home Screen Widget
+```
+
+WidgetKit manages the scheduling and calls your server at the configured interval. The widget extension reads credentials from the Shared Keychain, makes the HTTP request, and renders the response payload.
+
+## Error handling and retries
+
+When a server fetch fails, the widget extension falls back to the last successfully fetched data (or the initial state if no data has been fetched yet). WidgetKit schedules a retry after 15 minutes.
+
+- **Network error / timeout:** The widget falls back to cached content and retries in 15 minutes.
+- **Server errors (non-2xx):** Same fallback behavior — cached content is shown and a retry is scheduled in 15 minutes.
+- **Empty response:** Treated as an error; cached content is displayed.
+- **Parse errors:** If the server returns a 2xx response but the JSON can't be parsed into a valid widget tree, the cached data from the previous successful fetch is preserved (not overwritten). The widget continues to show the last known good content.
+
+:::note
+Unlike Android's WorkManager which retries with exponential backoff, iOS WidgetKit uses its own timeline-based scheduling. After a failed fetch, the timeline provider falls back to local data and schedules a retry in 15 minutes. WidgetKit may also throttle updates based on battery level and widget visibility.
+:::
diff --git a/website/docs/v1/ios/development/server-side-updates.md b/website/docs/v1/ios/development/server-side-updates.md
new file mode 100644
index 00000000..d35a31ec
--- /dev/null
+++ b/website/docs/v1/ios/development/server-side-updates.md
@@ -0,0 +1,327 @@
+# Server-side updates
+
+Voltra supports server-side updates for Live Activities through Apple Push Notification Service (APNS). This allows you to update Live Activities even when your app is in the background or terminated.
+
+## Configuration
+
+### Expo Config plugin setup
+
+To enable server-side updates via push notifications, you need to configure the Voltra plugin in your `app.json` or `app.config.js`:
+
+**Minimal configuration (push notifications only):**
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "enablePushNotifications": true
+ }
+ ]
+ ]
+ }
+}
+```
+
+**Full configuration (with App Groups for event forwarding and shared images):**
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "enablePushNotifications": true,
+ "groupIdentifier": "group.your.app.voltraui",
+ "deploymentTarget": "18.0"
+ }
+ ]
+ ]
+ }
+}
+```
+
+**Note:** The `groupIdentifier` is optional. It's only needed if you want to forward component events (like button taps) from the Live Activity extension to your JavaScript code, or if you need to share images between your app and the extension. Push notifications work perfectly fine without it.
+
+### Xcode configuration
+
+The Voltra config plugin automatically handles most of the Xcode configuration for you:
+
+- Adds the `aps-environment` entitlement (set to `development` for debug builds)
+- Configures Info.plist flags to enable push notification integration
+- Sets up App Group entitlements (if `groupIdentifier` is provided)
+
+However, you'll still need to complete a few manual steps:
+
+1. **Provisioning Profiles:** Ensure your app has proper provisioning profiles configured in Xcode with push notification capabilities enabled.
+
+2. **APNs Certificates:** Set up APNs authentication certificates or keys in your Apple Developer account. You'll need these to send push notifications from your server.
+
+3. **Capabilities:** Verify that Push Notifications capability is enabled in your app's target settings in Xcode.
+
+For detailed information about setting up push notifications in iOS, refer to [Apple's official push notification documentation](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server).
+
+## Server-side rendering
+
+Voltra provides a server-side API that allows you to render React components into JSON payloads that can be sent via APNS. The same Voltra components you use in your app work on the server.
+
+### Using the server-side API
+
+Import the server-side rendering functions from `voltra/server`:
+
+```tsx
+import { renderLiveActivityToString, Voltra } from 'voltra/server'
+
+// Render your UI components to a JSON string
+const jsonPayload = renderLiveActivityToString({
+ lockScreen: (
+
+
+ Driver arrived
+ Ready for pickup
+
+ ),
+})
+```
+
+The `renderLiveActivityToString` function accepts a `LiveActivityVariants` object and returns a compressed, base64-encoded JSON string for your APNS payload.
+
+## APNS payload format
+
+When sending push notifications to update Live Activities, you need to structure your APNS payload correctly. For detailed information about APNS payload structure, see Apple's [official ActivityKit push notification documentation](https://developer.apple.com/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications).
+
+You can also use Apple’s [CloudKit - Push Notifications tool](https://icloud.developer.apple.com/dashboard/notifications/) to send test push notifications during development.
+
+### Required APNS headers
+
+```
+apns-push-type: liveactivity
+apns-topic: .push-type.liveactivity
+```
+
+Replace `` with your app's bundle identifier (e.g., `com.example.myapp`).
+
+### Payload structure
+
+**For updating an existing Live Activity:**
+
+```json
+{
+ "aps": {
+ "event": "update",
+ "content-state": {
+ "uiJsonData": "{\"lockScreen\":{\"type\":\"VStack\",\"children\":{\"type\":\"Text\",\"children\":\"Hello\",\"props\":{}},\"props\":{}}}"
+ },
+ "timestamp": 1764145755
+ }
+}
+```
+
+**For starting a Live Activity remotely (iOS 17.2+):**
+
+```json
+{
+ "aps": {
+ "event": "start",
+ "content-state": {
+ "uiJsonData": "{\"lockScreen\":{\"type\":\"VStack\",\"children\":{\"type\":\"Text\",\"children\":\"Hello\",\"props\":{}},\"props\":{}}}"
+ },
+ "attributes-type": "VoltraAttributes",
+ "attributes": {
+ "name": "some-name",
+ "deepLinkUrl": "app://some-deep-link-url"
+ },
+ "timestamp": 1764145755,
+ "alert": {
+ "title": "Driver arrived",
+ "body": "Ready for pickup"
+ }
+ }
+}
+```
+
+**For ending an existing Live Activity:**
+
+```json
+{
+ "aps": {
+ "timestamp": 1683012400,
+ "event": "end",
+ "content-state": {
+ "uiJsonData": "{\"lockScreen\":{\"type\":\"VStack\",\"children\":{\"type\":\"Text\",\"children\":\"Hello\",\"props\":{}},\"props\":{}}}"
+ },
+ "dismissal-date": 1683016000
+ }
+}
+```
+
+**Key fields:**
+
+- `aps.event`: Set to `"update"` to update, `"end"` to end, or `"start"` for push-to-start (iOS 17.2+)
+- `aps.content-state.uiJsonData`: The JSON string returned by `renderLiveActivityToString`, embedded as a string value eg. `ixOAeyJ2IjoxLCJscyI6eyJ0IjowLCJjIjoiSGVsbG8sIHdvcmxkISJ9fQM=`. This must be sent for `update` and `end` events.
+- `aps.timestamp`: Unix timestamp in seconds (required for Live Activities)
+- `aps.attributes-type`: For push-to-start, must be `"VoltraAttributes"`
+- `aps.attributes.name`: For push-to-start, a user-defined name for the activity (can be any string you choose)
+- `aps.alert`: Required field for push-to-start
+
+For `event: "end"`, include `aps.dismissal-date`.
+To remove the Live Activity from the Lock Screen right after it ends, set `dismissal-date` to a timestamp in the past (for example, 1663177260).
+You can also set a timestamp up to four hours in the future to control when it’s dismissed.
+
+:::danger
+ActivityKit enforces a strict payload size limit of approximately 4 KB. Keep your UI JSON minimal to stay within this limit. Avoid deeply nested component trees and excessive styling to ensure your payloads fit within the constraint.
+:::
+
+## Getting push tokens
+
+Voltra provides specialized push tokens that are different from regular device tokens. These tokens are specifically designed for Live Activity push notifications and should be used instead of standard device tokens.
+
+### Tokens for updating Live Activities
+
+To update existing Live Activities, use activity push tokens:
+
+```tsx
+import { addVoltraListener } from 'voltra/client'
+
+useEffect(() => {
+ const subscription = addVoltraListener('activityTokenReceived', ({
+ activityName,
+ pushToken,
+ timestamp,
+ type
+ }) => {
+ // Send the token to your server for updating this specific Live Activity
+ sendTokenToServer({
+ activityID,
+ token: pushToken,
+ type: 'update',
+ })
+ })
+
+ return () => {
+ subscription.remove()
+ }
+}, [])
+```
+
+### Tokens for starting Live Activities remotely
+
+To start Live Activities remotely (push-to-start), use push-to-start tokens:
+
+```tsx
+useEffect(() => {
+ const subscription = addVoltraListener('activityPushToStartTokenReceived', ({ pushToStartToken, type }) => {
+ // Send the token to your server for starting new Live Activities
+ sendTokenToServer({
+ token: pushToStartToken,
+ type: 'start',
+ })
+ })
+
+ return () => {
+ subscription.remove()
+ }
+}, [])
+```
+
+Use only Voltra-provided tokens, which are specialized for Live Activity push notifications and different from regular device tokens. Update tokens are tied to specific Live Activities, while push-to-start tokens are for starting new activities. Update tokens are provided when Live Activities are started and may change during the activity's lifecycle. When sending notifications via APNS, use these push tokens as the target device token to route notifications to the correct Live Activity or device.
+
+## Broadcast push notifications (iOS 18+)
+
+Starting with iOS 18 and iPadOS 18, you can use **broadcast push notifications** to update many Live Activities with a single push notification. Instead of sending individual notifications to each device token, you send one broadcast to a shared channel—all Live Activities subscribed to that channel receive the update. This is ideal for scenarios like live sports scores or flight status where many users follow the same event.
+
+### Prerequisites
+
+1. **Enable Broadcast Capability:** In your [Apple Developer account](https://developer.apple.com/account), go to Certificates, Identifiers & Profiles > Identifiers, select your App ID, and enable **Broadcast Capability** under Push Notifications.
+
+2. **Create a channel:** Your server creates a channel via APNs and receives a channel ID. You can maintain up to 10,000 channels per app. Use [Apple Push Notification Console](https://icloud.developer.apple.com/dashboard/notifications/) or the [channel management API](https://developer.apple.com/documentation/usernotifications/sending-channel-management-requests-to-apns) to create channels.
+
+3. **Plugin configuration:** Keep `enablePushNotifications: true` in your Voltra plugin config—the `aps-environment` entitlement is still required for broadcast push.
+
+### Starting a Live Activity with a channel
+
+Pass the `channelId` option when starting a Live Activity to subscribe it to a broadcast channel:
+
+```typescript
+import { startLiveActivity } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+const activityId = await startLiveActivity(variants, {
+ activityName: 'match-123',
+ channelId: 'CTrNsYq/Ee8AALLzHQaVlA==', // Channel ID from your server
+})
+```
+
+When `channelId` is provided, the Live Activity subscribes to broadcast updates on iOS 18+. On iOS versions before 18, broadcast is unavailable and Voltra falls back to token-based Live Activity push updates.
+
+### Sending broadcast updates
+
+To update all Live Activities on a channel, send a POST request to APNs with:
+
+- **Path:** `/4/broadcasts/apps/` (bundle ID without the `.push-type.liveactivity` suffix)
+- **Header:** `apns-channel-id: `
+- **Payload:** Same structure as individual updates—`event: "update"`, `content-state`, `timestamp`, etc.
+
+For the full broadcast payload format and headers, see [Apple's broadcast push documentation](https://developer.apple.com/documentation/usernotifications/sending-broadcast-push-notification-requests-to-apns).
+
+### Broadcast vs. individual tokens
+
+| Aspect | Individual tokens | Broadcast |
+|--------|-------------------|-----------|
+| Server sends | One notification per device | One notification per channel |
+| `activityTokenReceived` event | Fires for each activity | Does not fire |
+| Best for | Per-user content (orders, rides) | Shared content (scores, flights) |
+| iOS version | 16.2+ | 18+ |
+
+## Handling background execution
+
+When Live Activity tokens change or need to be refreshed, iOS may wake your app in the background to deliver new tokens. The app has a limited window of time (typically around 30 seconds) to handle the event and communicate with your server before iOS may suspend or terminate the background process.
+
+### Detecting background execution
+
+Use the `isHeadless()` function to determine if your app is running in the background:
+
+```typescript
+import { isHeadless } from 'voltra/client'
+
+// In your app's entry point or root component
+if (isHeadless()) {
+ // App is running in background/headless mode
+ console.log('App launched in headless mode for token refresh')
+} else {
+ // App is running in foreground
+ console.log('App launched in foreground')
+}
+```
+
+### Optimizing for background execution
+
+When the app is launched in headless mode for token handling, **do not mount your main app component** to avoid excessive resource usage that could cause iOS to terminate the app before token updates are processed.
+
+```typescript
+// In your app's entry point (e.g., App.tsx, index.js)
+import { isHeadless, addVoltraListener } from 'voltra/client'
+
+function App() {
+ // Handle token events even in headless mode
+ useEffect(() => {
+ const subscription = addVoltraListener('activityTokenReceived', (event) => {
+ // Process token updates and send to server
+ sendTokenToServer(event)
+ })
+
+ return () => subscription.remove()
+ }, [])
+
+ // Only render the UI if not in headless mode
+ if (isHeadless()) {
+ return null // Don't mount the app component tree
+ }
+
+ // Render your normal app UI
+ return
+}
+```
diff --git a/website/docs/v1/ios/development/styling.md b/website/docs/v1/ios/development/styling.md
new file mode 100644
index 00000000..4cea5b83
--- /dev/null
+++ b/website/docs/v1/ios/development/styling.md
@@ -0,0 +1,372 @@
+# Styling
+
+You can style Voltra components using React Native-style `style` props. The `style` prop works with a limited set of React Native properties that are automatically converted to SwiftUI modifiers under the hood.
+
+## React Native style prop
+
+Voltra supports a limited subset of React Native style properties. When you pass a `style` prop to a Voltra component, these properties are automatically converted to SwiftUI modifiers under the hood. This makes it easy to get started if you're familiar with React Native styling.
+
+### Supported properties
+
+The following React Native style properties are supported:
+
+**Layout:**
+
+- `width` - Fixed width (number values only, percentages are ignored)
+- `height` - Fixed height (number values only, percentages are ignored)
+- `flex` - Flex shorthand (follows Yoga's behavior). Positive values act as `flexGrow`, negative values act as `flexShrink`. Explicit `flexGrow`/`flexShrink` take precedence if both are specified.
+- `flexGrow` - Flex grow factor. When > 0, allows the view to grow to fill available space (converts to flexible frame with `maxWidth`/`maxHeight` set to infinity)
+- `flexShrink` - Flex shrink factor. When > 0, allows the view to shrink below its ideal size (sets `minWidth`/`minHeight` to 0)
+- `padding` - Uniform padding on all edges
+- `paddingTop`, `paddingBottom`, `paddingLeft`, `paddingRight` - Individual edge padding
+- `paddingHorizontal`, `paddingVertical` - Horizontal and vertical padding
+- `margin`, `marginTop`, `marginBottom`, `marginLeft`, `marginRight`, `marginHorizontal`, `marginVertical` - All margin properties are mapped to padding in SwiftUI
+
+**Positioning:**
+
+- `position` - Positioning mode: `'static'` (default), `'relative'`, or `'absolute'`
+- `left` - Horizontal position coordinate (used with `position`)
+- `top` - Vertical position coordinate (used with `position`)
+- `zIndex` - Z-order of the element
+
+**Style:**
+
+- `backgroundColor` - Background color (hex strings, color names, or CSS gradient strings — see [Gradients](#gradients))
+- `opacity` - Opacity value between 0 and 1
+- `borderRadius` - Corner radius value
+- `borderWidth` - Border width
+- `borderColor` - Border color
+
+**Shadow:**
+
+- `shadowColor` - Shadow color
+- `shadowOffset` - Shadow offset (`{ width: number, height: number }`)
+- `shadowOpacity` - Shadow opacity
+- `shadowRadius` - Shadow blur radius
+
+**Text:**
+
+- `fontSize` - Font size (maps to `font` modifier)
+- `fontWeight` - Font weight (e.g., `'600'`, `'bold'`, `'regular'`)
+- `fontFamily` - Custom font family name (see [Custom Fonts](#custom-fonts) section below)
+- `color` - Text color (maps to `foregroundStyle` modifier)
+- `letterSpacing` - Spacing between characters (maps to `kerning` modifier)
+- `fontVariant` - Font variant array (e.g., `['small-caps', 'tabular-nums']`). Supported values:
+ - `'small-caps'` - Applies small caps styling (iOS 14+)
+ - `'tabular-nums'` - Applies monospaced digits (iOS 15+)
+
+**Effects:**
+
+- `overflow: 'hidden'` - Clips content to bounds (maps to `clipped` modifier)
+
+### Flexbox Properties (Opt-in)
+
+When using the `View` component or enabling flexbox mode on `VStack`/`HStack` with `layout="flex"`, additional flexbox properties become available via the `style` prop:
+
+**Container Properties:**
+- `flexDirection`: `'row'` | `'column'` - Main axis direction
+- `alignItems`: `'flex-start'` | `'center'` | `'flex-end'` | `'stretch'` - Cross-axis alignment
+- `justifyContent`: `'flex-start'` | `'center'` | `'flex-end'` | `'space-between'` | `'space-around'` | `'space-evenly'` - Main-axis distribution
+- `gap`: number - Spacing between children along the main axis (one-axis only, no columnGap/rowGap)
+
+**Child Properties:**
+- `flex`: number - Shorthand for flexGrow/flexShrink
+- `flexGrow`: number - Growth factor
+- `flexShrink`: number - Shrink factor
+- `flexBasis`: number | 'auto' - Base size
+- `alignSelf`: Override parent's alignItems
+
+:::note
+Flexbox properties only work when flexbox layout is enabled. See [Flexbox Layout](./flexbox-layout) for comprehensive documentation.
+:::
+
+### Limitations
+
+Properties not listed above are ignored during rendering. This includes common React Native properties like:
+
+- Flexbox layout properties (`flexDirection`, `justifyContent`, `alignItems`, `gap`, etc.) are only available when using flexbox layout. Use the `View` component or set `layout="flex"` on VStack/HStack to enable these properties. See [Flexbox Layout](./flexbox-layout) for details. Properties `flex`, `flexGrow`, `flexShrink`, `flexBasis`, and `alignSelf` work on children inside flexbox containers.
+- `columnGap`, `rowGap`, and `flexWrap` properties - Voltra only supports a single `gap` value along the main axis, and does not support wrapping
+- Percentage-based widths and heights
+- `right` and `bottom` positioning properties - Only `left` and `top` are supported
+- Most text styling properties beyond `fontSize`, `fontWeight`, `fontFamily`, `color`, `letterSpacing`, and `fontVariant`
+- **Live Update Overrides**: Certain styling properties (like `height` or `borderRadius` on progress bars) may be ignored when using live-updating features like `timerInterval` to ensure compatibility with smooth system animations.
+
+:::tip Positioning in Voltra
+
+Voltra supports CSS-style positioning with three modes:
+
+- **`position: 'static'`** - Normal layout flow. `left` and `top` are ignored.
+- **`position: 'relative'`** - Offsets the element from its natural position using `left` and `top`. The offset moves the element right (positive `left`) and down (positive `top`).
+- **`position: 'absolute'`** (default when `left`/`top` provided) - Positions the element's **center** at the coordinates specified by `left` and `top`. This differs from CSS which positions from the top-left corner, but matches SwiftUI's native behavior.
+
+**Note**: If you provide `left` or `top` without specifying `position`, it defaults to `'absolute'` for backward compatibility. To ignore `left`/`top`, explicitly set `position: 'static'`.
+
+For most layouts, prefer using stack `alignment` props (`ZStack`, `VStack`, `HStack`) which provide better layout control. Use positioning for fine-tuning or overlays.
+
+See the [Layout & Containers](../components/layout) documentation for details on alignment.
+
+:::
+
+### Example
+
+```tsx
+import { Voltra } from 'voltra'
+
+const element = (
+
+
+ Styled Text
+
+
+)
+```
+
+## Gradients
+
+The `backgroundColor` style property accepts CSS gradient strings in addition to solid colors. Gradients are rendered natively using SwiftUI gradient modifiers and are automatically clipped by `borderRadius`.
+
+Invalid or unsupported gradient syntax is parsed in **strict mode** and results in **no gradient background** (instead of silent best-effort fallback).
+
+### Linear gradients
+
+```tsx
+// Named direction
+
+
+// Diagonal
+
+
+// Angle in degrees/radians/turns
+
+
+```
+
+Supported directions: `to right`, `to left`, `to top`, `to bottom`, `to top right`, `to top left`, `to bottom right`, `to bottom left`.
+
+### Color stops
+
+Explicit percentage positions are supported:
+
+```tsx
+
+```
+
+When positions are omitted, Voltra applies CSS-like stop fix-up:
+- First and last unspecified stops default to `0%` and `100%`.
+- Unspecified stops between explicit anchors are linearly interpolated.
+- Non-monotonic explicit positions are clamped forward.
+
+### RGBA colors inside gradients
+
+```tsx
+
+```
+
+### Radial gradients
+
+```tsx
+
+
+
+```
+
+:::note
+Radial gradients are rendered with geometry-aware radii computed from the view size (`closest-side`, `farthest-side`, `closest-corner`, `farthest-corner`).
+
+For `ellipse`, SwiftUI does not provide native elliptical radial gradients. Voltra approximates ellipse behavior by scaling a circular radial gradient.
+:::
+
+### Conic gradients
+
+```tsx
+
+
+```
+
+### With border radius
+
+Gradients are clipped by `borderRadius` automatically — no extra configuration needed:
+
+```tsx
+
+ Rounded gradient card
+
+```
+
+### Solid colors still work unchanged
+
+Passing a plain color string to `backgroundColor` continues to work exactly as before:
+
+```tsx
+
+
+
+```
+
+### Comparison with `` component
+
+| Feature | `backgroundColor` gradient | `` component |
+|---|---|---|
+| CSS string syntax | ✓ | — |
+| Named directions (`to right`) | ✓ (physical direction) | ✓ |
+| Angle units | `deg`, `rad`, `turn` | ✓ via `{x,y}` |
+| `{x, y}` coordinate control | — | ✓ |
+| Stop positions | `linear/radial`: `%`, `conic`: `%` + angle units | ✓ via `locations` prop |
+| Multi-position stops (`red 20% 40%`) | ✓ | — |
+| `radial-gradient` | ✓ (`circle` / approximated `ellipse`) | — |
+| `conic-gradient` | ✓ (`from` + `at`) | — |
+| Strict invalid syntax handling | ✓ (fails closed) | — |
+| Dithering | — | ✓ |
+| Children layered on top | ✓ (as background) | ✓ (as container) |
+
+Use `backgroundColor` gradient strings for convenience and web-style syntax. Use `` when you need precise `{x, y}` coordinate control or dithering.
+
+### Scope and exclusions
+
+- Supported core syntax: `linear-gradient(...)`, `radial-gradient(...)`, `conic-gradient(...)`.
+- Not supported in this parser: `repeating-linear-gradient(...)`, `repeating-radial-gradient(...)`, `repeating-conic-gradient(...)`.
+
+## Custom Fonts
+
+Voltra supports custom fonts through the `fontFamily` style property.
+
+### Adding Custom Fonts to Your Project
+
+Voltra supports custom fonts in your Live Activities and Widgets through two main methods:
+
+#### 1. Using Voltra's Font Configuration (Recommended)
+
+The simplest way is to specify fonts directly in the Voltra plugin configuration. This follows the same pattern as `expo-font`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "groupIdentifier": "group.com.example.app",
+ "fonts": ["./assets/fonts", "./assets/custom-font.ttf"]
+ }
+ ]
+ ]
+ }
+}
+```
+
+The `fonts` array can include:
+- Individual font files: `"./assets/fonts/CustomFont.ttf"`
+- Entire directories: `"./assets/fonts"` (all fonts in the directory will be included)
+- Supported formats: `.ttf`, `.otf`, `.woff`, `.woff2`
+
+#### 2. Adding Fonts Manually in Xcode
+
+For non-Expo projects or if you prefer manual configuration, you can add fonts directly to your Xcode project:
+
+1. Add your font files (`.otf` or `.ttf`) to your Xcode project
+2. Ensure they're included in the Live Activity target's "Copy Bundle Resources" build phase
+3. Add the font file names to your `Info.plist` under the `UIAppFonts` key for Live Activity target
+
+For detailed instructions, see Apple's documentation on [Applying custom fonts to text](https://developer.apple.com/documentation/swiftui/applying-custom-fonts-to-text).
+
+### Using Custom Fonts
+
+Once your fonts are added to the project, you can use them with the `fontFamily` style property:
+
+```tsx
+import { Voltra } from 'voltra'
+
+const element = (
+
+ Text with Custom Font
+
+)
+```
+
+:::tip Font Family Names
+
+The font family name you use in `fontFamily` should match the font's PostScript name, not the file name. You can find the PostScript name:
+- In the Font Book app on macOS
+- Using online tools like [fontdrop.info](https://fontdrop.info)
+- In Xcode's font picker
+
+For example, the font file `Inter-Bold.ttf` has the PostScript name `Inter-Bold`.
+
+:::
+
+### Font Weight with Custom Fonts
+
+When using `fontFamily`, the `fontWeight` style property is ignored since you typically specify the exact font variant (e.g., `Inter-Bold`, `Inter-Regular`). If you need different weights, add multiple font files and specify the complete font name:
+
+```tsx
+// Regular weight
+
+ Regular Text
+
+
+// Bold weight
+
+ Bold Text
+
+```
+
+### Example with Google Fonts
+
+If you're using Google Fonts via `@expo-google-fonts`, they work seamlessly with Voltra:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "expo-font",
+ {
+ "fonts": ["node_modules/@expo-google-fonts/inter/Inter_400Regular.ttf"]
+ }
+ ]
+ ]
+ }
+}
+```
+
+```tsx
+
+ Text using Google Font
+
+```
+
+:::note System Font Fallback
+If `fontFamily` is not specified or the font cannot be found, Voltra will fall back to the system font with the specified `fontWeight`.
+:::
diff --git a/website/docs/v1/ios/development/widget-pre-rendering.md b/website/docs/v1/ios/development/widget-pre-rendering.md
new file mode 100644
index 00000000..8eea604a
--- /dev/null
+++ b/website/docs/v1/ios/development/widget-pre-rendering.md
@@ -0,0 +1,57 @@
+# Widget Pre-rendering
+
+Widget pre-rendering allows you to provide meaningful initial state for widgets before they are synced when the app runs for the first time.
+
+## Configuration
+
+Add `initialStatePath` to your widget configuration in `app.json`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "widgets": [
+ {
+ "id": "weather",
+ "displayName": "Weather Widget",
+ "description": "Shows current weather conditions",
+ "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"],
+ "initialStatePath": "./widgets/weather-initial.tsx"
+ }
+ ]
+ }
+ ]
+ ]
+ }
+}
+```
+
+## Implementation
+
+Create a file at the specified `initialStatePath` that exports a `WidgetVariants` object:
+
+```tsx
+import { Voltra, type WidgetVariants } from 'voltra'
+
+const initialState: WidgetVariants = {
+ systemSmall: Content,
+ systemMedium: Content,
+ systemLarge: Content,
+}
+
+export default initialState
+```
+
+## Build Process
+
+During build time, Voltra transpiles your widget files with Babel and executes them in a Node.js environment to generate initial states that are bundled into the iOS app.
+
+## Limitations
+
+- **Node.js Environment**: Code runs in Node.js, not in React Native or iOS
+- **Babel Support**: TypeScript is supported via Babel transpilation
+- **No Bundling**: Import resolution works for local files but there is no bundler involved
+- **Voltra Imports**: Do not use `voltra/client` or `voltra/server` imports; use `voltra` instead
diff --git a/website/docs/v1/ios/introduction.md b/website/docs/v1/ios/introduction.md
new file mode 100644
index 00000000..2bf2eee8
--- /dev/null
+++ b/website/docs/v1/ios/introduction.md
@@ -0,0 +1,56 @@
+# Introduction
+
+Adding live activities to your iOS app has traditionally been a time-consuming and complex process. JavaScript developers need to learn Xcode, master SwiftUI, understand how to start live activities, and figure out how to manage them throughout their lifecycle. This creates barriers between your app development and these dynamic features.
+
+## Voltra + JSX = Live Activity
+
+Voltra changes all of that by providing a JavaScript-based API you can use to display live activities in your app. Instead of writing SwiftUI code, you write JSX using Voltra components that get automatically converted to SwiftUI and displayed just like native code.
+
+Here's how simple it is to create a live activity:
+
+```tsx
+import { startLiveActivity } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+const activityUI = (
+
+
+ Driver en route
+ Building A · Lobby pickup
+
+ Contact driver
+
+
+)
+
+// Start the live activity
+await startLiveActivity({
+ lockScreen: activityUI,
+})
+```
+
+If you prefer using the hook API (`useLiveActivity`), you'll get live reloads for live activities, with changes appearing in milliseconds without manual restarts.
+
+## Server-side updates via push notifications
+
+Voltra also supports server-side updates through push notifications. You can use Voltra's server-side rendering to convert JSX into JSON payloads that you send to devices via Apple's Push Notification Service (APNS). This enables real-time updates without keeping your app running.
+
+The same components you use in your app work on the server:
+
+```tsx
+import { renderLiveActivityToString } from 'voltra/server'
+import { Voltra } from 'voltra'
+
+// Render JSX to JSON payload on your server
+const payload = renderLiveActivityToString({
+ lockScreen: (
+
+
+ Driver arrived
+ Ready for pickup
+
+ ),
+})
+```
+
+You're ready to dive into the [setup guide](./setup) and get started with live activities in your app.
diff --git a/website/docs/v1/ios/setup.mdx b/website/docs/v1/ios/setup.mdx
new file mode 100644
index 00000000..9e573d57
--- /dev/null
+++ b/website/docs/v1/ios/setup.mdx
@@ -0,0 +1,79 @@
+import { PackageManagerTabs } from '@rspress/core/theme'
+
+# iOS Setup
+
+Once you have [installed Voltra](../getting-started/installation), you need to configure the Expo plugin for iOS.
+
+:::info Minimum iOS Version
+Voltra requires **iOS 16.2 or later**. Make sure your app's deployment target is set to iOS 16.2 or higher. See the [expo-build-properties](https://docs.expo.dev/build-reference/variables/) documentation for details on configuring your iOS deployment target.
+:::
+
+## 1. Configure the Expo Plugin
+
+Add the Voltra plugin to your `app.json` or `app.config.js`. For iOS, you typically need to specify a `groupIdentifier` to enable data sharing between your main app and the Live Activity extension.
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "voltra",
+ {
+ "groupIdentifier": "group.your.bundle.identifier",
+ "enablePushNotifications": true
+ }
+ ]
+ ]
+ }
+}
+```
+
+For a complete list of plugin configuration options, see the [Plugin Configuration](./api/plugin-configuration) documentation.
+
+## 2. Regenerate your native project with Expo Prebuild
+
+It's time to update your native project. Run the following command to regenerate it and install native dependencies for Voltra.
+
+
+
+## 3. Create your first Live Activity
+
+Now let's create a simple "Hello World" live activity. Create a new component:
+
+```tsx
+import React from 'react'
+import { startLiveActivity } from 'voltra/client'
+import { Voltra } from 'voltra'
+
+function HelloWorldActivity() {
+ const activityUI = (
+
+ Hello World from Voltra!
+ Your first live activity
+
+ )
+
+ const startActivity = async () => {
+ await startLiveActivity({
+ lockScreen: activityUI,
+ })
+ }
+
+ return (
+
+
+ Start Live Activity
+
+
+ )
+}
+```
+
+You've created your first live activity with Voltra. The JSX you write gets automatically converted to SwiftUI and displayed natively on iOS.
diff --git a/website/docs/v1/public/favicon.ico b/website/docs/v1/public/favicon.ico
new file mode 100644
index 00000000..302f3443
Binary files /dev/null and b/website/docs/v1/public/favicon.ico differ
diff --git a/website/docs/v1/public/icons/card-id.svg b/website/docs/v1/public/icons/card-id.svg
new file mode 100644
index 00000000..cbdb152e
--- /dev/null
+++ b/website/docs/v1/public/icons/card-id.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/docs/v1/public/icons/notification.svg b/website/docs/v1/public/icons/notification.svg
new file mode 100644
index 00000000..607e22f3
--- /dev/null
+++ b/website/docs/v1/public/icons/notification.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/docs/v1/public/icons/radio-signal.svg b/website/docs/v1/public/icons/radio-signal.svg
new file mode 100644
index 00000000..5abddc61
--- /dev/null
+++ b/website/docs/v1/public/icons/radio-signal.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/docs/v1/public/icons/subscriptions.svg b/website/docs/v1/public/icons/subscriptions.svg
new file mode 100644
index 00000000..a778dc84
--- /dev/null
+++ b/website/docs/v1/public/icons/subscriptions.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/docs/v1/public/logo-dark.svg b/website/docs/v1/public/logo-dark.svg
new file mode 100644
index 00000000..19f5fbb6
--- /dev/null
+++ b/website/docs/v1/public/logo-dark.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/website/docs/v1/public/logo-light.svg b/website/docs/v1/public/logo-light.svg
new file mode 100644
index 00000000..d3a68eeb
--- /dev/null
+++ b/website/docs/v1/public/logo-light.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/website/docs/v1/public/og-image.png b/website/docs/v1/public/og-image.png
new file mode 100644
index 00000000..4937fae7
Binary files /dev/null and b/website/docs/v1/public/og-image.png differ
diff --git a/website/docs/v1/public/voltra-baner.jpg b/website/docs/v1/public/voltra-baner.jpg
new file mode 100644
index 00000000..592c5103
Binary files /dev/null and b/website/docs/v1/public/voltra-baner.jpg differ
diff --git a/website/docs/v2/_meta.json b/website/docs/v2/_meta.json
new file mode 100644
index 00000000..27c989df
--- /dev/null
+++ b/website/docs/v2/_meta.json
@@ -0,0 +1,17 @@
+[
+ {
+ "type": "dir",
+ "name": "getting-started",
+ "label": "Getting Started"
+ },
+ {
+ "type": "dir",
+ "name": "ios",
+ "label": "iOS"
+ },
+ {
+ "type": "dir",
+ "name": "android",
+ "label": "Android"
+ }
+]
diff --git a/website/docs/v2/_nav.json b/website/docs/v2/_nav.json
new file mode 100644
index 00000000..111f5352
--- /dev/null
+++ b/website/docs/v2/_nav.json
@@ -0,0 +1,14 @@
+[
+ {
+ "text": "Getting started",
+ "link": "/getting-started/introduction"
+ },
+ {
+ "text": "iOS",
+ "link": "/ios/introduction"
+ },
+ {
+ "text": "Android",
+ "link": "/android/introduction"
+ }
+]
diff --git a/website/docs/v2/android/_meta.json b/website/docs/v2/android/_meta.json
new file mode 100644
index 00000000..c3a6d9fa
--- /dev/null
+++ b/website/docs/v2/android/_meta.json
@@ -0,0 +1,32 @@
+[
+ {
+ "type": "file",
+ "name": "introduction",
+ "label": "Introduction"
+ },
+ {
+ "type": "file",
+ "name": "setup",
+ "label": "Setup"
+ },
+ {
+ "type": "dir",
+ "name": "components",
+ "label": "Components"
+ },
+ {
+ "type": "file",
+ "name": "charts",
+ "label": "Charts"
+ },
+ {
+ "type": "dir",
+ "name": "development",
+ "label": "Development"
+ },
+ {
+ "type": "dir",
+ "name": "api",
+ "label": "API Reference"
+ }
+]
diff --git a/website/docs/android/api/plugin-configuration.md b/website/docs/v2/android/api/plugin-configuration.md
similarity index 100%
rename from website/docs/android/api/plugin-configuration.md
rename to website/docs/v2/android/api/plugin-configuration.md
diff --git a/website/docs/v2/android/charts.md b/website/docs/v2/android/charts.md
new file mode 100644
index 00000000..309ef73b
--- /dev/null
+++ b/website/docs/v2/android/charts.md
@@ -0,0 +1,325 @@
+# Charts (Android)
+
+Use charts in Android widgets to show trends, comparisons, progress, or composition at a glance. You can mix bars, lines, areas, points, rules, and sectors in a single chart.
+
+:::info
+Charts are rendered to a bitmap using the Android Canvas API and displayed as a Glance `Image`. This approach is required because Jetpack Glance has no native charting components.
+:::
+
+:::warning
+Mark components (`BarMark`, `LineMark`, and the other mark types) must be direct children of ``. Do not wrap them in a custom component.
+:::
+
+## Basic Usage
+
+Wrap one or more mark components inside ``:
+
+```tsx
+
+
+
+```
+
+## Data Types
+
+All marks except `RuleMark` take a `data` prop. There are two data shapes depending on the mark type:
+
+```typescript
+// BarMark, LineMark, AreaMark, PointMark
+type ChartDataPoint = {
+ x: string | number // Categorical ("Jan") or numeric (42)
+ y: number
+ series?: string // Optional — groups data for multi-series charts
+}
+
+// SectorMark
+type SectorDataPoint = {
+ value: number // Proportional angular value
+ category: string // Sector label
+}
+```
+
+## Marks
+
+### BarMark
+
+Use bars when people need to compare values across categories.
+
+**Parameters:**
+
+- `data` (ChartDataPoint[], required): The data points.
+- `color` (string, optional): Fill color.
+- `cornerRadius` (number, optional): Rounded bar corners.
+- `width` (number, optional): Fixed bar width.
+- `stacking` (string, optional): `"grouped"` for side-by-side bars in a multi-series chart.
+
+```tsx
+
+
+
+```
+
+---
+
+### LineMark
+
+Use a line when the shape of change matters more than individual columns.
+
+**Parameters:**
+
+- `data` (ChartDataPoint[], required): The data points.
+- `color` (string, optional): Line color.
+- `lineWidth` (number, optional): Stroke width.
+- `interpolation` (string, optional): `"linear"`, `"monotone"`, `"catmullRom"`, `"cardinal"`, `"stepStart"`, `"stepCenter"`, or `"stepEnd"`.
+
+```tsx
+
+
+
+```
+
+---
+
+### AreaMark
+
+Use an area chart when you want the overall volume or rise/fall pattern to read quickly.
+
+**Parameters:**
+
+- `data` (ChartDataPoint[], required): The data points.
+- `color` (string, optional): Fill color.
+- `interpolation` (string, optional): Same options as `LineMark`.
+
+```tsx
+
+
+
+```
+
+---
+
+### PointMark
+
+Use points for sparse measurements, scatter plots, or to emphasize exact observations.
+
+**Parameters:**
+
+- `data` (ChartDataPoint[], required): The data points.
+- `color` (string, optional): Point color.
+- `symbolSize` (number, optional): Point size.
+
+```tsx
+
+
+
+```
+
+---
+
+### RuleMark
+
+A horizontal or vertical reference line. Unlike other marks, RuleMark has no `data` array.
+
+**Parameters:**
+
+- `yValue` (number, optional): Draw a horizontal line at this y value.
+- `xValue` (string | number, optional): Draw a vertical line at this x value.
+- `color` (string, optional): Line color.
+- `lineWidth` (number, optional): Stroke width.
+
+```tsx
+
+
+
+
+```
+
+---
+
+### SectorMark
+
+Pie and donut charts.
+
+**Parameters:**
+
+- `data` (SectorDataPoint[], required): Sector data with `value` and `category`.
+- `color` (string, optional): Fill color (overrides automatic coloring).
+- `innerRadius` (number, optional): `0` = pie chart, any value above `0` = donut chart. Values ≤ 1 are treated as a ratio of the max radius; values > 1 are treated as absolute dp.
+- `outerRadius` (number, optional): Same behavior as `innerRadius`.
+- `angularInset` (number, optional): Gap between sectors in degrees.
+
+```tsx
+// Pie chart
+
+
+
+
+// Donut chart
+
+
+
+```
+
+## Chart Props
+
+The `` container accepts these props in addition to the standard `style` prop:
+
+| Prop | Type | Description |
+|---|---|---|
+| `xAxisVisibility` | `"automatic" \| "visible" \| "hidden"` | Show or hide the x-axis |
+| `xAxisGridStyle` | `{ visible?: boolean }` | Show or hide x-axis grid lines |
+| `yAxisVisibility` | `"automatic" \| "visible" \| "hidden"` | Show or hide the y-axis |
+| `yAxisGridStyle` | `{ visible?: boolean }` | Show or hide y-axis grid lines |
+| `foregroundStyleScale` | `Record` | Map series names to colors |
+
+## Grid Lines
+
+Hide grid lines when you want the chart to feel more compact:
+
+```tsx
+
+
+
+```
+
+## Multi-Series Charts
+
+Add a `series` field to your data points when you want multiple datasets in the same chart. Use `foregroundStyleScale` to keep those series colors consistent:
+
+```tsx
+
+
+
+```
+
+Without `foregroundStyleScale`, colors are chosen automatically.
+
+For side-by-side bars, set `stacking="grouped"`:
+
+```tsx
+
+
+
+```
+
+## Combining Marks
+
+Mix mark types when one chart needs both context and emphasis, such as bars for totals plus a rule for a target:
+
+```tsx
+
+
+
+
+
+```
+
+## Sparkline / Minimal Style
+
+Hide axes for a clean, compact visualization:
+
+```tsx
+
+
+
+```
+
+## Sizing
+
+Chart dimensions are read from the `style` prop:
+
+- **Fixed size**: `style={{ width: 300, height: 200 }}`
+- **Fill parent**: `style={{ width: '100%', height: '100%' }}`
+
+If no width or height is specified, the chart defaults to 300×200 dp.
+
+## Platform Notes
+
+- `legendVisibility` is not currently available on Android charts.
+- Point markers are circular on Android.
+- `innerRadius` and `outerRadius` on `SectorMark` accept either a ratio (`0` to `1`) or a larger fixed value.
diff --git a/website/docs/v2/android/components/_meta.json b/website/docs/v2/android/components/_meta.json
new file mode 100644
index 00000000..80c2c5b5
--- /dev/null
+++ b/website/docs/v2/android/components/_meta.json
@@ -0,0 +1,22 @@
+[
+ {
+ "type": "file",
+ "name": "layout",
+ "label": "Layout & Containers"
+ },
+ {
+ "type": "file",
+ "name": "visual",
+ "label": "Visual Elements"
+ },
+ {
+ "type": "file",
+ "name": "status",
+ "label": "Status & Progress"
+ },
+ {
+ "type": "file",
+ "name": "interactive",
+ "label": "Interactivity"
+ }
+]
diff --git a/website/docs/android/components/interactive.md b/website/docs/v2/android/components/interactive.md
similarity index 100%
rename from website/docs/android/components/interactive.md
rename to website/docs/v2/android/components/interactive.md
diff --git a/website/docs/android/components/layout.md b/website/docs/v2/android/components/layout.md
similarity index 100%
rename from website/docs/android/components/layout.md
rename to website/docs/v2/android/components/layout.md
diff --git a/website/docs/android/components/status.md b/website/docs/v2/android/components/status.md
similarity index 100%
rename from website/docs/android/components/status.md
rename to website/docs/v2/android/components/status.md
diff --git a/website/docs/android/components/visual.md b/website/docs/v2/android/components/visual.md
similarity index 100%
rename from website/docs/android/components/visual.md
rename to website/docs/v2/android/components/visual.md
diff --git a/website/docs/v2/android/development/_meta.json b/website/docs/v2/android/development/_meta.json
new file mode 100644
index 00000000..9dd1d5a6
--- /dev/null
+++ b/website/docs/v2/android/development/_meta.json
@@ -0,0 +1,52 @@
+[
+ {
+ "type": "file",
+ "name": "developing-widgets",
+ "label": "Developing Widgets"
+ },
+ {
+ "type": "file",
+ "name": "managing-ongoing-notifications",
+ "label": "Managing Ongoing Notifications"
+ },
+ {
+ "type": "file",
+ "name": "querying-active-widgets",
+ "label": "Querying Active Widgets"
+ },
+ {
+ "type": "file",
+ "name": "styling",
+ "label": "Styling"
+ },
+ {
+ "type": "file",
+ "name": "dynamic-colors",
+ "label": "Dynamic Colors"
+ },
+ {
+ "type": "file",
+ "name": "images",
+ "label": "Images"
+ },
+ {
+ "type": "file",
+ "name": "image-preloading",
+ "label": "Image Preloading"
+ },
+ {
+ "type": "file",
+ "name": "widget-pre-rendering",
+ "label": "Widget Pre-rendering"
+ },
+ {
+ "type": "file",
+ "name": "server-driven-widgets",
+ "label": "Server-driven widgets"
+ },
+ {
+ "type": "file",
+ "name": "custom-fonts",
+ "label": "Custom Fonts"
+ }
+]
diff --git a/website/docs/android/development/custom-fonts.md b/website/docs/v2/android/development/custom-fonts.md
similarity index 100%
rename from website/docs/android/development/custom-fonts.md
rename to website/docs/v2/android/development/custom-fonts.md
diff --git a/website/docs/android/development/developing-widgets.md b/website/docs/v2/android/development/developing-widgets.md
similarity index 100%
rename from website/docs/android/development/developing-widgets.md
rename to website/docs/v2/android/development/developing-widgets.md
diff --git a/website/docs/android/development/dynamic-colors.md b/website/docs/v2/android/development/dynamic-colors.md
similarity index 100%
rename from website/docs/android/development/dynamic-colors.md
rename to website/docs/v2/android/development/dynamic-colors.md
diff --git a/website/docs/android/development/image-preloading.md b/website/docs/v2/android/development/image-preloading.md
similarity index 100%
rename from website/docs/android/development/image-preloading.md
rename to website/docs/v2/android/development/image-preloading.md
diff --git a/website/docs/android/development/images.md b/website/docs/v2/android/development/images.md
similarity index 100%
rename from website/docs/android/development/images.md
rename to website/docs/v2/android/development/images.md
diff --git a/website/docs/android/development/managing-ongoing-notifications.md b/website/docs/v2/android/development/managing-ongoing-notifications.md
similarity index 100%
rename from website/docs/android/development/managing-ongoing-notifications.md
rename to website/docs/v2/android/development/managing-ongoing-notifications.md
diff --git a/website/docs/android/development/querying-active-widgets.md b/website/docs/v2/android/development/querying-active-widgets.md
similarity index 100%
rename from website/docs/android/development/querying-active-widgets.md
rename to website/docs/v2/android/development/querying-active-widgets.md
diff --git a/website/docs/android/development/server-driven-widgets.md b/website/docs/v2/android/development/server-driven-widgets.md
similarity index 100%
rename from website/docs/android/development/server-driven-widgets.md
rename to website/docs/v2/android/development/server-driven-widgets.md
diff --git a/website/docs/android/development/styling.md b/website/docs/v2/android/development/styling.md
similarity index 100%
rename from website/docs/android/development/styling.md
rename to website/docs/v2/android/development/styling.md
diff --git a/website/docs/android/development/testing-and-previews.md b/website/docs/v2/android/development/testing-and-previews.md
similarity index 100%
rename from website/docs/android/development/testing-and-previews.md
rename to website/docs/v2/android/development/testing-and-previews.md
diff --git a/website/docs/android/development/widget-pre-rendering.md b/website/docs/v2/android/development/widget-pre-rendering.md
similarity index 100%
rename from website/docs/android/development/widget-pre-rendering.md
rename to website/docs/v2/android/development/widget-pre-rendering.md
diff --git a/website/docs/android/introduction.md b/website/docs/v2/android/introduction.md
similarity index 100%
rename from website/docs/android/introduction.md
rename to website/docs/v2/android/introduction.md
diff --git a/website/docs/android/setup.mdx b/website/docs/v2/android/setup.mdx
similarity index 100%
rename from website/docs/android/setup.mdx
rename to website/docs/v2/android/setup.mdx
diff --git a/website/docs/getting-started/_meta.json b/website/docs/v2/getting-started/_meta.json
similarity index 100%
rename from website/docs/getting-started/_meta.json
rename to website/docs/v2/getting-started/_meta.json
diff --git a/website/docs/getting-started/installation.mdx b/website/docs/v2/getting-started/installation.mdx
similarity index 100%
rename from website/docs/getting-started/installation.mdx
rename to website/docs/v2/getting-started/installation.mdx
diff --git a/website/docs/getting-started/introduction.md b/website/docs/v2/getting-started/introduction.md
similarity index 100%
rename from website/docs/getting-started/introduction.md
rename to website/docs/v2/getting-started/introduction.md
diff --git a/website/docs/getting-started/migration-v2.mdx b/website/docs/v2/getting-started/migration-v2.mdx
similarity index 100%
rename from website/docs/getting-started/migration-v2.mdx
rename to website/docs/v2/getting-started/migration-v2.mdx
diff --git a/website/docs/v2/getting-started/prior-art.md b/website/docs/v2/getting-started/prior-art.md
new file mode 100644
index 00000000..ee1f45f5
--- /dev/null
+++ b/website/docs/v2/getting-started/prior-art.md
@@ -0,0 +1,31 @@
+# Prior Art
+
+Voltra wouldn't be possible without the incredible work of the open-source community. This page acknowledges the libraries and projects that inspired and informed our approach to bridging JavaScript and native iOS Live Activities.
+
+## Inspiration
+
+### Dynamic UI
+
+The core concept of describing SwiftUI layouts through JSON configuration was pioneered by Wesley de Groot's [Dynamic UI](https://github.com/0xWDG/DynamicUI).
+
+### Expo Live Activity
+
+The insight of combining JavaScript-driven development with iOS Live Activities came from Software Mansion's [Expo Live Activity](https://github.com/software-mansion-labs/expo-live-activity). Referencing their open-source Expo config plugin logic was helpful during early development.
+
+## Thank You
+
+We extend our gratitude to the following organizations and individuals:
+
+- **Wesley de Groot** for creating Dynamic UI and demonstrating the power of JSON-driven SwiftUI interfaces.
+
+- **Software Mansion** for open-sourcing their Expo Live Activity library.
+
+- **Expo** for building Expo Modules that power Voltra behind the scenes.
+
+- **The broader open-source community** for all the libraries, tools, and ideas that make innovative projects like Voltra possible in the first place.
+
+Voltra is our contribution back to the broader React Native community. We hope it helps developers create more engaging and interactive Live Activities and Widgets for their iOS applications.
+
+---
+
+_If you're working on something that builds upon Voltra, we'd love to hear about it! Feel free to reach out and let us know how you're using it._
diff --git a/website/docs/getting-started/react-native-cli.mdx b/website/docs/v2/getting-started/react-native-cli.mdx
similarity index 100%
rename from website/docs/getting-started/react-native-cli.mdx
rename to website/docs/v2/getting-started/react-native-cli.mdx
diff --git a/website/docs/v2/index.md b/website/docs/v2/index.md
new file mode 100644
index 00000000..163a4b30
--- /dev/null
+++ b/website/docs/v2/index.md
@@ -0,0 +1,29 @@
+---
+pageType: home
+
+hero:
+ image:
+ src:
+ light: /logo-light.svg
+ dark: /logo-dark.svg
+ alt: Voltra
+ name: 'Live Activities & Widgets in React'
+ tagline: 'Voltra lets React Native developers build native Live Activities and widgets on iOS and Android using React components — no Swift or Kotlin required. It supports hot reload, push updates on iOS, and a config plugin that wires everything automatically.'
+ actions:
+ - theme: brand
+ text: Get Started
+ link: /getting-started/introduction
+ badge:
+ text: Beta
+ type: info
+features:
+ - title: Native Primitives in JSX
+ details: Compose native interfaces using SwiftUI (iOS) and Jetpack Compose Glance (Android) primitives directly in JSX.
+ icon:
+ - title: Live Activities & Widgets
+ details: Build Dynamic Island experiences and Live Activities for iOS, plus Home Screen widgets for both iOS and Android using a unified React workflow.
+ icon:
+ - title: Push-to-update for Live Activities
+ details: Stream real-time updates to Live Activities via push notifications from any JavaScript runtime. Keep activities current without app interaction.
+ icon:
+---
diff --git a/website/docs/v2/ios/_meta.json b/website/docs/v2/ios/_meta.json
new file mode 100644
index 00000000..c3a6d9fa
--- /dev/null
+++ b/website/docs/v2/ios/_meta.json
@@ -0,0 +1,32 @@
+[
+ {
+ "type": "file",
+ "name": "introduction",
+ "label": "Introduction"
+ },
+ {
+ "type": "file",
+ "name": "setup",
+ "label": "Setup"
+ },
+ {
+ "type": "dir",
+ "name": "components",
+ "label": "Components"
+ },
+ {
+ "type": "file",
+ "name": "charts",
+ "label": "Charts"
+ },
+ {
+ "type": "dir",
+ "name": "development",
+ "label": "Development"
+ },
+ {
+ "type": "dir",
+ "name": "api",
+ "label": "API Reference"
+ }
+]
diff --git a/website/docs/v2/ios/api/_meta.json b/website/docs/v2/ios/api/_meta.json
new file mode 100644
index 00000000..0e1c3086
--- /dev/null
+++ b/website/docs/v2/ios/api/_meta.json
@@ -0,0 +1,12 @@
+[
+ {
+ "type": "file",
+ "name": "plugin-configuration",
+ "label": "Plugin Configuration"
+ },
+ {
+ "type": "file",
+ "name": "configuration",
+ "label": "Configuration"
+ }
+]
diff --git a/website/docs/ios/api/configuration.md b/website/docs/v2/ios/api/configuration.md
similarity index 100%
rename from website/docs/ios/api/configuration.md
rename to website/docs/v2/ios/api/configuration.md
diff --git a/website/docs/ios/api/plugin-configuration.md b/website/docs/v2/ios/api/plugin-configuration.md
similarity index 100%
rename from website/docs/ios/api/plugin-configuration.md
rename to website/docs/v2/ios/api/plugin-configuration.md
diff --git a/website/docs/v2/ios/charts.md b/website/docs/v2/ios/charts.md
new file mode 100644
index 00000000..e44e24d7
--- /dev/null
+++ b/website/docs/v2/ios/charts.md
@@ -0,0 +1,352 @@
+# Charts (iOS)
+
+Use charts in Live Activities and widgets to show trends, comparisons, progress, or composition at a glance. You can mix bars, lines, areas, points, rules, and sectors in a single chart.
+
+:::info
+Charts require iOS 16.0+. SectorMark (pie/donut) requires iOS 17.0+.
+:::
+
+:::warning
+Mark components (`BarMark`, `LineMark`, etc.) must be **direct children** of ``. They cannot be wrapped in custom components. For example, this will not work:
+
+```tsx
+// This won't work — marks are not direct children
+function MyMarks() {
+ return
+}
+
+
+
+
+```
+
+Instead, always place marks directly inside Chart:
+
+```tsx
+
+
+
+```
+:::
+
+## Basic Usage
+
+Wrap one or more mark components inside ``:
+
+```tsx
+
+
+
+```
+
+## Data Types
+
+All marks except `RuleMark` take a `data` prop. There are two data shapes depending on the mark type:
+
+```typescript
+// BarMark, LineMark, AreaMark, PointMark
+type ChartDataPoint = {
+ x: string | number // Categorical ("Jan") or numeric (42)
+ y: number
+ series?: string // Optional — groups data for multi-series charts
+}
+
+// SectorMark
+type SectorDataPoint = {
+ value: number // Proportional angular value
+ category: string // Sector label
+}
+```
+
+## Marks
+
+### BarMark
+
+Use bars when people need to compare values across categories.
+
+**Parameters:**
+
+- `data` (ChartDataPoint[], required): The data points.
+- `color` (string, optional): Fill color.
+- `cornerRadius` (number, optional): Rounded bar corners.
+- `width` (number, optional): Fixed bar width.
+- `stacking` (string, optional): `"grouped"` (side by side).
+
+```tsx
+
+
+
+```
+
+---
+
+### LineMark
+
+Use a line when the shape of change matters more than individual columns.
+
+**Parameters:**
+
+- `data` (ChartDataPoint[], required): The data points.
+- `color` (string, optional): Line color.
+- `lineWidth` (number, optional): Stroke width in points.
+- `interpolation` (string, optional): Curve type — `"linear"`, `"monotone"`, `"catmullRom"`, `"cardinal"`, `"stepStart"`, `"stepCenter"`, or `"stepEnd"`.
+- `symbol` (string, optional): Symbol at each point — `"circle"`, `"square"`, `"triangle"`, `"diamond"`, `"pentagon"`, `"cross"`, `"plus"`, or `"asterisk"`.
+
+```tsx
+
+
+
+```
+
+---
+
+### AreaMark
+
+Use an area chart when you want the overall volume or rise/fall pattern to read quickly.
+
+**Parameters:**
+
+- `data` (ChartDataPoint[], required): The data points.
+- `color` (string, optional): Fill color.
+- `interpolation` (string, optional): Same options as LineMark.
+
+```tsx
+
+
+
+```
+
+---
+
+### PointMark
+
+Use points for sparse measurements, scatter plots, or to emphasize exact observations.
+
+**Parameters:**
+
+- `data` (ChartDataPoint[], required): The data points.
+- `color` (string, optional): Point color.
+- `symbol` (string, optional): Same options as LineMark.
+- `symbolSize` (number, optional): Symbol size in points.
+
+```tsx
+
+
+
+```
+
+---
+
+### RuleMark
+
+A horizontal or vertical reference line. Unlike other marks, RuleMark has no `data` array.
+
+**Parameters:**
+
+- `yValue` (number, optional): Draw a horizontal line at this y value.
+- `xValue` (string | number, optional): Draw a vertical line at this x value.
+- `color` (string, optional): Line color.
+- `lineWidth` (number, optional): Stroke width in points.
+
+If both `xValue` and `yValue` are provided, Voltra renders both lines.
+
+```tsx
+
+
+
+
+```
+
+---
+
+### SectorMark
+
+Pie and donut charts. Requires iOS 17+.
+
+**Parameters:**
+
+- `data` (SectorDataPoint[], required): Sector data with `value` and `category`.
+- `color` (string, optional): Fill color (overrides automatic coloring).
+- `innerRadius` (number, optional): Ratio from 0 to 1. `0` = pie chart, any value above `0` = donut chart.
+- `outerRadius` (number, optional): Ratio from 0 to 1.
+- `angularInset` (number, optional): Gap between sectors in degrees.
+
+```tsx
+// Pie chart
+
+
+
+
+// Donut chart
+
+
+
+```
+
+## Chart Props
+
+The `` container accepts these props in addition to the standard Voltra `style` prop:
+
+| Prop | Type | Description |
+|---|---|---|
+| `xAxisVisibility` | `"automatic" \| "visible" \| "hidden"` | Show or hide the x-axis |
+| `xAxisGridStyle` | `{ visible?: boolean; color?: string; lineWidth?: number; dash?: number[] }` | Control x-axis grid lines |
+| `yAxisVisibility` | `"automatic" \| "visible" \| "hidden"` | Show or hide the y-axis |
+| `yAxisGridStyle` | `{ visible?: boolean; color?: string; lineWidth?: number; dash?: number[] }` | Control y-axis grid lines |
+| `legendVisibility` | `"automatic" \| "visible" \| "hidden"` | Show or hide the legend |
+| `foregroundStyleScale` | `Record` | Map series names to colors |
+
+## Grid Lines
+
+Use `xAxisGridStyle` and `yAxisGridStyle` when the default grid is too busy or not visible enough for your design:
+
+```tsx
+
+
+
+```
+
+## Multi-Series Charts
+
+Add a `series` field to your data points when you want multiple datasets in the same chart. Use `foregroundStyleScale` to keep those series colors consistent:
+
+```tsx
+
+
+
+```
+
+Without `foregroundStyleScale`, colors are chosen automatically.
+
+For side-by-side bars, set `stacking="grouped"`:
+
+```tsx
+
+
+
+```
+
+## Combining Marks
+
+Mix mark types when one chart needs both context and emphasis, such as bars for totals plus a rule for a target:
+
+```tsx
+
+
+
+
+
+```
+
+## Sparkline / Minimal Style
+
+Hide axes and legend for a clean, compact visualization:
+
+```tsx
+
+
+
+```
+
+## Widget / Live Activity Notes
+
+Charts in widgets and Live Activities are static. Focus on a clear snapshot of the data rather than interactions like panning or scrolling.
+
+The `` component supports the full Voltra style system on its container — padding, background, borders, corner radius, shadows, and sizing all work:
+
+```tsx
+
+
+
+```
diff --git a/website/docs/v2/ios/components/_meta.json b/website/docs/v2/ios/components/_meta.json
new file mode 100644
index 00000000..e41032dc
--- /dev/null
+++ b/website/docs/v2/ios/components/_meta.json
@@ -0,0 +1,27 @@
+[
+ {
+ "type": "file",
+ "name": "overview",
+ "label": "Overview"
+ },
+ {
+ "type": "file",
+ "name": "layout",
+ "label": "Layout & Containers"
+ },
+ {
+ "type": "file",
+ "name": "visual",
+ "label": "Visual Elements"
+ },
+ {
+ "type": "file",
+ "name": "status",
+ "label": "Status & Progress"
+ },
+ {
+ "type": "file",
+ "name": "interactive",
+ "label": "Interactivity"
+ }
+]
diff --git a/website/docs/ios/components/interactive.md b/website/docs/v2/ios/components/interactive.md
similarity index 100%
rename from website/docs/ios/components/interactive.md
rename to website/docs/v2/ios/components/interactive.md
diff --git a/website/docs/v2/ios/components/layout.md b/website/docs/v2/ios/components/layout.md
new file mode 100644
index 00000000..d27fabe8
--- /dev/null
+++ b/website/docs/v2/ios/components/layout.md
@@ -0,0 +1,221 @@
+# Layout & Containers (iOS)
+
+Components that arrange other elements or provide structural grouping.
+
+## Alignment & Positioning
+
+Voltra uses SwiftUI's native positioning model. Instead of CSS-style `position: absolute` with `top`/`left`/`right`/`bottom`, you use:
+
+1. **Stack `alignment` props** - Position children within their container
+2. **`offsetX`/`offsetY` styles** - Fine-tune individual element positions
+
+Each stack type has different alignment options based on its layout direction.
+
+---
+
+### VStack
+
+A vertical stack container that arranges its children in a column.
+
+**Parameters:**
+
+- `spacing` (number, optional): Spacing between children in points
+- `alignment` (string, optional): Horizontal alignment of children:
+ - `"leading"` - Align to left edge
+ - `"center"` (default) - Align to center
+ - `"trailing"` - Align to right edge
+
+**Apple Documentation:** [VStack](https://developer.apple.com/documentation/swiftui/vstack)
+
+---
+
+### HStack
+
+A horizontal stack container that arranges its children in a row.
+
+**Parameters:**
+
+- `spacing` (number, optional): Spacing between children in points
+- `alignment` (string, optional): Vertical alignment of children:
+ - `"top"` - Align to top edge
+ - `"center"` (default) - Align to center
+ - `"bottom"` - Align to bottom edge
+ - `"firstTextBaseline"` - Align to first text baseline
+ - `"lastTextBaseline"` - Align to last text baseline
+
+**Apple Documentation:** [HStack](https://developer.apple.com/documentation/swiftui/hstack)
+
+---
+
+### ZStack
+
+A depth-based stack container that overlays its children on top of each other. Use ZStack when you need to layer elements, such as placing a badge over an image.
+
+**Parameters:**
+
+- `alignment` (string, optional): Positions ALL children at the specified alignment point. Available values:
+ - `"center"` (default) - Center of the stack
+ - `"leading"` - Left edge (or right in RTL)
+ - `"trailing"` - Right edge (or left in RTL)
+ - `"top"` - Top edge
+ - `"bottom"` - Bottom edge
+ - `"topLeading"` - Top-left corner
+ - `"topTrailing"` - Top-right corner
+ - `"bottomLeading"` - Bottom-left corner
+ - `"bottomTrailing"` - Bottom-right corner
+
+**Apple Documentation:** [ZStack](https://developer.apple.com/documentation/swiftui/zstack)
+
+#### Positioning with ZStack
+
+In SwiftUI (and Voltra), positioning works differently than CSS. The `alignment` prop on ZStack positions **all children** at the same alignment point. The ZStack's size is determined by its largest child.
+
+**Example: Badge overlay**
+
+```tsx
+
+ {/* Image defines the ZStack size */}
+
+
+ {/* Badge is positioned at top-right, then nudged with offset */}
+
+ 3
+
+
+```
+
+:::tip
+Use `offsetX` and `offsetY` style properties to fine-tune individual element positions after alignment. Positive `offsetX` moves right, positive `offsetY` moves down.
+:::
+
+---
+
+### View
+
+A flexible container component that **always uses flexbox layout**. Unlike VStack and HStack which use native SwiftUI stacks by default, View is specifically designed for React Native-style flexbox layouts.
+
+:::tip Flexbox-First Component
+The View component is purpose-built for flexbox layouts and always uses the flexbox layout engine. See the [Flexbox Layout](../development/flexbox-layout) guide for comprehensive documentation.
+:::
+
+**Style Properties:**
+
+View responds to flexbox style properties set via the `style` prop:
+
+- `flexDirection`: `'row'` | `'column'` (default: `'column'`)
+- `alignItems`: `'flex-start'` | `'center'` | `'flex-end'` | `'stretch'`
+- `justifyContent`: `'flex-start'` | `'center'` | `'flex-end'` | `'space-between'` | `'space-around'` | `'space-evenly'`
+- `gap`: Spacing between children in points
+
+**Example:**
+
+```tsx
+// Horizontal layout with space between
+
+ Left
+ Center
+ Right
+
+
+// Vertical layout with centered items
+
+
+ Success
+
+```
+
+**When to use View:**
+
+- You need React Native-style flexbox behavior
+- You want dynamic `flexDirection` (switching between row/column)
+- You need `justifyContent` spacing modes
+
+**When to use VStack/HStack:**
+
+- Simple vertical or horizontal layouts
+- You want SwiftUI's native stack performance
+- You need SwiftUI-specific alignment (like firstTextBaseline)
+
+**Availability:** iOS 16.0+
+
+**Learn More:** [Flexbox Layout Guide](../development/flexbox-layout)
+
+---
+
+### Spacer
+
+A flexible space component that expands to fill available space in its container.
+
+**Parameters:**
+
+- `minLength` (number, optional): Minimum length
+
+**Apple Documentation:** [Spacer](https://developer.apple.com/documentation/swiftui/spacer)
+
+---
+
+### GroupBox
+
+A grouped content container that visually groups related content with a styled background.
+
+**Parameters:** None
+
+**Apple Documentation:** [GroupBox](https://developer.apple.com/documentation/swiftui/groupbox)
+
+---
+
+### GlassContainer
+
+A Liquid Glass container that wraps Apple's [`GlassEffectContainer`](https://developer.apple.com/documentation/swiftui/glasseffectcontainer) to provide a modern glassmorphism effect for grouping content.
+
+:::warning iOS 26 SDK Required
+This component uses Apple's `GlassEffectContainer` API which requires **Xcode with iOS 26 SDK** to build. If you're using an older Xcode version, you'll encounter build errors:
+
+```
+- value of type 'some View' has no member 'glassEffect'
+- cannot find 'GlassEffectContainer' in scope
+```
+
+**Workaround:** Avoid using this component until iOS 26 SDK is available in your Xcode version. At runtime, devices with iOS < 26 will gracefully fall back to a regular container without the glass effect.
+:::
+
+**Parameters:**
+
+- `spacing` (number, optional): Spacing between glass elements
+
+**Availability:**
+- **Build:** Requires Xcode with iOS 26 SDK (uses `GlassEffectContainer` API)
+- **Runtime:** iOS 26+ for glass effect, graceful fallback on earlier versions
+
+**Apple Documentation:** [GlassEffectContainer](https://developer.apple.com/documentation/swiftui/glasseffectcontainer)
diff --git a/website/docs/ios/components/overview.md b/website/docs/v2/ios/components/overview.md
similarity index 100%
rename from website/docs/ios/components/overview.md
rename to website/docs/v2/ios/components/overview.md
diff --git a/website/docs/v2/ios/components/status.md b/website/docs/v2/ios/components/status.md
new file mode 100644
index 00000000..0ca5d891
--- /dev/null
+++ b/website/docs/v2/ios/components/status.md
@@ -0,0 +1,76 @@
+# Data Visualization & Status (iOS)
+
+Components specifically designed to show dynamic values or states over time in Live Activities and Widgets.
+
+### LinearProgressView
+
+A horizontal progress bar that displays determinate progress or timer-based progress.
+
+#### Limitations
+
+- When using `timerInterval` for smooth animations, custom styling properties such as `height`, `trackColor`, `cornerRadius`, and the `thumb` component are ignored. The component will use the default system appearance in this mode.
+
+---
+
+### CircularProgressView
+
+A circular progress indicator that displays determinate progress or timer-based progress.
+
+#### Limitations
+
+- While `timerInterval` is supported, the progress ring will not animate continuously. It only updates its visual state when the component state is refreshed. For a smooth, live-animating progress bar, use `LinearProgressView`.
+
+---
+
+### Gauge
+
+A gauge indicator for progress visualization (iOS 16+).
+
+---
+
+### Timer
+
+A flexible component for displaying live-updating time intervals. Crucial for Live Activities, it uses native SwiftUI text interpolation to ensure the time updates automatically on the lock screen and in the Dynamic Island without requiring background updates from React Native.
+
+**Modes:**
+
+- **Timer Mode:** For fixed intervals (countdowns or counting up to a target). Requires `endAtMs` or `durationMs`.
+- **Stopwatch Mode:** For open-ended intervals counting up from a starting point. Requires `startAtMs` and `direction="up"`, but both `endAtMs` and `durationMs` must be omitted.
+
+**Parameters:**
+
+- `startAtMs` (number, optional): Start time in milliseconds since epoch.
+- `endAtMs` (number, optional): End time in milliseconds since epoch.
+- `durationMs` (number, optional): Duration in milliseconds. Used if `endAtMs` is omitted.
+- `direction` (string, optional): Count direction. Can be `'up'` or `'down'`. Defaults to `'down'`.
+- `textStyle` (string, optional): Formatting style.
+ - `'timer'`: Standard clock format (e.g., `05:00`).
+ - `'relative'`: Relative format (e.g., `5m`).
+- `showHours` (boolean, optional): Whether to show hours (e.g., `1:30:00` vs `90:00`). Defaults to `false`.
+- `textTemplates` (string, optional): JSON-encoded object with `running` and `completed` templates. Use `{time}` as a placeholder.
+
+**Examples:**
+
+```tsx
+// Timer Mode: Countdown 5 minutes
+
+
+// Stopwatch Mode: Count up indefinitely from now
+
+
+// Relative Timer with Template
+
+```
diff --git a/website/docs/v2/ios/components/visual.md b/website/docs/v2/ios/components/visual.md
new file mode 100644
index 00000000..3791578e
--- /dev/null
+++ b/website/docs/v2/ios/components/visual.md
@@ -0,0 +1,69 @@
+# Visual Elements & Typography (iOS)
+
+Static or decorative elements used to display content.
+
+### Text
+
+Displays text content.
+
+**Parameters:**
+
+- `numberOfLines` (number, optional): Maximum number of lines to display
+
+---
+
+### Label
+
+A semantic label that can display both an icon and title text.
+
+**Parameters:**
+
+- `title` (string, optional): Text content for the label
+- `systemImage` (string, optional): SF Symbol name for the label icon
+
+---
+
+### Image
+
+Displays bitmap images from the asset catalog or base64 encoded data.
+
+**Parameters:**
+
+- `source` (object, optional): Image source object (`assetName` or `base64`)
+- `resizeMode` (string, optional): `"cover"`, `"contain"`, `"stretch"`, `"repeat"`, or `"center"`
+- `fallback` (ReactNode, optional): Custom content rendered when the image is missing
+
+**Styling the fallback:**
+
+To add a background color when an image is missing, use `backgroundColor` in the `style` prop:
+
+```jsx
+
+```
+
+---
+
+### Symbol
+
+Displays SF Symbols (system icons) with configuration options.
+
+---
+
+### Divider
+
+A visual divider component.
+
+---
+
+### LinearGradient
+
+A linear gradient background that can contain children.
+
+---
+
+### Mask
+
+Masks content using any Voltra element as the mask shape.
diff --git a/website/docs/v2/ios/development/_meta.json b/website/docs/v2/ios/development/_meta.json
new file mode 100644
index 00000000..71d6d2b8
--- /dev/null
+++ b/website/docs/v2/ios/development/_meta.json
@@ -0,0 +1,72 @@
+[
+ {
+ "type": "file",
+ "name": "managing-live-activities-locally",
+ "label": "Managing Live Activities locally"
+ },
+ {
+ "type": "file",
+ "name": "developing-live-activities",
+ "label": "Developing Live Activities"
+ },
+ {
+ "type": "file",
+ "name": "developing-widgets",
+ "label": "Developing Widgets"
+ },
+ {
+ "type": "file",
+ "name": "querying-active-widgets",
+ "label": "Querying Active Widgets"
+ },
+ {
+ "type": "file",
+ "name": "widget-pre-rendering",
+ "label": "Widget pre-rendering"
+ },
+ {
+ "type": "file",
+ "name": "images",
+ "label": "Images"
+ },
+ {
+ "type": "file",
+ "name": "image-preloading",
+ "label": "Image preloading"
+ },
+ {
+ "type": "file",
+ "name": "performance",
+ "label": "Performance"
+ },
+ {
+ "type": "file",
+ "name": "styling",
+ "label": "Styling"
+ },
+ {
+ "type": "file",
+ "name": "flexbox-layout",
+ "label": "Flexbox Layout"
+ },
+ {
+ "type": "file",
+ "name": "interactions",
+ "label": "Interactions"
+ },
+ {
+ "type": "file",
+ "name": "events",
+ "label": "Events"
+ },
+ {
+ "type": "file",
+ "name": "server-side-updates",
+ "label": "Server-side updates"
+ },
+ {
+ "type": "file",
+ "name": "server-driven-widgets",
+ "label": "Server-driven widgets"
+ }
+]
diff --git a/website/docs/ios/development/developing-live-activities.md b/website/docs/v2/ios/development/developing-live-activities.md
similarity index 100%
rename from website/docs/ios/development/developing-live-activities.md
rename to website/docs/v2/ios/development/developing-live-activities.md
diff --git a/website/docs/ios/development/developing-widgets.md b/website/docs/v2/ios/development/developing-widgets.md
similarity index 100%
rename from website/docs/ios/development/developing-widgets.md
rename to website/docs/v2/ios/development/developing-widgets.md
diff --git a/website/docs/ios/development/events.md b/website/docs/v2/ios/development/events.md
similarity index 100%
rename from website/docs/ios/development/events.md
rename to website/docs/v2/ios/development/events.md
diff --git a/website/docs/v2/ios/development/flexbox-layout.md b/website/docs/v2/ios/development/flexbox-layout.md
new file mode 100644
index 00000000..423e14dc
--- /dev/null
+++ b/website/docs/v2/ios/development/flexbox-layout.md
@@ -0,0 +1,80 @@
+# Flexbox Layout
+
+:::warning Experimental Feature
+Flexbox layout is a **new layout system** in Voltra. While it covers common use cases, you may encounter bugs or edge cases. Please [report any issues](https://github.com/callstackincubator/voltra/issues) you find.
+:::
+
+Voltra supports React Native-style flexbox layout through the `Voltra.View` component.
+
+## Enabling Flexbox
+
+Use `Voltra.View` to opt in to flexbox layout:
+
+```tsx
+
+ Left
+ Right
+
+```
+
+## Supported Properties
+
+Voltra supports the following flexbox style properties. For detailed explanations and examples of each property, see the [React Native Flexbox documentation](https://reactnative.dev/docs/flexbox).
+
+### Container Properties
+
+| Property | Supported Values |
+|---|---|
+| `flexDirection` | `'column'` (default), `'row'` |
+| `justifyContent` | `'flex-start'` (default), `'center'`, `'flex-end'`, `'space-between'`, `'space-around'`, `'space-evenly'` |
+| `alignItems` | `'stretch'` (default), `'flex-start'`, `'center'`, `'flex-end'` |
+| `gap` | Number (points) — applies along main axis only |
+
+### Child Properties
+
+| Property | Description |
+|---|---|
+| `flex` | Shorthand for `flexGrow` and `flexShrink` |
+| `flexGrow` | How much the item should grow (default: `0`) |
+| `flexShrink` | How much the item should shrink (default: `0`) |
+| `flexBasis` | Initial size before flex grow/shrink |
+| `alignSelf` | Override parent's `alignItems` for this child |
+| `width`, `height` | Fixed dimensions |
+| `minWidth`, `maxWidth` | Width constraints |
+| `minHeight`, `maxHeight` | Height constraints |
+| `margin` | Outer spacing |
+| `padding` | Inner spacing |
+
+### Unsupported Properties
+
+The following React Native flexbox properties are **not supported** in Voltra:
+
+- `flexWrap` — items are always laid out in a single line
+- `rowGap`, `columnGap` — only the unified `gap` property is supported, applied along the main axis
+- Percentage-based `width` or `height` (e.g. `width: '50%'`) — use `flexGrow` instead
+
+## Differences from React Native
+
+If you're coming from React Native, keep these differences in mind:
+
+### Flexbox is opt-in
+
+In React Native, every `View` uses flexbox by default. In Voltra, only `Voltra.View` uses flexbox. Other containers (`VStack`, `HStack`) use native SwiftUI layout.
+
+### Gap is single-axis only
+
+React Native supports `gap`, `rowGap`, and `columnGap`. Voltra only supports `gap`, which applies spacing between children along the main axis (the direction of `flexDirection`).
+
+### No flex wrap
+
+React Native supports `flexWrap: 'wrap'` to flow items onto multiple lines. Voltra does not — all items stay on a single line and will overflow or shrink.
+
+### No percentage dimensions
+
+React Native allows `width: '50%'` and similar percentage values. In Voltra, use `flexGrow` for proportional sizing instead.
+
+## Next Steps
+
+- [React Native Flexbox documentation](https://reactnative.dev/docs/flexbox) — comprehensive guide to flexbox properties and concepts
+- [View component documentation](../components/layout#view) — API reference for `Voltra.View`
+- [Styling in Voltra](./styling) — more about style properties
diff --git a/website/docs/ios/development/image-preloading.md b/website/docs/v2/ios/development/image-preloading.md
similarity index 100%
rename from website/docs/ios/development/image-preloading.md
rename to website/docs/v2/ios/development/image-preloading.md
diff --git a/website/docs/ios/development/images.md b/website/docs/v2/ios/development/images.md
similarity index 100%
rename from website/docs/ios/development/images.md
rename to website/docs/v2/ios/development/images.md
diff --git a/website/docs/ios/development/interactions.md b/website/docs/v2/ios/development/interactions.md
similarity index 100%
rename from website/docs/ios/development/interactions.md
rename to website/docs/v2/ios/development/interactions.md
diff --git a/website/docs/ios/development/managing-live-activities-locally.md b/website/docs/v2/ios/development/managing-live-activities-locally.md
similarity index 100%
rename from website/docs/ios/development/managing-live-activities-locally.md
rename to website/docs/v2/ios/development/managing-live-activities-locally.md
diff --git a/website/docs/ios/development/performance.md b/website/docs/v2/ios/development/performance.md
similarity index 100%
rename from website/docs/ios/development/performance.md
rename to website/docs/v2/ios/development/performance.md
diff --git a/website/docs/ios/development/querying-active-widgets.md b/website/docs/v2/ios/development/querying-active-widgets.md
similarity index 100%
rename from website/docs/ios/development/querying-active-widgets.md
rename to website/docs/v2/ios/development/querying-active-widgets.md
diff --git a/website/docs/ios/development/server-driven-widgets.md b/website/docs/v2/ios/development/server-driven-widgets.md
similarity index 100%
rename from website/docs/ios/development/server-driven-widgets.md
rename to website/docs/v2/ios/development/server-driven-widgets.md
diff --git a/website/docs/ios/development/server-side-updates.md b/website/docs/v2/ios/development/server-side-updates.md
similarity index 100%
rename from website/docs/ios/development/server-side-updates.md
rename to website/docs/v2/ios/development/server-side-updates.md
diff --git a/website/docs/ios/development/styling.md b/website/docs/v2/ios/development/styling.md
similarity index 100%
rename from website/docs/ios/development/styling.md
rename to website/docs/v2/ios/development/styling.md
diff --git a/website/docs/ios/development/widget-pre-rendering.md b/website/docs/v2/ios/development/widget-pre-rendering.md
similarity index 100%
rename from website/docs/ios/development/widget-pre-rendering.md
rename to website/docs/v2/ios/development/widget-pre-rendering.md
diff --git a/website/docs/ios/introduction.md b/website/docs/v2/ios/introduction.md
similarity index 100%
rename from website/docs/ios/introduction.md
rename to website/docs/v2/ios/introduction.md
diff --git a/website/docs/ios/setup.mdx b/website/docs/v2/ios/setup.mdx
similarity index 100%
rename from website/docs/ios/setup.mdx
rename to website/docs/v2/ios/setup.mdx
diff --git a/website/docs/v2/public/favicon.ico b/website/docs/v2/public/favicon.ico
new file mode 100644
index 00000000..302f3443
Binary files /dev/null and b/website/docs/v2/public/favicon.ico differ
diff --git a/website/docs/v2/public/icons/card-id.svg b/website/docs/v2/public/icons/card-id.svg
new file mode 100644
index 00000000..cbdb152e
--- /dev/null
+++ b/website/docs/v2/public/icons/card-id.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/docs/v2/public/icons/notification.svg b/website/docs/v2/public/icons/notification.svg
new file mode 100644
index 00000000..607e22f3
--- /dev/null
+++ b/website/docs/v2/public/icons/notification.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/docs/v2/public/icons/radio-signal.svg b/website/docs/v2/public/icons/radio-signal.svg
new file mode 100644
index 00000000..5abddc61
--- /dev/null
+++ b/website/docs/v2/public/icons/radio-signal.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/docs/v2/public/icons/subscriptions.svg b/website/docs/v2/public/icons/subscriptions.svg
new file mode 100644
index 00000000..a778dc84
--- /dev/null
+++ b/website/docs/v2/public/icons/subscriptions.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/docs/v2/public/logo-dark.svg b/website/docs/v2/public/logo-dark.svg
new file mode 100644
index 00000000..19f5fbb6
--- /dev/null
+++ b/website/docs/v2/public/logo-dark.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/website/docs/v2/public/logo-light.svg b/website/docs/v2/public/logo-light.svg
new file mode 100644
index 00000000..d3a68eeb
--- /dev/null
+++ b/website/docs/v2/public/logo-light.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/website/docs/v2/public/og-image.png b/website/docs/v2/public/og-image.png
new file mode 100644
index 00000000..4937fae7
Binary files /dev/null and b/website/docs/v2/public/og-image.png differ
diff --git a/website/docs/v2/public/voltra-baner.jpg b/website/docs/v2/public/voltra-baner.jpg
new file mode 100644
index 00000000..592c5103
Binary files /dev/null and b/website/docs/v2/public/voltra-baner.jpg differ
diff --git a/website/package-lock.json b/website/package-lock.json
index 81cb2a5f..74d4a648 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -8,9 +8,9 @@
"name": "voltra-website",
"version": "1.0.0",
"dependencies": {
- "@callstack/rspress-preset": "0.5.1",
- "@callstack/rspress-theme": "0.5.1",
- "@rspress/core": "2.0.0-rc.4",
+ "@callstack/rspress-preset": "0.6.6",
+ "@callstack/rspress-theme": "0.6.6",
+ "@rspress/core": "2.0.8",
"@shikijs/transformers": "^3.13.0"
},
"devDependencies": {
@@ -18,31 +18,43 @@
}
},
"node_modules/@callstack/rspress-preset": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/@callstack/rspress-preset/-/rspress-preset-0.5.1.tgz",
- "integrity": "sha512-AlfWFAClrcwtZNXU1fXrPgnoaIazIJX9Uz8oTd4bUqms4BUEq7Lb/n27JoLdQxZ1RzhXc3APTK12eEAT6DBdwg==",
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/@callstack/rspress-preset/-/rspress-preset-0.6.6.tgz",
+ "integrity": "sha512-JvhV9eYZLx3maNOqBdSQOsteg2kMH+OWUEGsItP95AEp9ht7mpk4KZXS1I2AuRvq3PKfYw6PTYtsjOzWQKHP+w==",
"license": "MIT",
"dependencies": {
- "@callstack/rspress-theme": "0.5.1",
- "@rspress/plugin-llms": "2.0.0-rc.4",
- "@rspress/plugin-sitemap": "2.0.0-rc.4",
- "@vercel/analytics": "^1.5.0",
- "rsbuild-plugin-open-graph": "^1.0.2",
- "zod": "^3.23.8"
+ "@callstack/rspress-theme": "0.6.6",
+ "@rspress/plugin-sitemap": "^2.0.8",
+ "@vercel/analytics": "^2.0.1",
+ "rsbuild-plugin-open-graph": "^1.1.2",
+ "zod": "^4.3.6"
+ },
+ "peerDependencies": {
+ "@rspress/core": "^2.0.8"
+ }
+ },
+ "node_modules/@callstack/rspress-preset/node_modules/@rspress/plugin-sitemap": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.13.tgz",
+ "integrity": "sha512-JtkNlxNuA7BzknKIrLvLQkSk0XVi7OXzrE76ma/cLvleccNWr8LGrHtrac4IrDr+HauK4WKTM2JaHGGHUdOUKw==",
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
- "@rspress/core": "2.0.0-rc.4"
+ "@rspress/core": "^2.0.10"
}
},
"node_modules/@callstack/rspress-preset/node_modules/@vercel/analytics": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.5.0.tgz",
- "integrity": "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==",
- "license": "MPL-2.0",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-2.0.1.tgz",
+ "integrity": "sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g==",
+ "license": "MIT",
"peerDependencies": {
"@remix-run/react": "^2",
"@sveltejs/kit": "^1 || ^2",
"next": ">= 13",
+ "nuxt": ">= 3",
"react": "^18 || ^19 || ^19.0.0-rc",
"svelte": ">= 4",
"vue": "^3",
@@ -58,6 +70,9 @@
"next": {
"optional": true
},
+ "nuxt": {
+ "optional": true
+ },
"react": {
"optional": true
},
@@ -73,31 +88,31 @@
}
},
"node_modules/@callstack/rspress-theme": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/@callstack/rspress-theme/-/rspress-theme-0.5.1.tgz",
- "integrity": "sha512-p8iS0icR9JbAvYWKOeglxfnJwwCATEpItjDoFW5srTYNFw4swVD1Aahox8Fv89l+wHLTzADokmC8iqqG8jnvqg==",
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/@callstack/rspress-theme/-/rspress-theme-0.6.6.tgz",
+ "integrity": "sha512-B1A9fiU8Nk7629x/7mAdFDjpXJgLAQNM2eGNAT0YkAMMJu9EXntLUbiT7wlHc5Y6w1XESn5wm3YQnHySpLQKbg==",
"license": "MIT",
"peerDependencies": {
- "@rspress/core": "^2.0.0-rc.4",
- "react": "^19.2.0",
- "react-dom": "^19.2.0"
+ "@rspress/core": "^2.0.8",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4"
}
},
"node_modules/@emnapi/core": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
- "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/wasi-threads": "1.1.0",
+ "@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
- "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -105,9 +120,9 @@
}
},
"node_modules/@emnapi/wasi-threads": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
- "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -168,125 +183,86 @@
"react": ">=16"
}
},
- "node_modules/@module-federation/error-codes": {
- "version": "0.21.6",
- "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.21.6.tgz",
- "integrity": "sha512-MLJUCQ05KnoVl8xd6xs9a5g2/8U+eWmVxg7xiBMeR0+7OjdWUbHwcwgVFatRIwSZvFgKHfWEiI7wsU1q1XbTRQ==",
- "license": "MIT"
- },
- "node_modules/@module-federation/runtime": {
- "version": "0.21.6",
- "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.21.6.tgz",
- "integrity": "sha512-+caXwaQqwTNh+CQqyb4mZmXq7iEemRDrTZQGD+zyeH454JAYnJ3s/3oDFizdH6245pk+NiqDyOOkHzzFQorKhQ==",
- "license": "MIT",
- "dependencies": {
- "@module-federation/error-codes": "0.21.6",
- "@module-federation/runtime-core": "0.21.6",
- "@module-federation/sdk": "0.21.6"
- }
- },
- "node_modules/@module-federation/runtime-core": {
- "version": "0.21.6",
- "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.21.6.tgz",
- "integrity": "sha512-5Hd1Y5qp5lU/aTiK66lidMlM/4ji2gr3EXAtJdreJzkY+bKcI5+21GRcliZ4RAkICmvdxQU5PHPL71XmNc7Lsw==",
- "license": "MIT",
- "dependencies": {
- "@module-federation/error-codes": "0.21.6",
- "@module-federation/sdk": "0.21.6"
- }
- },
- "node_modules/@module-federation/runtime-tools": {
- "version": "0.21.6",
- "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.21.6.tgz",
- "integrity": "sha512-fnP+ZOZTFeBGiTAnxve+axGmiYn2D60h86nUISXjXClK3LUY1krUfPgf6MaD4YDJ4i51OGXZWPekeMe16pkd8Q==",
- "license": "MIT",
- "dependencies": {
- "@module-federation/runtime": "0.21.6",
- "@module-federation/webpack-bundler-runtime": "0.21.6"
- }
- },
- "node_modules/@module-federation/sdk": {
- "version": "0.21.6",
- "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.21.6.tgz",
- "integrity": "sha512-x6hARETb8iqHVhEsQBysuWpznNZViUh84qV2yE7AD+g7uIzHKiYdoWqj10posbo5XKf/147qgWDzKZoKoEP2dw==",
- "license": "MIT"
- },
- "node_modules/@module-federation/webpack-bundler-runtime": {
- "version": "0.21.6",
- "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.21.6.tgz",
- "integrity": "sha512-7zIp3LrcWbhGuFDTUMLJ2FJvcwjlddqhWGxi/MW3ur1a+HaO8v5tF2nl+vElKmbG1DFLU/52l3PElVcWf/YcsQ==",
- "license": "MIT",
- "dependencies": {
- "@module-federation/runtime": "0.21.6",
- "@module-federation/sdk": "0.21.6"
- }
- },
"node_modules/@napi-rs/wasm-runtime": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
- "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
+ "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "^1.5.0",
- "@emnapi/runtime": "^1.5.0",
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
"@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@rsbuild/core": {
- "version": "1.6.15",
- "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-1.6.15.tgz",
- "integrity": "sha512-LvoOF53PL6zXgdzEhgnnP51S4FseDFH1bHrobK4EK6zZX/tN8qgf5tdlmN7h4OkMv/Qs1oUfvj0QcLWSstnnvA==",
+ "version": "2.0.0-beta.11",
+ "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.11.tgz",
+ "integrity": "sha512-IBbQx7SrnSpD7j2p2qyq3qDxoqmG4E6lcflTpbBitX6iUrzpVRQbP4rktXZ2iuY7ph9+FtUK/SVAVA+Ocm3Nig==",
"license": "MIT",
"dependencies": {
- "@rspack/core": "1.6.8",
- "@rspack/lite-tapable": "~1.1.0",
- "@swc/helpers": "^0.5.17",
- "core-js": "~3.47.0",
- "jiti": "^2.6.1"
+ "@rspack/core": "2.0.0-beta.9",
+ "@swc/helpers": "^0.5.20"
},
"bin": {
"rsbuild": "bin/rsbuild.js"
},
"engines": {
- "node": ">=18.12.0"
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "core-js": ">= 3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "core-js": {
+ "optional": true
+ }
}
},
"node_modules/@rsbuild/plugin-react": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/@rsbuild/plugin-react/-/plugin-react-1.4.3.tgz",
- "integrity": "sha512-Uf9FkKk2TqYDbsypXHgjdivPmEoYFvBTHJT5fPmjCf0Sc3lR2BHtlc0WMdZTCKUJ5jTxREygMs9LbnehX98L8g==",
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/@rsbuild/plugin-react/-/plugin-react-1.4.6.tgz",
+ "integrity": "sha512-LAT6xHlEyZKA0VjF/ph5d50iyG+WSmBx+7g98HNZUwb94VeeTMZFB8qVptTkbIRMss3BNKOXmHOu71Lhsh9oEw==",
"license": "MIT",
"dependencies": {
- "@rspack/plugin-react-refresh": "^1.5.3",
+ "@rspack/plugin-react-refresh": "^1.6.1",
"react-refresh": "^0.18.0"
},
"peerDependencies": {
"@rsbuild/core": "^1.0.0 || ^2.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@rsbuild/core": {
+ "optional": true
+ }
}
},
"node_modules/@rspack/binding": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.6.8.tgz",
- "integrity": "sha512-lUeL4mbwGo+nqRKqFDCm9vH2jv9FNMVt1X8jqayWRcOCPlj/2UVMEFgqjR7Pp2vlvnTKq//31KbDBJmDZq31RQ==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.9.tgz",
+ "integrity": "sha512-QgkOvzl6BJc4Vg5eaY9r7MkHNfXvVZPgTIeYkdBEOYPowdyCLhlG9vH7QltqLKP9KDNel70YIeMyUrpTqez01w==",
"license": "MIT",
"optionalDependencies": {
- "@rspack/binding-darwin-arm64": "1.6.8",
- "@rspack/binding-darwin-x64": "1.6.8",
- "@rspack/binding-linux-arm64-gnu": "1.6.8",
- "@rspack/binding-linux-arm64-musl": "1.6.8",
- "@rspack/binding-linux-x64-gnu": "1.6.8",
- "@rspack/binding-linux-x64-musl": "1.6.8",
- "@rspack/binding-wasm32-wasi": "1.6.8",
- "@rspack/binding-win32-arm64-msvc": "1.6.8",
- "@rspack/binding-win32-ia32-msvc": "1.6.8",
- "@rspack/binding-win32-x64-msvc": "1.6.8"
+ "@rspack/binding-darwin-arm64": "2.0.0-beta.9",
+ "@rspack/binding-darwin-x64": "2.0.0-beta.9",
+ "@rspack/binding-linux-arm64-gnu": "2.0.0-beta.9",
+ "@rspack/binding-linux-arm64-musl": "2.0.0-beta.9",
+ "@rspack/binding-linux-x64-gnu": "2.0.0-beta.9",
+ "@rspack/binding-linux-x64-musl": "2.0.0-beta.9",
+ "@rspack/binding-wasm32-wasi": "2.0.0-beta.9",
+ "@rspack/binding-win32-arm64-msvc": "2.0.0-beta.9",
+ "@rspack/binding-win32-ia32-msvc": "2.0.0-beta.9",
+ "@rspack/binding-win32-x64-msvc": "2.0.0-beta.9"
}
},
"node_modules/@rspack/binding-darwin-arm64": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.6.8.tgz",
- "integrity": "sha512-e8CTQtzaeGnf+BIzR7wRMUwKfIg0jd/sxMRc1Vd0bCMHBhSN9EsGoMuJJaKeRrSmy2nwMCNWHIG+TvT1CEKg+A==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.9.tgz",
+ "integrity": "sha512-9Aao24b+lrVGG25itl2c7e6HK6eNH5J5ao1Uq5UoSwSJZOxRPuY+QlHIvE2tyt833Ly9qcT1J7os2AIUNlF6Vw==",
"cpu": [
"arm64"
],
@@ -297,9 +273,9 @@
]
},
"node_modules/@rspack/binding-darwin-x64": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.6.8.tgz",
- "integrity": "sha512-ku1XpTEPt6Za11zhpFWhfwrTQogcgi9RJrOUVC4FESiPO9aKyd4hJ+JiPgLY0MZOqsptK6vEAgOip+uDVXrCpg==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.9.tgz",
+ "integrity": "sha512-sP6gusMsxm3W4aHpRsmVaBQU09n1p/1+XpLHT/gZy6nJ7Wy3nqfNKNoybNBORwCuFcGUon6cVRcieN9AEm6iJA==",
"cpu": [
"x64"
],
@@ -310,9 +286,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-gnu": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.6.8.tgz",
- "integrity": "sha512-fvZX6xZPvBT8qipSpvkKMX5M7yd2BSpZNCZXcefw6gA3uC7LI3gu+er0LrDXY1PtPzVuHTyDx+abwWpagV3PiQ==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.9.tgz",
+ "integrity": "sha512-k2DPN3B2qaz4L/h/R+l7rbDk/lLwbR/sayfsHZ8sLdZ3f6pvaSI9ejrsFv0nU4OmKCQsz4zYuoKTVFPtDfbGjA==",
"cpu": [
"arm64"
],
@@ -323,9 +299,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-musl": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.6.8.tgz",
- "integrity": "sha512-++XMKcMNrt59HcFBLnRaJcn70k3X0GwkAegZBVpel8xYIAgvoXT5+L8P1ExId/yTFxqedaz8DbcxQnNmMozviw==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.9.tgz",
+ "integrity": "sha512-7+XwAsqhfc2rIHMc9mY6RMBTP76RRqmUm1UjidqYdJl5hYBa5apffjeZfJYgAhVbSwKB/tUffzPpEffGUuc5kw==",
"cpu": [
"arm64"
],
@@ -336,9 +312,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-gnu": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.6.8.tgz",
- "integrity": "sha512-tv3BWkTE1TndfX+DsE1rSTg8fBevCxujNZ3MlfZ22Wfy9x1FMXTJlWG8VIOXmaaJ1wUHzv8S7cE2YUUJ2LuiCg==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.9.tgz",
+ "integrity": "sha512-z/EOUKEq5rq4sYsVSFL9uzdPtTPVA82x3gsRJlDTfEcruZZI7Y6JKUkpDYkC0LivXqyOnoOz8slAFd2/dByRtA==",
"cpu": [
"x64"
],
@@ -349,9 +325,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-musl": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.6.8.tgz",
- "integrity": "sha512-DCGgZ5/in1O3FjHWqXnDsncRy+48cMhfuUAAUyl0yDj1NpsZu9pP+xfGLvGcQTiYrVl7IH9Aojf1eShP/77WGA==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.9.tgz",
+ "integrity": "sha512-LVIXrqtAOy/DowIB04jyUyYy+5kHtZNJ0W5EJd39OwY/9gGvhgAEVvSWu7JrRAvKW1kQsV7GnRT5ninbDrRw1A==",
"cpu": [
"x64"
],
@@ -362,22 +338,22 @@
]
},
"node_modules/@rspack/binding-wasm32-wasi": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.6.8.tgz",
- "integrity": "sha512-VUwdhl/lI4m6o1OGCZ9JwtMjTV/yLY5VZTQdEPKb40JMTlmZ5MBlr5xk7ByaXXYHr6I+qnqEm73iMKQvg6iknw==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.9.tgz",
+ "integrity": "sha512-Vl7aDAt7DCqtZ/RJd8hLFjQqufX+efL/XZG3qADsagl/SspH1ItJ7N6X1S8o50eKoshy27Jr7mQYZEdufX9qhQ==",
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
- "@napi-rs/wasm-runtime": "1.0.7"
+ "@napi-rs/wasm-runtime": "1.1.1"
}
},
"node_modules/@rspack/binding-win32-arm64-msvc": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.6.8.tgz",
- "integrity": "sha512-23YX7zlOZlub+nPGDBUzktb4D5D6ETUAluKjXEeHIZ9m7fSlEYBnGL66YE+3t1DHXGd0OqsdwlvrNGcyo6EXDQ==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.9.tgz",
+ "integrity": "sha512-g4Fc3JjfibuHt5ltoV64eK0bs6NKlh8kgHA8Go3ETwEGO6OBck877e+5CqPtjTH8c1/KQPbnCoccGR1OScoZGg==",
"cpu": [
"arm64"
],
@@ -388,9 +364,9 @@
]
},
"node_modules/@rspack/binding-win32-ia32-msvc": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.6.8.tgz",
- "integrity": "sha512-cFgRE3APxrY4AEdooVk2LtipwNNT/9mrnjdC5lVbsIsz+SxvGbZR231bxDJEqP15+RJOaD07FO1sIjINFqXMEg==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.9.tgz",
+ "integrity": "sha512-Oii4HpCEH3CBDKSXcS6EVlV9nGYVKAV/uBLSsuZ0RNdEG0i+OHvEiicqHAwuIYZNlH4Ea/Vwc+Dl5PM2twCZ4Q==",
"cpu": [
"ia32"
],
@@ -401,9 +377,9 @@
]
},
"node_modules/@rspack/binding-win32-x64-msvc": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.6.8.tgz",
- "integrity": "sha512-cIuhVsZYd3o3Neo1JSAhJYw6BDvlxaBoqvgwRkG1rs0ExFmEmgYyG7ip9pFKnKNWph/tmW3rDYypmEfjs1is7g==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.9.tgz",
+ "integrity": "sha512-7UFjyy7QMtWvf1CBEVQkHL6bJBKaVY9yq9+Qxb7ggtxvpBbkoYykdsrhMTvr/f5TBjBqHmyeb0/oYXqo5pWFBQ==",
"cpu": [
"x64"
],
@@ -414,41 +390,36 @@
]
},
"node_modules/@rspack/core": {
- "version": "1.6.8",
- "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.6.8.tgz",
- "integrity": "sha512-FolcIAH5FW4J2FET+qwjd1kNeFbCkd0VLuIHO0thyolEjaPSxw5qxG67DA7BZGm6PVcoiSgPLks1DL6eZ8c+fA==",
+ "version": "2.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.9.tgz",
+ "integrity": "sha512-4sN3f72l4cj8n/dSCdWn6FkSjfHiDxHWrO1Kmqd0Bk0MmgyW+ldHitsSWPETCAxjTJGXY34r5sou5sYzb0DRww==",
"license": "MIT",
"dependencies": {
- "@module-federation/runtime-tools": "0.21.6",
- "@rspack/binding": "1.6.8",
- "@rspack/lite-tapable": "1.1.0"
+ "@rspack/binding": "2.0.0-beta.9"
},
"engines": {
- "node": ">=18.12.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
+ "@module-federation/runtime-tools": "^0.24.1 || ^2.0.0",
"@swc/helpers": ">=0.5.1"
},
"peerDependenciesMeta": {
+ "@module-federation/runtime-tools": {
+ "optional": true
+ },
"@swc/helpers": {
"optional": true
}
}
},
- "node_modules/@rspack/lite-tapable": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz",
- "integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==",
- "license": "MIT"
- },
"node_modules/@rspack/plugin-react-refresh": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.0.tgz",
- "integrity": "sha512-OO53gkrte/Ty4iRXxxM6lkwPGxsSsupFKdrPFnjwFIYrPvFLjeolAl5cTx+FzO5hYygJiGnw7iEKTmD+ptxqDA==",
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.2.tgz",
+ "integrity": "sha512-k+/VrfTNgo+KirjI6V+8CWRj6y+DH9jOUWv8JorYY4vKf/9xfnZ8xHzuB4iqCwTtoZl9YnxOaOuoyjJipc2tiQ==",
"license": "MIT",
"dependencies": {
- "error-stack-parser": "^2.1.4",
- "html-entities": "^2.6.0"
+ "error-stack-parser": "^2.1.4"
},
"peerDependencies": {
"react-refresh": ">=0.10.0 <1.0.0",
@@ -461,23 +432,21 @@
}
},
"node_modules/@rspress/core": {
- "version": "2.0.0-rc.4",
- "resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.0-rc.4.tgz",
- "integrity": "sha512-EHJjbc8yA/La6sJN3bjZus24KhSCdh5lEIVtjt7EBEnLteDN+wULzO1sFKj8agH3BXYE0XOuz2W1HbTTGfcGJQ==",
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.8.tgz",
+ "integrity": "sha512-MDkpm6fO0+NoW+Lx0KVL/n9DSRGQcoggeXY+EtlC+ySqF9VxQk4hu87fQhD8q2ikMOd7lbVsWmKspd3rIFD88g==",
"license": "MIT",
"dependencies": {
"@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.1",
- "@rsbuild/core": "~1.6.15",
- "@rsbuild/plugin-react": "~1.4.2",
- "@rspress/mdx-rs": "0.6.6",
- "@rspress/runtime": "2.0.0-rc.4",
- "@rspress/shared": "2.0.0-rc.4",
- "@shikijs/rehype": "^3.20.0",
+ "@rsbuild/core": "2.0.0-beta.11",
+ "@rsbuild/plugin-react": "~1.4.6",
+ "@rspress/shared": "2.0.8",
+ "@shikijs/rehype": "^4.0.2",
"@types/unist": "^3.0.3",
- "@unhead/react": "^2.0.19",
+ "@unhead/react": "^2.1.12",
"body-scroll-lock": "4.0.0-beta.0",
- "cac": "^6.7.14",
+ "cac": "^7.0.0",
"chokidar": "^3.6.0",
"clsx": "2.1.1",
"copy-to-clipboard": "^3.3.3",
@@ -485,28 +454,32 @@
"github-slugger": "^2.0.0",
"hast-util-heading-rank": "^3.0.0",
"hast-util-to-jsx-runtime": "^2.3.6",
- "html-to-text": "^9.0.5",
- "lodash-es": "^4.17.22",
+ "lodash-es": "^4.17.23",
"mdast-util-mdx": "^3.0.0",
"mdast-util-mdxjs-esm": "^2.0.1",
"medium-zoom": "1.1.0",
"nprogress": "^0.2.0",
"picocolors": "^1.1.1",
- "react": "^19.2.3",
- "react-dom": "^19.2.3",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
"react-lazy-with-preload": "^2.2.1",
"react-reconciler": "0.33.0",
- "react-router-dom": "^7.11.0",
+ "react-render-to-markdown": "19.0.1",
+ "react-router-dom": "^7.13.2",
"rehype-external-links": "^3.0.0",
"rehype-raw": "^7.0.0",
+ "remark-cjk-friendly": "^2.0.1",
+ "remark-cjk-friendly-gfm-strikethrough": "^2.0.1",
"remark-gfm": "^4.0.1",
"remark-mdx": "^3.1.1",
+ "remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"scroll-into-view-if-needed": "^3.1.0",
- "shiki": "^3.20.0",
+ "shiki": "^4.0.2",
"tinyglobby": "^0.2.15",
"tinypool": "^1.1.1",
"unified": "^11.0.5",
+ "unist-util-remove": "^4.0.0",
"unist-util-visit": "^5.0.0",
"unist-util-visit-children": "^3.0.0"
},
@@ -514,229 +487,22 @@
"rspress": "bin/rspress.js"
},
"engines": {
- "node": ">=20.9.0"
- }
- },
- "node_modules/@rspress/mdx-rs": {
- "version": "0.6.6",
- "resolved": "https://registry.npmjs.org/@rspress/mdx-rs/-/mdx-rs-0.6.6.tgz",
- "integrity": "sha512-NpNhTKBIlV3O6ADhoZkgHvBFvXMW2TYlIWmIT1ysJESUBqDpaN9H3Teve5fugjU2pQ2ORBZO6SQGKliMw/8m/Q==",
- "license": "MIT",
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@rspress/mdx-rs-darwin-arm64": "0.6.6",
- "@rspress/mdx-rs-darwin-x64": "0.6.6",
- "@rspress/mdx-rs-linux-arm64-gnu": "0.6.6",
- "@rspress/mdx-rs-linux-arm64-musl": "0.6.6",
- "@rspress/mdx-rs-linux-x64-gnu": "0.6.6",
- "@rspress/mdx-rs-linux-x64-musl": "0.6.6",
- "@rspress/mdx-rs-win32-arm64-msvc": "0.6.6",
- "@rspress/mdx-rs-win32-x64-msvc": "0.6.6"
- }
- },
- "node_modules/@rspress/mdx-rs-darwin-arm64": {
- "version": "0.6.6",
- "resolved": "https://registry.npmjs.org/@rspress/mdx-rs-darwin-arm64/-/mdx-rs-darwin-arm64-0.6.6.tgz",
- "integrity": "sha512-fsuhUko2VJin9oZvGDEM8FWIisbhTe+ki8SiiVMqtl6OUtga9wB8F3JmsjVNg615lHp7FiT66Mvfbxweo+jjTQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=14.12"
- }
- },
- "node_modules/@rspress/mdx-rs-darwin-x64": {
- "version": "0.6.6",
- "resolved": "https://registry.npmjs.org/@rspress/mdx-rs-darwin-x64/-/mdx-rs-darwin-x64-0.6.6.tgz",
- "integrity": "sha512-LAkc4H9cODxOsZLMsX57ma8Kk+KZytLTgkGTUXBX2M88O5ucZzrdBWFNXP8EvNcVcDR4O+YwcZPYMlZDqRyX5A==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=14.12"
- }
- },
- "node_modules/@rspress/mdx-rs-linux-arm64-gnu": {
- "version": "0.6.6",
- "resolved": "https://registry.npmjs.org/@rspress/mdx-rs-linux-arm64-gnu/-/mdx-rs-linux-arm64-gnu-0.6.6.tgz",
- "integrity": "sha512-l18CBbqFsn1NOWngdcfKVbqAGYsNouQw/WNAUxoKX3kPh+TsWxGZR2vBnPQ+In4yNzSz5AVMPKBMah2YNIFmXA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.12"
- }
- },
- "node_modules/@rspress/mdx-rs-linux-arm64-musl": {
- "version": "0.6.6",
- "resolved": "https://registry.npmjs.org/@rspress/mdx-rs-linux-arm64-musl/-/mdx-rs-linux-arm64-musl-0.6.6.tgz",
- "integrity": "sha512-diwYLjMUlK1CSoZ0D6Lrdd31B60SgGlGqvvWs49PqDFpb+/wbBuKTGfjx+bzPmRBvSgjDUJuNkh3tHldj9wpXg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.12"
- }
- },
- "node_modules/@rspress/mdx-rs-linux-x64-gnu": {
- "version": "0.6.6",
- "resolved": "https://registry.npmjs.org/@rspress/mdx-rs-linux-x64-gnu/-/mdx-rs-linux-x64-gnu-0.6.6.tgz",
- "integrity": "sha512-Qie1XlZ55qn2nyXZ5DO3vSYa8xiiTiT8vjh5gIkNMhYh/qvUefJTgp8RC+DFsdlyxSVHRWSTAiWchFyhpW6QCw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.12"
- }
- },
- "node_modules/@rspress/mdx-rs-linux-x64-musl": {
- "version": "0.6.6",
- "resolved": "https://registry.npmjs.org/@rspress/mdx-rs-linux-x64-musl/-/mdx-rs-linux-x64-musl-0.6.6.tgz",
- "integrity": "sha512-IegWebLUvioMIMQGo7JDW2sR3JOFOuJl/blX5Vy/fwHvfznIscRcJlu/Va6brMHkgv36fgXgCv7Yt3JwXGQaTQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.12"
- }
- },
- "node_modules/@rspress/mdx-rs-win32-arm64-msvc": {
- "version": "0.6.6",
- "resolved": "https://registry.npmjs.org/@rspress/mdx-rs-win32-arm64-msvc/-/mdx-rs-win32-arm64-msvc-0.6.6.tgz",
- "integrity": "sha512-EA/BNOhTvF6dE+vdoIBxZaHxynLjL46qxiyHhNj0+no0lcBS2NbeWIgl2ge3O35n5h7Pj0sbmchHazpXwgDNcg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=14.12"
- }
- },
- "node_modules/@rspress/mdx-rs-win32-x64-msvc": {
- "version": "0.6.6",
- "resolved": "https://registry.npmjs.org/@rspress/mdx-rs-win32-x64-msvc/-/mdx-rs-win32-x64-msvc-0.6.6.tgz",
- "integrity": "sha512-P6XbuHD+TRw73lqWxWf8Zb8/+MgHO4pCv4h1QoumxyFz0+2C+47576eBPimprWHgq066AZ34q3+037mrbZdvAA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=14.12"
- }
- },
- "node_modules/@rspress/plugin-llms": {
- "version": "2.0.0-rc.4",
- "resolved": "https://registry.npmjs.org/@rspress/plugin-llms/-/plugin-llms-2.0.0-rc.4.tgz",
- "integrity": "sha512-PSG8JOO2EOqCeViif48puAeiP2pBhe//v0sC8dm9wGmQsrq8xbx2rH1aiQElZIwcoximqukzHavrU6O3qPW2NA==",
- "license": "MIT",
- "dependencies": {
- "remark-mdx": "^3.1.1",
- "remark-parse": "^11.0.0",
- "remark-stringify": "^11.0.0",
- "unified": "^11.0.5",
- "unist-util-visit": "^5.0.0"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "peerDependencies": {
- "@rspress/core": "^2.0.0-rc.4"
- }
- },
- "node_modules/@rspress/plugin-sitemap": {
- "version": "2.0.0-rc.4",
- "resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.0-rc.4.tgz",
- "integrity": "sha512-sr900krxs9ZVfbV+b/ig7t8mAPaMOonWz2Qj9zXg4TgEgnh+LnNtHQpHYeFbiTF5WAozVxdu5lJ1E36oh231pQ==",
- "license": "MIT",
- "engines": {
- "node": ">=20.9.0"
- },
- "peerDependencies": {
- "@rspress/core": "^2.0.0-rc.4"
- }
- },
- "node_modules/@rspress/runtime": {
- "version": "2.0.0-rc.4",
- "resolved": "https://registry.npmjs.org/@rspress/runtime/-/runtime-2.0.0-rc.4.tgz",
- "integrity": "sha512-BcNs6zpIXcWfhXGCDS525HDwSgLXO+S+q2Ty6sQPmyYJzOg+V9tHIlulKTOfuBCyEhTZeTAJofP4+rG4EZBUgw==",
- "license": "MIT",
- "dependencies": {
- "@rspress/shared": "2.0.0-rc.4",
- "@unhead/react": "^2.0.19",
- "react": "^19.2.3",
- "react-dom": "^19.2.3",
- "react-router-dom": "^7.11.0"
- },
- "engines": {
- "node": ">=20.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rspress/shared": {
- "version": "2.0.0-rc.4",
- "resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.0-rc.4.tgz",
- "integrity": "sha512-46jTwxV8SRLMbe3euCEMWQudmdYE2Tf+EN/5l6EP10i6QICOXMIzUFFWfr0LOTr4aZCSTSJu7Tp6Xxml6JbheA==",
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.8.tgz",
+ "integrity": "sha512-kvfBUvMvWcn/7PJHqZxPeu1yblzvAuB1/gk/1orp5KsYu3wbZ7X3Hsm9smDJVs5Plw1iPt67t9fOYNSM0+VjUA==",
"license": "MIT",
"dependencies": {
- "@rsbuild/core": "~1.6.15",
- "@shikijs/rehype": "^3.20.0",
+ "@rsbuild/core": "2.0.0-beta.11",
+ "@shikijs/rehype": "^4.0.2",
"gray-matter": "4.0.3",
- "lodash-es": "^4.17.22",
+ "lodash-es": "^4.17.23",
"unified": "^11.0.5"
}
},
- "node_modules/@selderee/plugin-htmlparser2": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
- "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==",
- "license": "MIT",
- "dependencies": {
- "domhandler": "^5.0.3",
- "selderee": "^0.11.0"
- },
- "funding": {
- "url": "https://ko-fi.com/killymxi"
- }
- },
"node_modules/@shikijs/core": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.17.1.tgz",
@@ -750,106 +516,163 @@
}
},
"node_modules/@shikijs/engine-javascript": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.21.0.tgz",
- "integrity": "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.2.0.tgz",
+ "integrity": "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.21.0",
+ "@shikijs/types": "4.2.0",
"@shikijs/vscode-textmate": "^10.0.2",
- "oniguruma-to-es": "^4.3.4"
+ "oniguruma-to-es": "^4.3.6"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/engine-javascript/node_modules/@shikijs/types": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz",
- "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
+ "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/engine-oniguruma": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.21.0.tgz",
- "integrity": "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.2.0.tgz",
+ "integrity": "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.21.0",
+ "@shikijs/types": "4.2.0",
"@shikijs/vscode-textmate": "^10.0.2"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/engine-oniguruma/node_modules/@shikijs/types": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz",
- "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
+ "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/langs": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.21.0.tgz",
- "integrity": "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.2.0.tgz",
+ "integrity": "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.21.0"
+ "@shikijs/types": "4.2.0"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/langs/node_modules/@shikijs/types": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz",
- "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
+ "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/primitive": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.2.0.tgz",
+ "integrity": "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "4.2.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/primitive/node_modules/@shikijs/types": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
+ "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/rehype": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-3.21.0.tgz",
- "integrity": "sha512-fTQvwsZL67QdosMFdTgQ5SNjW3nxaPplRy//312hqOctRbIwviTV0nAbhv3NfnztHXvFli2zLYNKsTz/f9tbpQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.2.0.tgz",
+ "integrity": "sha512-ST3EWye/dwF1gWskczJNBnwFtDzEQ9ceytXZtyc/GfwR5V0qJrkoSGZO55O3SAKDDsXkTDcsfwd9pVe7ROlAHg==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.21.0",
+ "@shikijs/types": "4.2.0",
"@types/hast": "^3.0.4",
"hast-util-to-string": "^3.0.1",
- "shiki": "3.21.0",
+ "shiki": "4.2.0",
"unified": "^11.0.5",
- "unist-util-visit": "^5.0.0"
+ "unist-util-visit": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/rehype/node_modules/@shikijs/types": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz",
- "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
+ "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/themes": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.21.0.tgz",
- "integrity": "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.2.0.tgz",
+ "integrity": "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.21.0"
+ "@shikijs/types": "4.2.0"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/themes/node_modules/@shikijs/types": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz",
- "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
+ "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@shikijs/transformers": {
@@ -879,18 +702,18 @@
"license": "MIT"
},
"node_modules/@swc/helpers": {
- "version": "0.5.18",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz",
- "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
+ "version": "0.5.23",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
+ "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@tybys/wasm-util": {
- "version": "0.10.1",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
- "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -898,18 +721,18 @@
}
},
"node_modules/@types/debug": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
- "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
"license": "MIT",
"dependencies": {
"@types/ms": "*"
}
},
"node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"license": "MIT"
},
"node_modules/@types/estree-jsx": {
@@ -962,9 +785,9 @@
}
},
"node_modules/@types/react": {
- "version": "19.2.9",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
- "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
+ "version": "19.2.16",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz",
+ "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==",
"license": "MIT",
"peer": true,
"dependencies": {
@@ -984,12 +807,12 @@
"license": "ISC"
},
"node_modules/@unhead/react": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.2.tgz",
- "integrity": "sha512-VNKa0JJZq5Jp28VuiOMfjAA7CTLHI0SdW/Hs1ZPq2PsNV/cgxGv8quFBGXWx4gfoHB52pejO929RKjIpYX5+iQ==",
+ "version": "2.1.15",
+ "resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.15.tgz",
+ "integrity": "sha512-5hfAaZ3XJq9JkspRzZdSPsMrXXA8v/SKiEOxZcN9L40o44byF/50bcQuOLgSSCAx8802mI5VG32KZXWTtsLu9Q==",
"license": "MIT",
"dependencies": {
- "unhead": "2.1.2"
+ "unhead": "2.1.15"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
@@ -999,9 +822,9 @@
}
},
"node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -1091,12 +914,12 @@
}
},
"node_modules/cac": {
- "version": "6.7.14",
- "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
- "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz",
+ "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=20.19.0"
}
},
"node_modules/ccount": {
@@ -1230,17 +1053,6 @@
"toggle-selection": "^1.0.6"
}
},
- "node_modules/core-js": {
- "version": "3.47.0",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz",
- "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
- "hasInstallScript": true,
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/core-js"
- }
- },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -1278,15 +1090,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -1309,65 +1112,10 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/dom-serializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
- "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
- "license": "MIT",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.2",
- "entities": "^4.2.0"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
- }
- },
- "node_modules/domelementtype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
- "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "license": "BSD-2-Clause"
- },
- "node_modules/domhandler": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
- "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
- "license": "BSD-2-Clause",
- "dependencies": {
- "domelementtype": "^2.3.0"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/domutils": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
- "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
- "license": "BSD-2-Clause",
- "dependencies": {
- "dom-serializer": "^2.0.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3"
- },
- "funding": {
- "url": "https://github.com/fb55/domutils?sponsor=1"
- }
- },
"node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -1605,6 +1353,18 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/github-slugger": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
@@ -1863,43 +1623,11 @@
}
},
"node_modules/hookable": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.0.1.tgz",
- "integrity": "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==",
- "license": "MIT"
- },
- "node_modules/html-entities": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
- "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/mdevils"
- },
- {
- "type": "patreon",
- "url": "https://patreon.com/mdevils"
- }
- ],
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz",
+ "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==",
"license": "MIT"
},
- "node_modules/html-to-text": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
- "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==",
- "license": "MIT",
- "dependencies": {
- "@selderee/plugin-htmlparser2": "^0.11.0",
- "deepmerge": "^4.3.1",
- "dom-serializer": "^2.0.0",
- "htmlparser2": "^8.0.2",
- "selderee": "^0.11.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
"node_modules/html-void-elements": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
@@ -1910,25 +1638,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/htmlparser2": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
- "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
- "funding": [
- "https://github.com/fb55/htmlparser2?sponsor=1",
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.0.1",
- "entities": "^4.4.0"
- }
- },
"node_modules/inline-style-parser": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
@@ -2054,15 +1763,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jiti": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
- "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
- "license": "MIT",
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
"node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
@@ -2085,19 +1785,10 @@
"node": ">=0.10.0"
}
},
- "node_modules/leac": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
- "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==",
- "license": "MIT",
- "funding": {
- "url": "https://ko-fi.com/killymxi"
- }
- },
"node_modules/lodash-es": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
- "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT"
},
"node_modules/longest-streak": {
@@ -2149,9 +1840,9 @@
}
},
"node_modules/mdast-util-from-markdown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
- "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
+ "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
@@ -2494,6 +2185,77 @@
"micromark-util-types": "^2.0.0"
}
},
+ "node_modules/micromark-extension-cjk-friendly": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly/-/micromark-extension-cjk-friendly-2.0.1.tgz",
+ "integrity": "sha512-OkzoYVTL1ChbvQ8Cc1ayTIz7paFQz8iS9oIYmewncweUSwmWR+hkJF9spJ1lxB90XldJl26A1F4IkPOKS3bDXw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.1.0",
+ "micromark-extension-cjk-friendly-util": "3.0.1",
+ "micromark-util-chunked": "^2.0.1",
+ "micromark-util-resolve-all": "^2.0.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "micromark": "^4.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "micromark-util-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/micromark-extension-cjk-friendly-gfm-strikethrough": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly-gfm-strikethrough/-/micromark-extension-cjk-friendly-gfm-strikethrough-2.0.1.tgz",
+ "integrity": "sha512-wVC0zwjJNqQeX+bb07YTPu/CvSAyCTafyYb7sMhX1r62/Lw5M/df3JyYaANyp8g15c1ypJRFSsookTqA1IDsUg==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.1.0",
+ "get-east-asian-width": "^1.4.0",
+ "micromark-extension-cjk-friendly-util": "3.0.1",
+ "micromark-util-character": "^2.1.1",
+ "micromark-util-chunked": "^2.0.1",
+ "micromark-util-resolve-all": "^2.0.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "micromark": "^4.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "micromark-util-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/micromark-extension-cjk-friendly-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly-util/-/micromark-extension-cjk-friendly-util-3.0.1.tgz",
+ "integrity": "sha512-GcbXqTTHOsiZHyF753oIddP/J2eH8j9zpyQPhkof6B2JNxfEJabnQqxbCgzJNuNes0Y2jTNJ3LiYPSXr6eJA8w==",
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.4.0",
+ "micromark-util-character": "^2.1.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "micromark-util-types": {
+ "optional": true
+ }
+ }
+ },
"node_modules/micromark-extension-gfm": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
@@ -3164,19 +2926,19 @@
"license": "MIT"
},
"node_modules/oniguruma-parser": {
- "version": "0.12.1",
- "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
- "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==",
+ "version": "0.12.2",
+ "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz",
+ "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==",
"license": "MIT"
},
"node_modules/oniguruma-to-es": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz",
- "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==",
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz",
+ "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==",
"license": "MIT",
"dependencies": {
- "oniguruma-parser": "^0.12.1",
- "regex": "^6.0.1",
+ "oniguruma-parser": "^0.12.2",
+ "regex": "^6.1.0",
"regex-recursion": "^6.0.2"
}
},
@@ -3217,40 +2979,6 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
- "node_modules/parse5/node_modules/entities": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
- "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/parseley": {
- "version": "0.12.1",
- "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz",
- "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==",
- "license": "MIT",
- "dependencies": {
- "leac": "^0.6.0",
- "peberminta": "^0.9.0"
- },
- "funding": {
- "url": "https://ko-fi.com/killymxi"
- }
- },
- "node_modules/peberminta": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
- "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==",
- "license": "MIT",
- "funding": {
- "url": "https://ko-fi.com/killymxi"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -3258,9 +2986,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -3280,24 +3008,24 @@
}
},
"node_modules/react": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
- "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
+ "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
+ "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
- "react": "^19.2.3"
+ "react": "^19.2.7"
}
},
"node_modules/react-lazy-with-preload": {
@@ -3330,10 +3058,22 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-render-to-markdown": {
+ "version": "19.0.1",
+ "resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.0.1.tgz",
+ "integrity": "sha512-BPv48o+ubcu2JyUDIktvJXFqLIZqR7hA4mvGu1eFIofz9fogT2me9UvXwRvqvGs9jEtNaJkxZIUKUX0oiK4hDA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-reconciler": "0.33.0"
+ },
+ "peerDependencies": {
+ "react": ">=19"
+ }
+ },
"node_modules/react-router": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
- "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.16.0.tgz",
+ "integrity": "sha512-wArC8lVyJb3+jM9OpDyW6hLCizACWkvQR/sSGqSs+o5uEXEtGlqdZ4v8hENR3Jad6i+LRkK93q/+bQAcvl6V1A==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
@@ -3353,12 +3093,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz",
- "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==",
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.16.0.tgz",
+ "integrity": "sha512-kMUAbimWB5FVbF4Bce4bJsiKJWLIUHq/mEG8+CFDnCSgltptBiG5nguducmsJeGKytlCvQud9Qhzpn49iduTlA==",
"license": "MIT",
"dependencies": {
- "react-router": "7.12.0"
+ "react-router": "7.16.0"
},
"engines": {
"node": ">=20.0.0"
@@ -3519,6 +3259,48 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/remark-cjk-friendly": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/remark-cjk-friendly/-/remark-cjk-friendly-2.0.1.tgz",
+ "integrity": "sha512-6WwkoQyZf/4j5k53zdFYrR8Ca+UVn992jXdLUSBDZR4eBpFhKyVxmA4gUHra/5fesjGIxrDhHesNr/sVoiiysA==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-cjk-friendly": "2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/mdast": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/mdast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/remark-cjk-friendly-gfm-strikethrough": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/remark-cjk-friendly-gfm-strikethrough/-/remark-cjk-friendly-gfm-strikethrough-2.0.1.tgz",
+ "integrity": "sha512-pWKj25O2eLXIL1aBupayl1fKhco+Brw8qWUWJPVB9EBzbQNd7nGLj0nLmJpggWsGLR5j5y40PIdjxby9IEYTuA==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-cjk-friendly-gfm-strikethrough": "2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/mdast": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/mdast": {
+ "optional": true
+ }
+ }
+ },
"node_modules/remark-gfm": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
@@ -3600,12 +3382,12 @@
}
},
"node_modules/rsbuild-plugin-open-graph": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/rsbuild-plugin-open-graph/-/rsbuild-plugin-open-graph-1.1.0.tgz",
- "integrity": "sha512-ETrmQxhs/hW7xFzDGb6CIiZ1tVOrsrGPDgPTC/49bQ+sK75y6LZ3/CvcqPNxMwdnI3Ba79l62FIFvR0uC+UISw==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/rsbuild-plugin-open-graph/-/rsbuild-plugin-open-graph-1.1.3.tgz",
+ "integrity": "sha512-jIOxWCOgYnlONDcxj4LzGpQY5h3B9Apl+e0ieJeAs6YgMvbZ948dsPW5sx8WLj0DuyI3XR8yDNU5P34XXvwXgQ==",
"license": "MIT",
"peerDependencies": {
- "@rsbuild/core": "1.x"
+ "@rsbuild/core": "^1.0.0 || ^2.0.0-0"
},
"peerDependenciesMeta": {
"@rsbuild/core": {
@@ -3641,18 +3423,6 @@
"node": ">=4"
}
},
- "node_modules/selderee": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
- "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==",
- "license": "MIT",
- "dependencies": {
- "parseley": "^0.12.0"
- },
- "funding": {
- "url": "https://ko-fi.com/killymxi"
- }
- },
"node_modules/set-cookie-parser": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
@@ -3660,41 +3430,51 @@
"license": "MIT"
},
"node_modules/shiki": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.21.0.tgz",
- "integrity": "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.2.0.tgz",
+ "integrity": "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==",
"license": "MIT",
"dependencies": {
- "@shikijs/core": "3.21.0",
- "@shikijs/engine-javascript": "3.21.0",
- "@shikijs/engine-oniguruma": "3.21.0",
- "@shikijs/langs": "3.21.0",
- "@shikijs/themes": "3.21.0",
- "@shikijs/types": "3.21.0",
+ "@shikijs/core": "4.2.0",
+ "@shikijs/engine-javascript": "4.2.0",
+ "@shikijs/engine-oniguruma": "4.2.0",
+ "@shikijs/langs": "4.2.0",
+ "@shikijs/themes": "4.2.0",
+ "@shikijs/types": "4.2.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/shiki/node_modules/@shikijs/core": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.21.0.tgz",
- "integrity": "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.2.0.tgz",
+ "integrity": "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==",
"license": "MIT",
"dependencies": {
- "@shikijs/types": "3.21.0",
+ "@shikijs/primitive": "4.2.0",
+ "@shikijs/types": "4.2.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/shiki/node_modules/@shikijs/types": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz",
- "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
+ "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/source-map": {
@@ -3770,13 +3550,13 @@
}
},
"node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
- "picomatch": "^4.0.3"
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
@@ -3803,9 +3583,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -3875,9 +3655,9 @@
"license": "MIT"
},
"node_modules/unhead": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.2.tgz",
- "integrity": "sha512-vSihrxyb+zsEUfEbraZBCjdE0p/WSoc2NGDrpwwSNAwuPxhYK1nH3eegf02IENLpn1sUhL8IoO84JWmRQ6tILA==",
+ "version": "2.1.15",
+ "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.15.tgz",
+ "integrity": "sha512-MCt5T90mCWyr3Z6pUCdM9lVRXoMoVBlL7z7U4CYVIiaDiuzad/UCfLuMqz5MeNmpZUgoBCQnrucJimU7EZR+XA==",
"license": "MIT",
"dependencies": {
"hookable": "^6.0.1"
@@ -3944,6 +3724,21 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unist-util-remove": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-4.0.0.tgz",
+ "integrity": "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/unist-util-stringify-position": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
@@ -3958,9 +3753,9 @@
}
},
"node_modules/unist-util-visit": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
- "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
@@ -4052,9 +3847,9 @@
}
},
"node_modules/zod": {
- "version": "3.25.76",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
- "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
diff --git a/website/package.json b/website/package.json
index 4411c1be..f7331a2a 100644
--- a/website/package.json
+++ b/website/package.json
@@ -8,9 +8,9 @@
"preview": "rspress preview"
},
"dependencies": {
- "@callstack/rspress-preset": "0.5.1",
- "@callstack/rspress-theme": "0.5.1",
- "@rspress/core": "2.0.0-rc.4",
+ "@callstack/rspress-preset": "0.6.6",
+ "@callstack/rspress-theme": "0.6.6",
+ "@rspress/core": "2.0.8",
"@shikijs/transformers": "^3.13.0"
},
"devDependencies": {
diff --git a/website/rspress.config.ts b/website/rspress.config.ts
index ce16901d..15533978 100644
--- a/website/rspress.config.ts
+++ b/website/rspress.config.ts
@@ -22,8 +22,15 @@ export default withCallstackPreset(
vercelAnalytics: true,
},
defineConfig({
+ multiVersion: {
+ default: 'v1',
+ versions: ['v1', 'v2'],
+ },
themeConfig: {
enableScrollToTop: true,
},
+ search: {
+ versioned: true,
+ },
})
)