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

feat!: overhaul native contracts, rendering, and extension builds (#101) · pythonnative/pythonnative@9cb4ebb · GitHub

feat!: overhaul native contracts, rendering, and extension builds (#101) · pythonnative/pythonnative@9cb4ebb · GitHub
Skip to content

Navigation Menu

Commit 9cb4ebb

Browse files
authored
feat!: overhaul native contracts, rendering, and extension builds (#101)
1 parent 481a592 commit 9cb4ebb

188 files changed

Lines changed: 16613 additions & 10695 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.

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ jobs:
5757
# The Swift half of the rendering core: decoders, component managers,
5858
# gestures, and module promises run under XCTest on a simulator.
5959
native-ios:
60-
runs-on: macos-14
60+
runs-on: macos-15
61+
env:
62+
DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer
6163
steps:
6264
- name: Checkout
6365
uses: actions/checkout@v4

‎.github/workflows/e2e.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ jobs:
134134
# alerts present twice, pickers reopen after selecting. This suite
135135
# ran green on the macos-15 image for months.
136136
runs-on: macos-15
137+
env:
138+
DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer
137139
# Budget for the worst case: ~8 min build/boot plus two full suite
138140
# attempts (run-e2e.sh retries the whole suite once after a driver
139141
# flake). The test step gets its own, shorter timeout so a hung

‎docs/concepts/bridge.md‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ A surface commit has this shape:
2525

2626
```json
2727
{
28-
"version": 2,
28+
"version": 3,
2929
"application": "unique-application-id",
3030
"surface": 1,
3131
"revision": 1,
@@ -40,7 +40,7 @@ A surface commit has this shape:
4040
| Operation | Fields |
4141
| --- | --- |
4242
| `c` | tag, component type, props |
43-
| `u` | tag, changed props; `null` removes a prop |
43+
| `u` | tag, changed props, removed property names; `null` remains a value |
4444
| `i` | parent tag, child tag, insertion index |
4545
| `d` | tag; children must already be destroyed |
4646
| `f` | tag, x, y, width, height |
@@ -74,10 +74,15 @@ provenance. Python tests build a host binding, iOS and Android compile the same
7474
core, and the browser preview uses the corresponding Yoga WebAssembly package.
7575

7676
The application sends styles as props. Native leaf managers measure text,
77-
controls, and images next to their widgets. One `Layout.compute` request returns
78-
a batch of changed frames with `application`, `surface`, and `revision`.
77+
controls, and images next to their widgets. When the viewport is known, a commit
78+
includes a `layout` object with `roots`, `width`, and `height`. Its acknowledgment
79+
includes the resulting frames, so mutation and required layout share one bridge
80+
crossing. `Layout.compute` remains available for viewport changes without
81+
mutations. Both return frames with `application`, `surface`, and `revision`.
7982
Geometry from another surface or revision is ignored. Drawing-only prop updates
80-
skip the request entirely. Python doesn't make one measurement RPC per leaf.
83+
skip a separate request. Yoga calculations reuse unchanged constraints, and
84+
frame collection visits only subtrees with new layout. Python doesn't make one
85+
measurement RPC per leaf.
8186

8287
Commit and required layout acknowledgments precede ref and effect publication.
8388
Native navigation animations and later platform layout changes may continue after
@@ -91,7 +96,12 @@ UIKit collection views and Android recycler views own physical cells; a bounded
9196
window of ordinary keyed Python row components supplies their contents. Headless
9297
tests simulate the same viewport and row requests. Dataset revisions change when
9398
data or rendering inputs change; per-item revisions avoid reloading unchanged
94-
cells. Global and section headers don't change public item indices. Row instances
99+
cells. Dataset indexing is memoized separately from viewport changes. Stable
100+
sequence identity, callbacks, and `data_revision` reuse that index even when a
101+
parent rerenders. Replace an edited sequence or increment `data_revision` after
102+
in-place edits. Native prefetch requests warm the bounded row window. Native
103+
containers preserve the first visible key and its offset across dataset changes;
104+
iOS also preserves it as measured row heights replace estimates. Global and section headers don't change public item indices. Row instances
95105
can unmount outside the window, so persistent item state belongs in app data.
96106

97107
## Modules, contracts, and animations

‎docs/concepts/native-views.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ the render loop.
3030
| Op | Meaning |
3131
|---|---|
3232
| `CreateOp(tag, type_name, props)` | Create a native view for `tag`. Props are already *clean*: callables have been routed to the event registry. |
33-
| `UpdateOp(tag, changed_props)` | Apply only the props that changed (removed props arrive as `None`). |
33+
| `UpdateOp(tag, changed_props)` | Apply only the props that changed (`UNSET` removes a prop; `None` is explicit null). |
3434
| `InsertOp(parent_tag, child_tag, index)` | Place the child at `index` (move-aware: an attached child is repositioned, not duplicated). |
3535
| `DestroyOp(tag)` | Release the native view (detaching it from its parent) and drop the tag record. |
3636
| `SetFrameOp(tag, x, y, w, h)` | Apply a frame from headless layout or an explicit frame operation. Bridge renderers compute ordinary layout beside their widgets. |

‎docs/concepts/reconciliation.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ tree:
2626

2727
- If their `type` matches, **update**: emit an
2828
[`UpdateOp`][pythonnative.mutations.UpdateOp] carrying only the
29-
props that changed (removed props arrive as `None`).
29+
props that changed (removed props arrive as `UNSET`; `None` is explicit null).
3030
- If their `type` differs, **replace**: emit destroy ops for the old
3131
subtree, create ops for the new one, and recurse into its children.
3232
- For container elements, match children by `key` first and by

‎docs/guides/building-for-release.md‎

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,30 @@ pn doctor ios
3030
```
3131

3232
`pn doctor` validates `pythonnative.toml` and checks the platform
33-
toolchain: for Android, `adb`, a JDK, and whether release signing is
34-
configured; for iOS, macOS, Xcode, `simctl`, and a development team. It
33+
toolchain: Android needs JDK 17 through 23, the configured SDK (36 by
34+
default), NDK 28.2.13676358, and `adb`. iOS needs macOS, Xcode 26 or
35+
later, and `simctl`. The doctor also reports signing, lockfile, and
36+
privacy-manifest configuration. It
3537
exits non-zero on anything that will block a build, so you can gate CI
3638
on it.
3739

3840
---
3941

42+
## Frozen dependencies
43+
44+
Release builds with Python dependencies require a current `pn.lock` covering
45+
all build targets. Resolve and review the lock before building:
46+
47+
```bash
48+
pn deps android --lock
49+
pn deps ios --lock
50+
```
51+
52+
Commit the lock with your application. Changing requirements or replacing a
53+
local wheel requires refreshing it. Releases reject missing or stale locks;
54+
development builds can still resolve unlocked requirements. Native extension
55+
manifests pin SwiftPM and Maven dependencies to exact versions.
56+
4057
## Android
4158

4259
`pn build android` runs `assembleRelease` and `bundleRelease`, producing
@@ -48,6 +65,21 @@ build/android/android_template/app/build/outputs/apk/release/app-release.apk
4865
build/android/android_template/app/build/outputs/bundle/release/app-release.aab
4966
```
5067

68+
### Toolchain and packaged binaries
69+
70+
The template uses compile/target SDK 36, Gradle 8.13, Android Gradle Plugin
71+
8.11.1, Kotlin 2.2.21, and NDK 28.2.13676358. Release builds reject a target
72+
SDK below 36. These are PythonNative's supported build defaults, independent
73+
of store submission deadlines.
74+
75+
After Gradle builds, PythonNative inspects the APK and AAB. Every packaged
76+
64-bit ELF library must have 16 KB aligned LOAD segments, including Python
77+
extensions inside Chaquopy assets. Uncompressed APK libraries must also be
78+
ZIP aligned, and the AAB must request compatible generated APK alignment.
79+
An error names the offending dependency. Rebuild or replace that dependency;
80+
upgrading your app's NDK can't repair a prebuilt wheel. See Android's
81+
[16 KB page-size guidance](https://developer.android.com/guide/practices/page-sizes).
82+
5183
### Signing
5284

5385
Without signing configured, Gradle emits an **unsigned** release APK
@@ -110,6 +142,24 @@ The upload uses the App Store Connect credentials Xcode has stored; in
110142
CI, provide an ASC API key through Xcode's standard mechanisms or
111143
upload the exported `.ipa` with `xcrun altool`/Transporter instead.
112144

145+
### Privacy manifests
146+
147+
The template includes an app `PrivacyInfo.xcprivacy`. Supply your app's own
148+
manifest with a path relative to the project:
149+
150+
```toml
151+
[ios]
152+
privacy_manifest = "assets/PrivacyInfo.xcprivacy"
153+
```
154+
155+
PythonNativeKit ships its own resource-bundle manifest for preferences and
156+
cache-file timestamps. Archive validation checks that the app and runtime
157+
manifests are present and structurally valid before export or upload. It also
158+
parses manifests supplied by bundled dependencies. This check doesn't infer
159+
your application's data collection, tracking, or required-reason API usage.
160+
Declare those from your actual behavior using Apple's
161+
[privacy-manifest documentation](https://developer.apple.com/documentation/bundleresources/privacy-manifest-files).
162+
113163
### Bytecode-only bundles
114164

115165
Release builds byte-compile your `app/` sources and every bundled

‎docs/guides/configuration.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ packages = ["humanize", "httpx", "numpy"]
3838
[ios]
3939
deployment_target = "13.0"
4040
development_team = "ABCDE12345"
41+
privacy_manifest = "PrivacyInfo.xcprivacy" # optional app declarations
4142
# bundle_id = "com.example.myapp"
4243

4344
[ios.signing]
@@ -46,7 +47,7 @@ export_method = "development" # development | ad-hoc | app-store | enterprise
4647

4748
[android]
4849
min_sdk = 24
49-
target_sdk = 34
50+
target_sdk = 36
5051
# abi_filters = ["arm64-v8a", "x86_64"] # the default; add 32-bit ABIs if needed
5152

5253
[android.signing]
@@ -200,6 +201,7 @@ generated registration file calls every plugin's `register`. See
200201
[ios]
201202
deployment_target = "13.0"
202203
development_team = "ABCDE12345"
204+
privacy_manifest = "PrivacyInfo.xcprivacy" # optional app declarations
203205

204206
[ios.signing]
205207
export_method = "app-store"

‎docs/guides/lists.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ def Big():
3535
The list never holds 10,000 native views; only the window around the
3636
viewport ever exists.
3737

38+
## Data snapshots
39+
40+
Lists accept a `Sequence` and cache keys and row metadata separately from the
41+
scrolling window. Keep `render_item`, `key_extractor`, and height callbacks
42+
stable when the parent rerenders, using module-level functions or
43+
`use_callback`. Scrolling then does work proportional to the mounted window.
44+
45+
Prefer replacing data with a new sequence. If you mutate a sequence in place,
46+
increment `data_revision` when passing it to `FlatList` or `SectionList`.
47+
The revision invalidates the cached snapshot. Native prefetch requests mount
48+
nearby rows, and keyed anchoring preserves the visible row as measurements or
49+
data updates change extents above it.
50+
3851
## Row heights
3952

4053
Three ways to tell the list how tall rows are, in order of preference:

0 commit comments

Comments
 (0)

Footer

© 2026 GitHub, Inc.

Back | FazBrowse Home | New Git URL