@webview-native-ui/engine 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +17 -14
  2. package/README.md +27 -599
  3. package/WebViewNativeUIEngine.podspec +2 -2
  4. package/dist/{scroll-bindings.d.ts → dom/scroll-bindings.d.ts} +1 -1
  5. package/dist/{scroll-placement.d.ts → dom/scroll-placement.d.ts} +1 -1
  6. package/dist/index.d.ts +9 -9
  7. package/dist/index.js +8 -8
  8. package/dist/ionic.d.ts +3 -3
  9. package/dist/ionic.js +1 -1
  10. package/dist/{native-bootstrap.d.ts → native/bootstrap.d.ts} +1 -1
  11. package/dist/{native-bootstrap.js → native/bootstrap.js} +1 -1
  12. package/dist/{bridge.d.ts → native/bridge.d.ts} +1 -1
  13. package/dist/{native-session.d.ts → native/session.d.ts} +3 -2
  14. package/dist/{native-session.js → native/session.js} +8 -7
  15. package/dist/{projection-events.d.ts → projection/events.d.ts} +1 -1
  16. package/dist/{projection-events.js → projection/events.js} +1 -1
  17. package/dist/{web-projection.d.ts → projection/web.d.ts} +5 -3
  18. package/dist/{web-projection.js → projection/web.js} +9 -6
  19. package/dist/types.d.ts +1 -1
  20. package/docs/assets/hero.png +0 -0
  21. package/docs/capacitor-plugin.md +279 -0
  22. package/docs/cordova-plugin.md +198 -0
  23. package/docs/cordova.md +1 -123
  24. package/docs/maintenance.md +52 -0
  25. package/docs/projection.md +340 -0
  26. package/ios/Sources/WebViewNativeUIEngine/CoveredSnapshots.swift +6 -14
  27. package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +6 -3
  28. package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +3 -3
  29. package/package.json +2 -3
  30. package/NOTICE +0 -39
  31. /package/dist/{geometry.d.ts → dom/geometry.d.ts} +0 -0
  32. /package/dist/{geometry.js → dom/geometry.js} +0 -0
  33. /package/dist/{overlays.d.ts → dom/overlays.d.ts} +0 -0
  34. /package/dist/{overlays.js → dom/overlays.js} +0 -0
  35. /package/dist/{scroll-bindings.js → dom/scroll-bindings.js} +0 -0
  36. /package/dist/{scroll-placement.js → dom/scroll-placement.js} +0 -0
  37. /package/dist/{visibility.d.ts → dom/visibility.d.ts} +0 -0
  38. /package/dist/{visibility.js → dom/visibility.js} +0 -0
  39. /package/dist/{bridge.js → native/bridge.js} +0 -0
  40. /package/dist/{runtime.d.ts → native/resource-url.d.ts} +0 -0
  41. /package/dist/{runtime.js → native/resource-url.js} +0 -0
  42. /package/dist/{projection-placement.d.ts → projection/placement.d.ts} +0 -0
  43. /package/dist/{projection-placement.js → projection/placement.js} +0 -0
  44. /package/dist/{web-projection-scope.d.ts → projection/scope.d.ts} +0 -0
  45. /package/dist/{web-projection-scope.js → projection/scope.js} +0 -0
  46. /package/dist/{snapshot-styles.d.ts → projection/styles.d.ts} +0 -0
  47. /package/dist/{snapshot-styles.js → projection/styles.js} +0 -0
