FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat(cli)!: add dev server, dev clients, and browser preview (#74) · pythonnative/pythonnative@ee66d34 · GitHub

feat(cli)!: add dev server, dev clients, and browser preview (#74) · pythonnative/pythonnative@ee66d34 · GitHub
Skip to content

Navigation Menu

Commit ee66d34

Browse files
authored
feat(cli)!: add dev server, dev clients, and browser preview (#74)
1 parent d0aa342 commit ee66d34

122 files changed

Lines changed: 10143 additions & 5339 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CONTRIBUTING.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,16 +151,19 @@ Recommended scopes (choose the smallest, most accurate unit; prefer module/direc
151151
- `gestures`: gesture descriptors and the pure-Python recognition arbiter (`gestures.py`)
152152
- `hooks`: hooks and contexts (`hooks.py`)
153153
- `hosts`: screen hosts, lifecycle forwarding, and render scheduling (`hosts/`)
154-
- `hot_reload`: file watcher and module reloader (`hot_reload.py`)
154+
- `devserver`: dev server, file watcher, and WebSocket implementation (`devserver/`)
155+
- `devclient`: on-device dev client that syncs sources and Fast Refreshes (`devclient.py`)
156+
- `hot_reload`: module reloader and Fast Refresh (`hot_reload.py`)
155157
- `layout`: pure-Python flexbox engine (`layout.py`)
156158
- `mutations`: batched mutation ops between reconciler and native backends (`mutations.py`)
157-
- `native_modules`: native module registry, Python facades, and desktop implementations (`native_modules/`)
158-
- `native_views`: view registry protocol, bridge backend, and desktop handlers (`native_views/`)
159+
- `native_modules`: native module registry, Python facades, and Python fallbacks (`native_modules/`)
160+
- `native_views`: view registry protocol and bridge backend (`native_views/`)
159161
- `navigation`: navigation state, container, navigators, hooks, and linking (`navigation/`)
160162
- `net`: awaitable HTTP client (`net.py`)
161163
- `package`: `src/pythonnative/__init__.py` exports and package boundary
162164
- `platform`: `Platform.OS`/`Platform.select` and version detection (`platform.py`)
163165
- `platform_metrics`: platform-reported metrics like safe-area insets and bar heights (`platform_metrics.py`)
166+
- `preview`: `pn start` / `pn preview` session and the browser preview page (`preview.py`, `devserver/static/`)
164167
- `project`: `pythonnative.toml` config, template configurators, plugin staging, and the builder (`project/`)
165168
- `reconciler`: virtual view tree diffing, boundaries, and the layout pass (`reconciler/`)
166169
- `runtime`: framework-wide asyncio loop and thread-safe future helpers (`runtime.py`)

‎README.md‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,20 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
3535
- **Device APIs:** Cross-platform modules for `Camera`, `Location`, `FileSystem`, `Notifications`, `Clipboard`, `Share`, `Linking`, `Permissions`, `AppState`, `NetInfo`, `SecureStore`, `Battery`, `Haptics` / `Vibration`, and `Biometrics`, plus reactive `use_app_state` and `use_net_info` hooks.
3636
- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect` / `use_layout_effect`, refs and imperative handles with `use_ref` / `use_imperative_handle`, and navigation with `use_navigation`, all through one consistent pattern. Components can return a single element, a list of siblings, or `None`, and `Fragment`, `Portal`, and reactive `Provider` context work the way they do in React.
3737
- **Async-first rendering:** One `asyncio` event loop runs the whole framework on the platform's main thread. Components can be `async def` and `await` data directly in the body; `Suspense` boundaries show declarative loading states while they wait. `use_resource` fetches during render, `use_effect` accepts coroutine callbacks (cancelled automatically on unmount), `lazy` code-splits components, and `use_transition` / `use_deferred_value` keep the UI responsive during expensive updates.
38-
- **Developer feedback that finds your bugs:** In dev mode (`pn preview`, hot reload, or `PN_DEV=1`), uncaught errors from renders, effects, and event handlers show a full-screen RedBox with the traceback; unknown style keys and duplicate list keys print "did you mean" warnings; and conditional hooks raise a `HookOrderError` at the source instead of silently cross-wiring state.
38+
- **Developer feedback that finds your bugs:** In dev mode (`pn start`, `pn preview`, debug builds from `pn run`, or `PN_DEV=1`), uncaught errors from renders, effects, and event handlers show a full-screen RedBox with the traceback; unknown style keys and duplicate list keys print "did you mean" warnings; and conditional hooks raise a `HookOrderError` at the source instead of silently cross-wiring state.
3939
- **Typed `style` prop and theme:** Pass all visual and layout properties through a single `style` dict, fully described by the `pn.Style` `TypedDict` and the ergonomic `pn.style(...)` helper for IDE autocomplete and static checking. Compose reusable styles with `StyleSheet`, and read design tokens from a typed, immutable `pn.Theme` via `use_theme()` that follows light and dark mode by default.
4040
- **Cross-platform flexbox engine:** A pure-Python, Yoga-style layout engine computes frames once and applies them to native views, so `flex`, `padding`, `aspect_ratio`, and `position: "absolute"` produce the same geometry on Android and iOS.
4141
- **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation. Each commit lands as **one batched transaction** of mutation ops, and event callbacks are routed through a tag-based registry so re-renders that only change closures cost zero native calls. State updates re-render **locally**: only the component whose state changed (and its subtree) re-runs, and unchanged leaves reuse cached intrinsic measurements, so deep UIs stay responsive instead of re-rendering the whole app from the root on every tap.
4242
- **Native-driven animations:** The `Animated` API (timing / spring / decay / loop / stagger, awaitable or fire-and-forget) hands animations to Core Animation and `ViewPropertyAnimator` whenever possible, so no Python code runs per frame; a pure-Python ticker covers the rest. `interpolate`, arithmetic operators on animated nodes, `Animated.event` scroll binding, and `diff_clamp` cover the scroll-driven patterns (collapsing headers, parallax) that define native feel.
43-
- **Native gesture system:** Attach `Tap`, `LongPress`, `Pan`, `Swipe`, `Fling`, `Pinch`, and `Rotation` recognizers to any view via the `gestures=` prop, backed by `UIGestureRecognizer` on iOS and a unit-testable pure-Python arbiter on Android and desktop. Compose them with `Race`, `Exclusive`, and `Simultaneous` for cross-gesture arbitration (single vs. double tap, drag vs. long press).
43+
- **Native gesture system:** Attach `Tap`, `LongPress`, `Pan`, `Swipe`, `Fling`, `Pinch`, and `Rotation` recognizers to any view via the `gestures=` prop, backed by `UIGestureRecognizer` on iOS and a unit-testable pure-Python arbiter on Android and in the browser preview. Compose them with `Race`, `Exclusive`, and `Simultaneous` for cross-gesture arbitration (single vs. double tap, drag vs. long press).
4444
- **Virtualized lists:** `FlatList` / `SectionList` window their rows in Python over the platform scroll view: uniform, exact, or measured variable heights, grids, headers/footers, infinite scroll, and an imperative scroll controller, identical on every platform.
4545
- **Native rendering core:** each commit is one serialized transaction applied by Swift and Kotlin component managers, the same shape as React Native's Fabric. Device APIs are native modules registered by name (TurboModules-style), callable from thin Python facades.
4646
- **Custom-component SDK:** Wrap any platform widget as a first-class element with type-checked props via `pythonnative.sdk` (`Props`, `@native_component`, `element_factory`). Plugins distributed on PyPI auto-register through the `pythonnative.handlers` entry-point group.
47-
- **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
47+
- **Metro-style dev loop:** `pn start` runs one dev server that serves the browser preview and every connected debug build. Save a file and each client Fast Refreshes in place, keeping component state; their `print` output and tracebacks stream back into the same terminal. `pn run android` / `pn run ios` bakes the server URL into a debug build and rebuilds the native project only when a native input actually changed, so relaunching after a Python edit takes seconds, not minutes.
4848
- **PyPI packages, binary wheels included:** List requirements in `pythonnative.toml` and the CLI resolves them for the *device* (iOS wheels via PEP 730 and BeeWare's index, Android wheels via PEP 738 and Chaquopy), bundling numpy, Pillow, cryptography, and friends alongside pure-Python packages. `pn deps` reports what each target resolves to before you build, and a weekly-checked compatibility matrix keeps the docs honest.
49-
- **Instant desktop preview:** `pn preview` renders your app in a native desktop window via Tkinter with Fast Refresh on every save: iterate on layout, state, and navigation in milliseconds without booting a simulator or device. The reconciler, hooks, layout engine, and navigation are the same code that ships to the phone.
49+
- **Browser preview:** `pn preview` renders your app in a browser tab inside a phone frame (pick a device, rotate, toggle dark mode) with Fast Refresh on every save. The page is a bridge peer exactly like the Swift and Kotlin runtimes, so the reconciler, hooks, layout engine, navigation, and screen host are the same code that ships to the phone; only the leaf widgets are DOM elements.
5050
- **Native-backed navigation:** Declarative `Stack`, `Tab`, and `Drawer` navigators inspired by React Navigation. The root stack drives the platform's native navigation controller (`UINavigationController` on iOS, AndroidX Navigation Component on Android), so transitions, back gestures, and the hardware back button match what users expect; `use_back_handler` intercepts the back action when a screen needs to.
51-
- **Fast Refresh hot reload:** `pn run --hot-reload` watches `app/` and patches edits into the running app on save, preserving component state across most changes.
51+
- **Dev client:** Build a shell app once with `pn run ios --dev-client` and point it at any project's dev server from a connect screen, the way Expo Go does; physical phones on the same Wi-Fi get the same Fast Refresh as simulators.
5252
- **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
5353

5454
## Quick Start
@@ -80,6 +80,16 @@ def App():
8080
)
8181
```
8282

83+
### Develop
84+
85+
```bash
86+
pn init my-app && cd my-app
87+
pn preview # dev server + browser preview with Fast Refresh
88+
pn run ios # in another terminal: debug build that connects to the same server
89+
pn run android
90+
pn build ios # standalone release artifacts
91+
```
92+
8393
## Documentation
8494

8595
Visit [pythonnative.com](https://pythonnative.com/) for the full documentation, including getting started guides, platform-specific instructions for Android and iOS, API reference, and working examples.

‎docs/api/cli.md‎

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,29 @@ the documented behavior never drifts from the code.
2323
`--json` for a machine-readable report, `--python` to pick the
2424
interpreter that runs pip. Exits non-zero when any target can't be
2525
satisfied. See [PyPI packages](../guides/pypi-packages.md).
26-
- `pn preview [component]`: render the app in a desktop (Tkinter) window
27-
with Fast Refresh, the fastest way to iterate on UI. Flags:
28-
`--width`, `--height`, `--title`, `--no-hot-reload`. See the
29-
[Desktop preview guide](../guides/desktop-preview.md).
26+
- `pn start [entry]`: run the dev server. It watches `app/`, syncs
27+
every save to each connected debug build with Fast Refresh, relays
28+
their logs, and serves the browser preview page. Flags: `--port`
29+
(default 8765), `--host` (default `0.0.0.0`), `--open` to also open
30+
the browser preview. See the
31+
[Development workflow](../guides/dev-workflow.md).
32+
- `pn preview [entry]`: `pn start` plus opening the browser preview in
33+
your default browser. Flags: `--port`, `--host`, `--no-open`. See the
34+
[Browser preview guide](../guides/browser-preview.md).
3035
- `pn devices [android|ios]`: list connected devices, emulators, and
3136
simulators with the identifiers `--device` accepts. Flag: `--json` to
3237
print a JSON array to stdout for scripting; the "no devices" hints go
3338
to stderr instead, and an empty list prints `[]` and exits 0.
34-
- `pn run android|ios`: build and run on a connected device or
35-
simulator. Flags: `--device` (target a specific device by identifier
36-
or name), `--prepare-only`, `--hot-reload`, `--no-logs`.
39+
- `pn run android|ios`: build, install, and launch a debug build that
40+
connects to the running dev server. The native toolchain runs only
41+
when a native input changed (config, template, `pythonnative`
42+
itself, native plugins); otherwise the previous artifact is
43+
reinstalled. Flags: `--device` (target a specific device by
44+
identifier or name), `--prepare-only`, `--no-logs`, `--rebuild`
45+
(force the toolchain), `--dev-server URL` (override the server URL
46+
baked into the app), `--port` (where `pn start` listens),
47+
`--dev-client` (build a shell app with a connect screen that loads
48+
any project from a dev server).
3749
- `pn logs android|ios`: stream logs from the running app without
3850
rebuilding. Flag: `--device` (target a specific device by identifier
3951
or name, same as `pn run`). Physical iOS devices aren't supported for
@@ -57,5 +69,5 @@ the documented behavior never drifts from the code.
5769
## Next steps
5870

5971
- See the [Getting started](../getting-started.md) walkthrough.
60-
- Read about [Hot reload](../guides/hot-reload.md) when you turn on
61-
`--hot-reload`.
72+
- Read the [Development workflow](../guides/dev-workflow.md) for how
73+
`pn start` and `pn run` work together.

‎docs/api/devserver.md‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Dev server and dev client
2+
3+
The development loop is a server process (`pn start`) and any number of
4+
clients: the browser preview and debug builds on simulators, emulators,
5+
and devices. See the [Development workflow](../guides/dev-workflow.md)
6+
guide for how they fit together; this page documents the modules.
7+
8+
## `pythonnative.devserver`
9+
10+
::: pythonnative.devserver
11+
options:
12+
show_root_heading: false
13+
show_root_toc_entry: false
14+
members_order: source
15+
filters: ["!^_"]
16+
17+
### Server
18+
19+
::: pythonnative.devserver.server
20+
options:
21+
show_root_heading: false
22+
show_root_toc_entry: false
23+
members_order: source
24+
filters: ["!^_"]
25+
26+
### File watcher and source snapshots
27+
28+
::: pythonnative.devserver.watcher
29+
options:
30+
show_root_heading: false
31+
show_root_toc_entry: false
32+
members_order: source
33+
filters: ["!^_"]
34+
35+
### WebSocket
36+
37+
::: pythonnative.devserver.ws
38+
options:
39+
show_root_heading: false
40+
show_root_toc_entry: false
41+
members_order: source
42+
filters: ["!^_"]
43+
44+
## `pythonnative.devclient`
45+
46+
::: pythonnative.devclient
47+
options:
48+
show_root_heading: false
49+
show_root_toc_entry: false
50+
members_order: source
51+
filters: ["!^_"]
52+
53+
## `pythonnative.preview`
54+
55+
::: pythonnative.preview
56+
options:
57+
show_root_heading: false
58+
show_root_toc_entry: false
59+
members_order: source
60+
filters: ["!^_"]
61+
62+
## `pythonnative.bridge.web`
63+
64+
::: pythonnative.bridge.web
65+
options:
66+
show_root_heading: false
67+
show_root_toc_entry: false
68+
members_order: source
69+
filters: ["!^_"]
70+
71+
## `pythonnative.project.fingerprint`
72+
73+
::: pythonnative.project.fingerprint
74+
options:
75+
show_root_heading: false
76+
show_root_toc_entry: false
77+
members_order: source
78+
filters: ["!^_"]
79+
80+
## Next steps
81+
82+
- [Fast Refresh](../guides/hot-reload.md) and the
83+
[hot reload API](hot_reload.md) for the device-side reload.
84+
- [Browser preview](../guides/browser-preview.md).

‎docs/api/diagnostics.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ props and styles, the [`HookOrderError`][pythonnative.HookOrderError]
66
raised on conditional hooks, and the RedBox overlay that surfaces
77
uncaught errors from renders, effects, and event handlers.
88

9-
Dev mode is enabled automatically by `pn preview` and by `pn run
10-
--hot-reload`, or explicitly with the `PN_DEV=1` environment variable.
9+
Dev mode is enabled automatically by `pn start` / `pn preview` and in
10+
the debug builds `pn run` produces, or explicitly with the `PN_DEV=1`
11+
environment variable.
1112
In production builds every check in this module is skipped, so
1213
shipping apps pay no overhead.
1314

‎docs/api/hosts.md‎

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ templates create a host through the `Host` native module's `create`
1212
event and never need to be edited by app code; one
1313
[`NativeScreenHost`][pythonnative.hosts.native.NativeScreenHost] class
1414
serves both platforms because every platform-specific step goes over
15-
the [bridge](../concepts/bridge.md). The desktop preview uses
16-
[`DesktopScreenHost`][pythonnative.hosts.desktop.DesktopScreenHost].
15+
the [bridge](../concepts/bridge.md). The browser preview uses the same
16+
class: its page is a bridge peer that speaks the `Host` module protocol
17+
over a WebSocket (see
18+
[`WebTransport`][pythonnative.bridge.web.WebTransport]).
1719

1820
::: pythonnative.hosts
1921
options:
@@ -40,13 +42,6 @@ the [bridge](../concepts/bridge.md). The desktop preview uses
4042
members_order: source
4143
filters: ["!^_"]
4244

43-
::: pythonnative.hosts.desktop
44-
options:
45-
show_root_heading: false
46-
show_root_toc_entry: false
47-
members: ["DesktopScreenHost"]
48-
filters: ["!^_"]
49-
5045
## Next steps
5146

5247
- Understand the render queue in [Lifecycle](../concepts/lifecycle.md).

‎docs/api/hot_reload.md‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Hot reload
22

3-
Hot-reload comes in two cooperating pieces: a host-side file watcher
4-
that pushes changed `.py` files to the device, and a device-side
5-
module reloader that swaps the new code in and re-renders the active
6-
page. Both are wired up automatically by `pn run --hot-reload`.
3+
Fast Refresh comes in two cooperating pieces: the
4+
[dev server](devserver.md) that watches `app/` and pushes changed files
5+
to every connected client, and this device-side module reloader that
6+
swaps the new code in and refreshes every mounted screen. Debug builds
7+
launched with `pn run` while `pn start` is running are wired up
8+
automatically.
79

810
::: pythonnative.hot_reload
911
options:
@@ -14,4 +16,5 @@ page. Both are wired up automatically by `pn run --hot-reload`.
1416

1517
## Next steps
1618

17-
- See the workflow in [Hot reload guide](../guides/hot-reload.md).
19+
- See the workflow in the [Fast Refresh guide](../guides/hot-reload.md).
20+
- The other half: [Dev server and dev client](devserver.md).

‎docs/api/native_modules.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ and the [Async + data guide](../guides/async.md).
2626

2727
## Desktop implementations
2828

29-
::: pythonnative.native_modules.desktop
29+
::: pythonnative.native_modules.fallback
3030
options:
3131
show_root_heading: false
3232
show_root_toc_entry: false

‎docs/api/native_views.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ call on a [`NativeViewRegistry`][pythonnative.native_views.NativeViewRegistry].
99
On device that registry is the
1010
[`BridgeBackend`][pythonnative.native_views.bridge_backend.BridgeBackend],
1111
which forwards the transaction to Swift and Kotlin component managers
12-
over the [native bridge](bridge.md). Off device it dispatches to Python
13-
[`ViewHandler`][pythonnative.native_views.base.ViewHandler] objects
14-
(Tkinter for `pn preview`, a fake for tests).
12+
over the [native bridge](bridge.md). The browser preview uses the same
13+
backend with a WebSocket transport to the page. In tests it dispatches
14+
to Python [`ViewHandler`][pythonnative.native_views.base.ViewHandler]
15+
objects (an in-memory fake).
1516

1617
::: pythonnative.native_views
1718
options:

‎docs/api/platform.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ branches throughout the codebase.
1616

1717
| Attribute | Values |
1818
|---|---|
19-
| `Platform.OS` | `"ios"`, `"android"`, `"desktop"`, or `"test"` |
19+
| `Platform.OS` | `"ios"`, `"android"`, `"web"`, or `"test"` |
2020
| `Platform.Version` | Best-effort OS version string |
21-
| `Platform.is_ios` / `Platform.is_android` / `Platform.is_desktop` / `Platform.is_test` | Booleans |
21+
| `Platform.is_ios` / `Platform.is_android` / `Platform.is_web` / `Platform.is_test` | Booleans |
2222
| `Platform.select(spec, default=None)` | Pick a value matching the current platform |
2323

24-
- `"desktop"` means the `pn preview` Tkinter backend.
24+
- `"web"` means the `pn preview` browser preview.
2525

2626
## See also
2727

0 commit comments

Comments
 (0)

Footer

© 2026 GitHub, Inc.

Back | FazBrowse Home | New Git URL