@@ -0,0 +1,198 @@
1
+ # Cordova plugin integration
2
+
3
+ The same npm package, Swift library and Android AAR support Capacitor and Cordova. Engine is a library used by plugin maintainers, not a separately registered Cordova plugin. A Cordova application does not need `@capacitor/core` or a Capacitor native SDK.
4
+
5
+ The consuming plugin still owns its native view and the corresponding DOM, including placement, size and stacking order. Supply an existing selector such as `native-ad` or `#player`. The DOM may be added after initialization. Image preparation, scroll handling, overlays and restoration use the common Engine implementation.
6
+
7
+ ## Requirements
8
+
9
+ The integration is verified with cordova-ios 8.1.1 and cordova-android 15.1.0 using their standard WebViews. The Engine itself requires iOS 15+ and Android API 23+; the Cordova host or SDK may require a higher minimum. Composited Android capture requires API 26+.
10
+
11
+ ## Dependencies
12
+
13
+ Use the same Engine release for the Web package and native libraries. The API is pre-1.0; pin versions and retain dependency lockfiles.
14
+
15
+ ```sh
16
+ npm install --save-exact @webview-native-ui/engine@0.0.1
17
+ ```
18
+
19
+ Declare the native dependencies in the consuming plugin as well. npm installation alone does not link either native library.
20
+
21
+ For Swift Package Manager:
22
+
23
+ ```swift
24
+ .package(url: "https://github.com/webview-native-ui/engine.git", exact: "0.0.1")
25
+ // In the plugin target's dependencies:
26
+ .product(name: "WebViewNativeUIEngine", package: "engine")
27
+ ```
28
+
29
+ For the plugin's Android Gradle configuration:
30
+
31
+ ```groovy
32
+ dependencies {
33
+ implementation 'io.github.webview-native-ui:engine:0.0.1'
34
+ }
35
+ ```
36
+
37
+ Keep `google()` and `mavenCentral()` in the consuming build's dependency repositories.
38
+
39
+ ## iOS
40
+
41
+ In a Cordova SwiftPM plugin, declare both Cordova and Engine as dependencies of the plugin target; neither framework becomes a dependency of the Engine target. Keep the plugin's existing SwiftPM product and source-file declarations.
42
+
43
+ In the iOS platform section of `plugin.xml`, register the plugin for startup if its renderer should appear before the first application call:
44
+
45
+ ```xml
46
+ <platform name="ios" package="swift">
47
+ <config-file target="config.xml" parent="/*">
48
+ <feature name="ExamplePlugin">
49
+ <param name="ios-package" value="ExamplePlugin" />
50
+ <param name="onload" value="true" />
51
+ </feature>
52
+ </config-file>
53
+ <!-- Keep the plugin's existing source-file declarations. -->
54
+ </platform>
55
+ ```
56
+
57
+ Merge these entries into the existing platform sections rather than adding duplicate sections. The [integration fixture](https://github.com/webview-native-ui/engine/tree/main/tests/cordova/plugin) includes complete plugin.xml, SwiftPM and Gradle wiring.
58
+
59
+ The examples extend an existing renderer: `nativeView` is its attached, positioned view, not a view created by the Engine.
60
+
61
+ ```swift
62
+ import Cordova
63
+ import WebKit
64
+ import WebViewNativeUIEngine
65
+
66
+ @objc(ExamplePlugin)
67
+ class ExamplePlugin: CDVPlugin {
68
+ private var projection: NativeUIProjection?
69
+ private var nativeView: UIView?
70
+
71
+ override func pluginInitialize() {
72
+ // Existing renderer creates, attaches and positions nativeView here.
73
+ do {
74
+ projection = try NativeUIProjection(
75
+ webView: webView as? WKWebView,
76
+ plugin: "ExamplePlugin",
77
+ projectionSelector: "#player",
78
+ receiveListeners: .init(
79
+ update: ["contentChanged"],
80
+ projection: .init(native: ["shown"], web: ["covered"], none: ["hidden"])
81
+ ),
82
+ runtime: .cordova,
83
+ view: { [weak self] in self?.nativeView }
84
+ )
85
+ } catch {
86
+ // Keep the existing Web fallback when registration is unavailable.
87
+ NSLog("Native UI projection unavailable: %@", String(describing: error))
88
+ }
89
+ }
90
+
91
+ override func dispose() {
92
+ projection?.destroy()
93
+ projection = nil
94
+ super.dispose()
95
+ }
96
+ }
97
+ ```
98
+
99
+ If the iOS renderer loads later, create the registration after its view is attached, and destroy the old registration before replacing the renderer.
100
+
101
+ Forward the plugin's existing presentation/content notifications with `projection?.receive("shown")`, `receive("hidden")` or `receive("contentChanged")`. Put this alongside the existing callback/event delivery, or in the plugin's shared notification method. `receive` updates the Engine session only: it does not deliver a second application callback. A matching destination emitted before JS startup is retained for the initial connection.
102
+
103
+ Cordova iOS's built-in `WkWebView.convertFilePath` serves the prepared PNGs. Engine resolves this internally. No file plugin, additional permission or author-provided URL converter is required. The default allowed origins include `app://localhost`; pass the actual trusted origin if the app changes its scheme or hostname.
104
+
105
+ ## Android
106
+
107
+ Declare the Maven dependency in the existing Cordova plugin Gradle configuration. Also declare `implementation 'androidx.webkit:webkit:1.14.0'` when using the `CordovaPluginPathHandler` signature; Cordova's internal WebKit dependency is not exposed on the plugin compile classpath. In the Android platform section of the plugin's `plugin.xml`, merge this registration so initialization runs before the first WebView navigation:
108
+
109
+ ```xml
110
+ <platform name="android">
111
+ <config-file target="res/xml/config.xml" parent="/*">
112
+ <feature name="ExamplePlugin">
113
+ <param name="android-package" value="com.example.ExamplePlugin" />
114
+ <param name="onload" value="true" />
115
+ </feature>
116
+ </config-file>
117
+ <!-- Keep existing source-file and Gradle framework declarations. -->
118
+ </platform>
119
+ ```
120
+
121
+ ```kotlin
122
+ import android.view.View
123
+ import android.webkit.WebView
124
+ import io.github.webview_native_ui.engine.NativeUIProjection
125
+ import org.apache.cordova.CordovaPlugin
126
+ import org.apache.cordova.CordovaPluginPathHandler
127
+
128
+ class ExamplePlugin : CordovaPlugin() {
129
+ private var projection: NativeUIProjection? = null
130
+ private var nativeView: View? = null
131
+
132
+ override fun pluginInitialize() {
133
+ val web = webView.view as WebView
134
+ projection = runCatching {
135
+ // One origin-restricted channel per WebView, shared with later registrations.
136
+ NativeUIProjection.install(web)
137
+ // Existing renderer creates, attaches and positions nativeView here.
138
+ NativeUIProjection(
139
+ window = cordova.activity.window,
140
+ webView = web,
141
+ plugin = serviceName,
142
+ projectionSelector = "#player",
143
+ receiveListeners = NativeUIProjection.ReceiveListeners(
144
+ update = listOf("contentChanged"),
145
+ projection = NativeUIProjection.ReceiveListeners.Projection(
146
+ native = listOf("shown"), web = listOf("covered"), none = listOf("hidden")
147
+ )
148
+ ),
149
+ runtime = NativeUIProjection.Runtime.CORDOVA,
150
+ view = { nativeView }
151
+ )
152
+ }.onFailure {
153
+ android.util.Log.e(javaClass.simpleName, "Native UI projection unavailable", it)
154
+ }.getOrNull()
155
+ }
156
+
157
+ override fun getPathHandler() = CordovaPluginPathHandler { path ->
158
+ projection?.handleResource(path)
159
+ }
160
+
161
+ override fun onDestroy() {
162
+ projection?.close()
163
+ projection = null
164
+ super.onDestroy()
165
+ }
166
+ }
167
+ ```
168
+
169
+ Use `projection?.receive(eventName, data)` at the existing notification point, as on iOS. Native view creation and placement remain on the UI thread. If the renderer loads asynchronously, keep `install(web)` in `pluginInitialize()` and create the registration after the view is ready. Close the old registration before replacing that renderer.
170
+
171
+ The `PathHandler` delegates same-origin image requests to Engine. It serves only the registration's PNG files in app-private cache storage; unrelated paths return `null`. If the plugin already has a handler, combine them with `projection?.handleResource(path) ?: existingHandler.handle(path)`. For multiple registrations, delegate to the matching owner. Keep the handler installed for the WebView lifetime. No WebViewClient replacement or storage permission is needed.
172
+
173
+ The defaults support Cordova's `https://localhost` and `http://localhost`. If the hostname changes, pass the same explicit origin set to `install` and each registration. Wildcard origins and `file://` app origins are not supported by this integration.
174
+
175
+ ## Lifetime and verification
176
+
177
+ Keep the registration for the WebView lifetime. Engine reconnects after a document reload and clears the old document's image state. If the plugin destroys its native UI in `onReset`, update its view provider and emit the appropriate destination; do not leave a registration pointing at a destroyed view. Dispose registrations when the plugin/WebView is destroyed.
178
+
179
+ Capacitor registrations retain their existing default. The Cordova integration requires the standard Cordova WebView resource handler. Running a Cordova plugin inside a Capacitor app is not covered by this integration; the host must use the Capacitor integration instead. See the shared [DOM and native surface requirements](projection.md#dom-and-native-surface-requirements) for selector, layout and capture rules. [Ionic overlay support](projection.md#ionic-overlay-support-optional) remains optional; plain Cordova apps need not enable it.
180
+
181
+ The integration fixture uses Cordova CLI 13, cordova-ios 8.1.1 and cordova-android 15.1.0 with their standard WebViews. It checks actual PNG handoff, native restoration and hidden-state retention across document reload, not merely JS bridge mocks. Older Cordova releases and custom WebView engines require separate verification.
182
+
183
+ Run the real consumer checks with an already booted simulator/emulator:
184
+
185
+ ```sh
186
+ npm run test:cordova -- ios SIMULATOR_UDID
187
+ npm run test:cordova -- android emulator-5554
188
+ ```
189
+
190
+ The script creates a disposable app, uses local SwiftPM/Maven Engine artifacts, and records the application result in the printed temporary directory. It never publishes to npm or Maven. Android requires JDK 21+, SDK 36 and `ANDROID_HOME`; iOS requires Xcode.
191
+
192
+ ## Animated native controls
193
+
194
+ Native appearance updates prepare replacement images after a 50 ms debounce by default.
195
+ If your control continues animating after its update notification, set `captureDelayMs`
196
+ on the native registration (Swift: `captureDelayMs: 600`; Kotlin: `captureDelayMs = 600`).
197
+ This changes background image preparation, not overlay presentation timing.
198
+ See [capture timing](projection.md#capture-timing) for the cache behavior and measured guidance.
package/docs/cordova.md CHANGED
@@ -1,125 +1,3 @@
1
1
  # Cordova plugin integration
2
2
 
3
- The same npm package, Swift library and Android AAR support Capacitor and Cordova. Engine is a library used by plugin maintainers, not a separately registered Cordova plugin. A Cordova application does not need `@capacitor/core` or a Capacitor native SDK.
4
-
5
- The consuming plugin still owns its native view and the corresponding DOM, including placement, size and stacking order. Supply an existing selector such as `native-ad` or `#player`. The DOM may be added after initialization. Image preparation, scroll handling, overlays and restoration use the common Engine implementation.
6
-
7
- ## iOS
8
-
9
- Use the same SwiftPM dependency described in the main README. In a Cordova SwiftPM plugin, declare both Cordova and Engine as dependencies of the plugin target; neither framework becomes a dependency of the Engine target.
10
-
11
- ```swift
12
- import Cordova
13
- import WebKit
14
- import WebViewNativeUIEngine
15
-
16
- @objc(ExamplePlugin)
17
- class ExamplePlugin: CDVPlugin {
18
- private var projection: NativeUIProjection?
19
- private var nativeView: UIView?
20
-
21
- override func pluginInitialize() {
22
- do {
23
- projection = try NativeUIProjection(
24
- webView: webView as? WKWebView,
25
- plugin: "ExamplePlugin",
26
- projectionSelector: "#player",
27
- receiveListeners: .init(
28
- update: ["contentChanged"],
29
- projection: .init(native: ["shown"], web: ["covered"], none: ["hidden"])
30
- ),
31
- runtime: .cordova,
32
- view: { [weak self] in self?.nativeView }
33
- )
34
- } catch {
35
- // Keep the existing Web fallback when registration is unavailable.
36
- NSLog("Native UI projection unavailable: %@", String(describing: error))
37
- }
38
- }
39
-
40
- override func dispose() {
41
- projection?.destroy()
42
- projection = nil
43
- super.dispose()
44
- }
45
- }
46
- ```
47
-
48
- Forward the plugin's existing presentation/content notifications with `projection?.receive("shown")`, `receive("hidden")` or `receive("contentChanged")`. Put this alongside the existing callback/event delivery, or in the plugin's shared notification method. `receive` updates the Engine session only: it does not deliver a second application callback. A matching destination emitted before JS startup is retained for the initial connection.
49
-
50
- Cordova iOS's built-in `WkWebView.convertFilePath` serves the prepared PNGs. Engine resolves this internally. No file plugin, additional permission or author-provided URL converter is required. The default allowed origins include `app://localhost`; pass the actual trusted origin if the app changes its scheme or hostname.
51
-
52
- ## Android
53
-
54
- Declare the Maven dependency in the existing Cordova plugin Gradle configuration. Also declare `implementation 'androidx.webkit:webkit:1.14.0'` when using the `CordovaPluginPathHandler` signature; Cordova's internal WebKit dependency is not exposed on the plugin compile classpath. The plugin's `plugin.xml` must initialize it before the first WebView navigation:
55
-
56
- ```xml
57
- <feature name="ExamplePlugin">
58
- <param name="android-package" value="com.example.ExamplePlugin" />
59
- <param name="onload" value="true" />
60
- </feature>
61
- ```
62
-
63
- ```kotlin
64
- class ExamplePlugin : CordovaPlugin() {
65
- private var projection: NativeUIProjection? = null
66
- private var nativeView: View? = null
67
-
68
- override fun pluginInitialize() {
69
- val web = webView.view as WebView
70
- projection = runCatching {
71
- // One origin-restricted channel per WebView, shared with later registrations.
72
- NativeUIProjection.install(web)
73
- NativeUIProjection(
74
- window = cordova.activity.window,
75
- webView = web,
76
- plugin = serviceName,
77
- projectionSelector = "#player",
78
- receiveListeners = NativeUIProjection.ReceiveListeners(
79
- update = listOf("contentChanged"),
80
- projection = NativeUIProjection.ReceiveListeners.Projection(
81
- native = listOf("shown"), web = listOf("covered"), none = listOf("hidden")
82
- )
83
- ),
84
- runtime = NativeUIProjection.Runtime.CORDOVA,
85
- view = { nativeView }
86
- )
87
- }.onFailure {
88
- android.util.Log.e(javaClass.simpleName, "Native UI projection unavailable", it)
89
- }.getOrNull()
90
- }
91
-
92
- override fun getPathHandler() = CordovaPluginPathHandler { path ->
93
- projection?.handleResource(path)
94
- }
95
-
96
- override fun onDestroy() {
97
- projection?.close()
98
- projection = null
99
- super.onDestroy()
100
- }
101
- }
102
- ```
103
-
104
- Use `projection?.receive(eventName, data)` at the existing notification point, as on iOS. Native view creation and placement remain on the UI thread.
105
-
106
- The `PathHandler` delegates same-origin image requests to Engine. It serves only the registration's PNG files in app-private cache storage; unrelated paths return `null`. If the plugin already has a handler, combine them with `projection?.handleResource(path) ?: existingHandler.handle(path)`. For multiple registrations, delegate to the matching owner. Keep the handler installed for the WebView lifetime. No WebViewClient replacement or storage permission is needed.
107
-
108
- The defaults support Cordova's `https://localhost` and `http://localhost`. If the hostname changes, pass the same explicit origin set to `install` and each registration. Wildcard origins and `file://` app origins are not supported by this integration.
109
-
110
- ## Lifetime and verification
111
-
112
- Keep the registration for the WebView lifetime. Engine reconnects after a document reload and clears the old document's image state. If the plugin destroys its native UI in `onReset`, update its view provider and emit the appropriate destination; do not leave a registration pointing at a destroyed view. Dispose registrations when the plugin/WebView is destroyed.
113
-
114
- Capacitor registrations retain their existing default. The Cordova integration requires the standard Cordova WebView resource handler. Running a Cordova plugin inside a Capacitor app is not covered by this integration; the host must use the Capacitor integration instead. Ionic overlay observation remains optional and shared through `enableSupportIonicOverlays`; plain Cordova apps need not enable it.
115
-
116
- The integration fixture uses Cordova CLI 13, cordova-ios 8.1.1 and cordova-android 15.1.0 with their standard WebViews. It checks actual PNG handoff, native restoration and hidden-state retention across document reload, not merely JS bridge mocks. Older Cordova releases and custom WebView engines require separate verification.
117
-
118
- Run the real consumer checks with an already booted simulator/emulator:
119
-
120
- ```sh
121
- npm run test:cordova -- ios SIMULATOR_UDID
122
- npm run test:cordova -- android emulator-5554
123
- ```
124
-
125
- The script creates a disposable app, uses local SwiftPM/Maven Engine artifacts, and records the application result in the printed temporary directory. It never publishes to npm or Maven. Android requires JDK 21+, SDK 36 and `ANDROID_HOME`; iOS requires Xcode.
3
+ This guide has moved to [Cordova plugin integration](cordova-plugin.md).
@@ -0,0 +1,52 @@
1
+ # Development and releases
2
+
3
+ ## Development
4
+
5
+ The public entry points are `src/index.ts` and `src/ionic.ts`, with shared contracts in `src/types.ts`. Implementation is grouped by responsibility:
6
+
7
+ - `src/native/`: bridge transport, native sessions, startup and resource URLs.
8
+ - `src/projection/`: image handoff, projection ownership, events and styles.
9
+ - `src/dom/`: geometry, scrolling, visibility and overlay coordination.
10
+
11
+ `npm run build` bundles `src/native/bootstrap.ts` into `runtime/native-ui-engine.js`, the shared WebView runtime. Android packages this file directly as an asset. The build also copies it into `ios/Sources/WebViewNativeUIEngine/Resources` because SwiftPM requires resources inside the target directory. Both generated files are committed so native consumers do not need Node.js. Edit the TypeScript source, not either generated file; CI checks regeneration and verifies the runtime included in npm and Maven archives.
12
+
13
+ Browser tests mirror these groups under `tests/`. `tests/cordova/` contains the real native consumer fixture.
14
+
15
+ Use Node.js 24, matching CI. From this repository:
16
+
17
+ ```sh
18
+ npm ci
19
+ npm run build
20
+ npm run lint
21
+ npx playwright install chromium
22
+ npm test
23
+ xcodebuild -scheme WebViewNativeUIEngine -destination 'platform=iOS Simulator,id=<booted simulator UUID>' test
24
+ ```
25
+
26
+ Lint combines strict TypeScript checking, the standard ESLint/TypeScript recommended rules, and typed Promise checks for `src`. Scripts and tests receive syntax-level linting; Prettier owns formatting. `npm run format` applies formatting only. Bridge payloads and test doubles may use explicit `any`; production boundary validation remains necessary. An explicit `void` marks background work, but does not handle a rejected Promise. Android uses `lintRelease` with warnings treated as errors in CI; Swift is compiled and tested on an iOS simulator.
27
+
28
+ The engine is versioned independently of its consumers. Publish order is Maven publication acceptance → npm engine; consumers upgrade separately. npm releases require a successful Maven publication workflow for the exact tagged commit. Maven Central distribution may still be pending: Android consumers must wait until the matching POM and AAR are downloadable, while iOS and Web consumers can upgrade immediately. No publication is performed by build or test commands. Tests cover plain DOM, fractional/RTL geometry, stale ACKs, image ownership and native WKWebView scroll tracking; consumer tests remain responsible for Ionic/SDK integration.
29
+
30
+ CI-only setup, packaging commands and GitHub outputs are defined in `.github/workflows/`. Scripts retain the runtime build, reusable consumer/package verification, and tested publication logic (Central state handling and npm artifact comparison).
31
+
32
+ ## Maven publication
33
+
34
+ The `Native UI Engine Maven Release` workflow is manually dispatched on `main` with the exact engine package version. That commit must first pass `Native UI Engine` CI. It signs an AAR, source archive, generated Kotlin API documentation and Maven metadata, uploads once, and completes when Central reports `PUBLISHING` or `PUBLISHED`. The deployment ID and state changes are recorded in the run summary. `PUBLISHING` means validation passed and Central accepted publication; it does not guarantee that artifacts can already be downloaded. This workflow does not wait for distribution. The npm release checks that this workflow succeeded for the exact release commit; it does not wait for Maven Central distribution. Existing coordinates are never overwritten; if an upload times out, inspect the recorded deployment in Central before taking further action.
35
+
36
+ Configure the repository's Actions secrets with `CENTRAL_USERNAME`, `CENTRAL_PASSWORD` (a Central user token), `MAVEN_SIGNING_KEY` (ASCII-armored private key) and `MAVEN_SIGNING_PASSWORD`. The signing public key must be available to Central. GitHub secrets are scoped to this repository.
37
+
38
+ For local packaging verification, run `python3 scripts/native-ui-engine-release/verify.py` with JDK 21, Android SDK 36 and GnuPG. It creates a disposable key, validates signatures and checksums, and deletes its test ZIP without uploading. Publication always rebuilds from clean outputs with the real signing key. Publish its Maven artifact before the corresponding npm release, including prereleases.
39
+
40
+ ## Maintainer release setup
41
+
42
+ This repository starts from the merged Engine implementation at [ionic-theme-ios27 `b218d8b`](https://github.com/rdlabo-dev/ionic-theme-ios27/commit/b218d8bfe0c87c02addc668fee9c7aa333489486). Extraction preserves the merged implementation; Cordova host support is developed in this repository. The renamed package starts at version `0.0.1`. Publish the same version for npm, SwiftPM and Maven; do not reuse the old package coordinates.
43
+
44
+ The npm organization `webview-native-ui` and Maven Central namespace `io.github.webview-native-ui` are registered. Maintain npm trusted publishing for `webview-native-ui/engine`, workflow `release.yml`, and provision the Maven secrets above in this repository. Credentials are not copied by moving source files.
45
+
46
+ 1. Prepare the next version and release notes on `main`; use `npm run release` to create a standard `vX.Y.Z` tag/GitHub release. Keep the GitHub release as a draft until the Maven publication workflow succeeds.
47
+ 2. After that main commit passes CI, dispatch `native-ui-engine-release.yml` with the exact package version.
48
+ 3. Central `PUBLISHING` is an accepted upload, not download readiness. Once the Maven publication workflow succeeds, publish the prepared GitHub release. Note any pending Android artifact distribution in the release notes. `release.yml` verifies the tag, package version, CI and Maven publication acceptance before npm publication (`next` for prereleases, `latest` otherwise).
49
+
50
+ No build or test command publishes a package. Existing npm/Maven versions are never overwritten.
51
+
52
+ To rerun release verification for an existing tag, dispatch `release.yml` with that tag. It checks the original tagged commit and skips npm publication only when the existing package contents match. Keep the tag unchanged. Release tooling uses the Node.js 24 bundled npm, matching CI.