@nativescript/vite 8.0.0-alpha.3 → 8.0.0-alpha.31
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.
- package/README.md +51 -11
- package/configuration/angular.d.ts +34 -1
- package/configuration/angular.js +373 -163
- package/configuration/angular.js.map +1 -1
- package/configuration/base.js +184 -14
- package/configuration/base.js.map +1 -1
- package/configuration/javascript.js +5 -72
- package/configuration/javascript.js.map +1 -1
- package/configuration/solid.js +27 -1
- package/configuration/solid.js.map +1 -1
- package/configuration/typescript.js +5 -75
- package/configuration/typescript.js.map +1 -1
- package/helpers/app-components.d.ts +2 -1
- package/helpers/app-components.js.map +1 -1
- package/helpers/app-css-state.d.ts +8 -0
- package/helpers/app-css-state.js +8 -0
- package/helpers/app-css-state.js.map +1 -0
- package/helpers/bundler-context.d.ts +11 -0
- package/helpers/bundler-context.js +71 -0
- package/helpers/bundler-context.js.map +1 -0
- package/helpers/config-as-json.js +10 -0
- package/helpers/config-as-json.js.map +1 -1
- package/helpers/dev-host.d.ts +341 -0
- package/helpers/dev-host.js +617 -0
- package/helpers/dev-host.js.map +1 -0
- package/helpers/esbuild-platform-resolver.js +4 -1
- package/helpers/esbuild-platform-resolver.js.map +1 -1
- package/helpers/global-defines.d.ts +51 -0
- package/helpers/global-defines.js +77 -0
- package/helpers/global-defines.js.map +1 -1
- package/helpers/hmr-scope.d.ts +26 -0
- package/helpers/hmr-scope.js +67 -0
- package/helpers/hmr-scope.js.map +1 -0
- package/helpers/init.js +0 -18
- package/helpers/init.js.map +1 -1
- package/helpers/logging.d.ts +1 -0
- package/helpers/logging.js +63 -3
- package/helpers/logging.js.map +1 -1
- package/helpers/main-entry.d.ts +2 -1
- package/helpers/main-entry.js +430 -47
- package/helpers/main-entry.js.map +1 -1
- package/helpers/nativeclass-esbuild-plugin.d.ts +2 -1
- package/helpers/nativeclass-esbuild-plugin.js.map +1 -1
- package/helpers/nativeclass-transform.js.map +1 -1
- package/helpers/nativeclass-transformer-plugin.d.ts +9 -2
- package/helpers/nativeclass-transformer-plugin.js +157 -14
- package/helpers/nativeclass-transformer-plugin.js.map +1 -1
- package/helpers/nativescript-package-resolver.js +8 -3
- package/helpers/nativescript-package-resolver.js.map +1 -1
- package/helpers/normalize-id.d.ts +42 -0
- package/helpers/normalize-id.js +60 -0
- package/helpers/normalize-id.js.map +1 -0
- package/helpers/ns-core-url.d.ts +88 -0
- package/helpers/ns-core-url.js +191 -0
- package/helpers/ns-core-url.js.map +1 -0
- package/helpers/package-platform-aliases.js +4 -3
- package/helpers/package-platform-aliases.js.map +1 -1
- package/helpers/platform-types.d.ts +2 -0
- package/helpers/platform-types.js +2 -0
- package/helpers/platform-types.js.map +1 -0
- package/helpers/project.d.ts +35 -0
- package/helpers/project.js +120 -2
- package/helpers/project.js.map +1 -1
- package/helpers/resolver.js +17 -2
- package/helpers/resolver.js.map +1 -1
- package/helpers/ts-config-paths.d.ts +14 -0
- package/helpers/ts-config-paths.js +89 -8
- package/helpers/ts-config-paths.js.map +1 -1
- package/helpers/typescript-check.d.ts +2 -1
- package/helpers/typescript-check.js.map +1 -1
- package/helpers/workers.d.ts +20 -19
- package/helpers/workers.js +624 -4
- package/helpers/workers.js.map +1 -1
- package/hmr/client/css-handler.d.ts +1 -0
- package/hmr/client/css-handler.js +33 -20
- package/hmr/client/css-handler.js.map +1 -1
- package/hmr/client/css-update-overlay.d.ts +18 -0
- package/hmr/client/css-update-overlay.js +27 -0
- package/hmr/client/css-update-overlay.js.map +1 -0
- package/hmr/client/framework-client-strategy.d.ts +73 -0
- package/hmr/client/framework-client-strategy.js +19 -0
- package/hmr/client/framework-client-strategy.js.map +1 -0
- package/hmr/client/hmr-pending-overlay.d.ts +27 -0
- package/hmr/client/hmr-pending-overlay.js +50 -0
- package/hmr/client/hmr-pending-overlay.js.map +1 -0
- package/hmr/client/index.js +459 -164
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.d.ts +6 -1
- package/hmr/client/utils.js +184 -8
- package/hmr/client/utils.js.map +1 -1
- package/hmr/entry-runtime.d.ts +2 -1
- package/hmr/entry-runtime.js +252 -65
- package/hmr/entry-runtime.js.map +1 -1
- package/hmr/frameworks/angular/build/angular-linker.d.ts +12 -0
- package/hmr/frameworks/angular/build/angular-linker.js +109 -0
- package/hmr/frameworks/angular/build/angular-linker.js.map +1 -0
- package/hmr/frameworks/angular/build/inject-component-hmr-registration.d.ts +112 -0
- package/hmr/frameworks/angular/build/inject-component-hmr-registration.js +291 -0
- package/hmr/frameworks/angular/build/inject-component-hmr-registration.js.map +1 -0
- package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.d.ts +75 -0
- package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.js +221 -0
- package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.js.map +1 -0
- package/{helpers/angular → hmr/frameworks/angular/build}/inline-decorator-component-templates.js +1 -170
- package/hmr/frameworks/angular/build/inline-decorator-component-templates.js.map +1 -0
- package/hmr/frameworks/angular/build/js-lexer.d.ts +4 -0
- package/{helpers/angular/synthesize-decorator-ctor-parameters.js → hmr/frameworks/angular/build/js-lexer.js} +22 -96
- package/hmr/frameworks/angular/build/js-lexer.js.map +1 -0
- package/hmr/frameworks/angular/build/shared-linker.d.ts +39 -0
- package/hmr/frameworks/angular/build/shared-linker.js +128 -0
- package/hmr/frameworks/angular/build/shared-linker.js.map +1 -0
- package/hmr/frameworks/angular/build/synthesize-decorator-ctor-parameters.js +88 -0
- package/hmr/frameworks/angular/build/synthesize-decorator-ctor-parameters.js.map +1 -0
- package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-injectable-factories.js +1 -174
- package/hmr/frameworks/angular/build/synthesize-injectable-factories.js.map +1 -0
- package/{helpers/angular → hmr/frameworks/angular/build}/util.d.ts +1 -0
- package/hmr/frameworks/angular/build/util.js +155 -0
- package/hmr/frameworks/angular/build/util.js.map +1 -0
- package/hmr/frameworks/angular/client/index.d.ts +1 -0
- package/hmr/frameworks/angular/client/index.js +778 -20
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/angular/client/strategy.d.ts +9 -0
- package/hmr/frameworks/angular/client/strategy.js +19 -0
- package/hmr/frameworks/angular/client/strategy.js.map +1 -0
- package/hmr/frameworks/angular/server/angular-root-component.d.ts +79 -0
- package/hmr/frameworks/angular/server/angular-root-component.js +149 -0
- package/hmr/frameworks/angular/server/angular-root-component.js.map +1 -0
- package/hmr/frameworks/angular/server/linker.js +1 -4
- package/hmr/frameworks/angular/server/linker.js.map +1 -1
- package/hmr/frameworks/angular/server/strategy.js +448 -12
- package/hmr/frameworks/angular/server/strategy.js.map +1 -1
- package/hmr/{server → frameworks/angular/server}/websocket-angular-entry.js +2 -2
- package/hmr/frameworks/angular/server/websocket-angular-entry.js.map +1 -0
- package/hmr/{server → frameworks/angular/server}/websocket-angular-hot-update.d.ts +17 -11
- package/hmr/frameworks/angular/server/websocket-angular-hot-update.js +336 -0
- package/hmr/frameworks/angular/server/websocket-angular-hot-update.js.map +1 -0
- package/hmr/frameworks/solid/build/solid-jsx-deps.d.ts +15 -0
- package/hmr/frameworks/solid/build/solid-jsx-deps.js +178 -0
- package/hmr/frameworks/solid/build/solid-jsx-deps.js.map +1 -0
- package/hmr/frameworks/solid/server/strategy.js +360 -16
- package/hmr/frameworks/solid/server/strategy.js.map +1 -1
- package/hmr/frameworks/typescript/server/strategy.js +28 -14
- package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
- package/hmr/frameworks/vue/client/index.js +30 -199
- package/hmr/frameworks/vue/client/index.js.map +1 -1
- package/hmr/frameworks/vue/client/strategy.d.ts +7 -0
- package/hmr/frameworks/vue/client/strategy.js +83 -0
- package/hmr/frameworks/vue/client/strategy.js.map +1 -0
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.d.ts +82 -0
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js +133 -0
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.d.ts +7 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.js +706 -0
- package/hmr/frameworks/vue/server/sfc-route-assemble.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-meta.d.ts +7 -0
- package/hmr/frameworks/vue/server/sfc-route-meta.js +80 -0
- package/hmr/frameworks/vue/server/sfc-route-meta.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.d.ts +8 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.js +457 -0
- package/hmr/frameworks/vue/server/sfc-route-serve.js.map +1 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.d.ts +19 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.js +14 -0
- package/hmr/frameworks/vue/server/sfc-route-shared.js.map +1 -0
- package/hmr/frameworks/vue/server/strategy.js +244 -0
- package/hmr/frameworks/vue/server/strategy.js.map +1 -1
- package/hmr/frameworks/vue/server/websocket-sfc.d.ts +15 -0
- package/hmr/frameworks/vue/server/websocket-sfc.js +20 -0
- package/hmr/frameworks/vue/server/websocket-sfc.js.map +1 -0
- package/hmr/helpers/ast-normalizer.js +52 -5
- package/hmr/helpers/ast-normalizer.js.map +1 -1
- package/hmr/helpers/cjs-named-exports.d.ts +23 -0
- package/hmr/helpers/cjs-named-exports.js +152 -0
- package/hmr/helpers/cjs-named-exports.js.map +1 -0
- package/hmr/helpers/package-exports.d.ts +16 -0
- package/hmr/helpers/package-exports.js +396 -0
- package/hmr/helpers/package-exports.js.map +1 -0
- package/hmr/server/constants.js +13 -4
- package/hmr/server/constants.js.map +1 -1
- package/hmr/server/core-sanitize.d.ts +90 -7
- package/hmr/server/core-sanitize.js +211 -56
- package/hmr/server/core-sanitize.js.map +1 -1
- package/hmr/server/device-transform-helpers.d.ts +24 -0
- package/hmr/server/device-transform-helpers.js +327 -0
- package/hmr/server/device-transform-helpers.js.map +1 -0
- package/hmr/server/framework-strategy.d.ts +95 -11
- package/hmr/server/hmr-module-graph.d.ts +37 -0
- package/hmr/server/hmr-module-graph.js +214 -0
- package/hmr/server/hmr-module-graph.js.map +1 -0
- package/hmr/server/import-map.d.ts +11 -2
- package/hmr/server/import-map.js +59 -40
- package/hmr/server/import-map.js.map +1 -1
- package/hmr/server/index.js +7 -16
- package/hmr/server/index.js.map +1 -1
- package/hmr/server/ns-core-cjs-shape.d.ts +204 -0
- package/hmr/server/ns-core-cjs-shape.js +271 -0
- package/hmr/server/ns-core-cjs-shape.js.map +1 -0
- package/hmr/server/ns-rt-bridge.d.ts +51 -0
- package/hmr/server/ns-rt-bridge.js +131 -0
- package/hmr/server/ns-rt-bridge.js.map +1 -0
- package/hmr/server/ns-rt-route.d.ts +5 -0
- package/hmr/server/ns-rt-route.js +35 -0
- package/hmr/server/ns-rt-route.js.map +1 -0
- package/hmr/server/perf-instrumentation.d.ts +114 -0
- package/hmr/server/perf-instrumentation.js +195 -0
- package/hmr/server/perf-instrumentation.js.map +1 -0
- package/hmr/server/process-code-for-device.d.ts +15 -0
- package/hmr/server/process-code-for-device.js +654 -0
- package/hmr/server/process-code-for-device.js.map +1 -0
- package/hmr/server/require-guard.d.ts +1 -0
- package/hmr/server/require-guard.js +12 -0
- package/hmr/server/require-guard.js.map +1 -0
- package/hmr/server/rewrite-imports.d.ts +2 -0
- package/hmr/server/rewrite-imports.js +604 -0
- package/hmr/server/rewrite-imports.js.map +1 -0
- package/hmr/server/route-helpers.d.ts +7 -0
- package/hmr/server/route-helpers.js +13 -0
- package/hmr/server/route-helpers.js.map +1 -0
- package/hmr/server/server-origin.d.ts +12 -0
- package/hmr/server/server-origin.js +66 -0
- package/hmr/server/server-origin.js.map +1 -0
- package/hmr/server/shared-transform-request.js +12 -5
- package/hmr/server/shared-transform-request.js.map +1 -1
- package/hmr/server/transform-cache-invalidation.d.ts +11 -0
- package/hmr/server/transform-cache-invalidation.js +84 -0
- package/hmr/server/transform-cache-invalidation.js.map +1 -0
- package/hmr/server/vendor-bare-module-shims.d.ts +4 -0
- package/hmr/server/vendor-bare-module-shims.js +80 -0
- package/hmr/server/vendor-bare-module-shims.js.map +1 -0
- package/hmr/server/vite-plugin.js +60 -42
- package/hmr/server/vite-plugin.js.map +1 -1
- package/hmr/server/websocket-core-bridge.d.ts +41 -6
- package/hmr/server/websocket-core-bridge.js +72 -75
- package/hmr/server/websocket-core-bridge.js.map +1 -1
- package/hmr/server/websocket-css-hot-update.d.ts +33 -0
- package/hmr/server/websocket-css-hot-update.js +65 -0
- package/hmr/server/websocket-css-hot-update.js.map +1 -0
- package/hmr/server/websocket-device-transform.d.ts +3 -0
- package/hmr/server/websocket-device-transform.js +7 -0
- package/hmr/server/websocket-device-transform.js.map +1 -0
- package/hmr/server/websocket-graph-upsert.d.ts +15 -0
- package/hmr/server/websocket-graph-upsert.js +20 -0
- package/hmr/server/websocket-graph-upsert.js.map +1 -1
- package/hmr/server/websocket-hmr-pending.d.ts +37 -0
- package/hmr/server/websocket-hmr-pending.js +55 -0
- package/hmr/server/websocket-hmr-pending.js.map +1 -0
- package/hmr/server/websocket-hot-update.d.ts +77 -0
- package/hmr/server/websocket-hot-update.js +330 -0
- package/hmr/server/websocket-hot-update.js.map +1 -0
- package/hmr/server/websocket-import-map-route.d.ts +15 -0
- package/hmr/server/websocket-import-map-route.js +46 -0
- package/hmr/server/websocket-import-map-route.js.map +1 -0
- package/hmr/server/websocket-module-bindings.js +3 -3
- package/hmr/server/websocket-module-bindings.js.map +1 -1
- package/hmr/server/websocket-module-specifiers.d.ts +66 -2
- package/hmr/server/websocket-module-specifiers.js +202 -19
- package/hmr/server/websocket-module-specifiers.js.map +1 -1
- package/hmr/server/websocket-ns-core.d.ts +21 -0
- package/hmr/server/websocket-ns-core.js +306 -0
- package/hmr/server/websocket-ns-core.js.map +1 -0
- package/hmr/server/websocket-ns-entry.d.ts +21 -0
- package/hmr/server/websocket-ns-entry.js +150 -0
- package/hmr/server/websocket-ns-entry.js.map +1 -0
- package/hmr/server/websocket-ns-m-paths.d.ts +3 -0
- package/hmr/server/websocket-ns-m-paths.js +92 -0
- package/hmr/server/websocket-ns-m-paths.js.map +1 -0
- package/hmr/server/websocket-ns-m-request.d.ts +45 -0
- package/hmr/server/websocket-ns-m-request.js +196 -0
- package/hmr/server/websocket-ns-m-request.js.map +1 -0
- package/hmr/server/websocket-ns-m.d.ts +33 -0
- package/hmr/server/websocket-ns-m.js +748 -0
- package/hmr/server/websocket-ns-m.js.map +1 -0
- package/hmr/server/websocket-served-module-helpers.d.ts +39 -0
- package/hmr/server/websocket-served-module-helpers.js +654 -0
- package/hmr/server/websocket-served-module-helpers.js.map +1 -0
- package/hmr/server/websocket-txn.d.ts +6 -0
- package/hmr/server/websocket-txn.js +39 -0
- package/hmr/server/websocket-txn.js.map +1 -0
- package/hmr/server/websocket-vendor-unifier.d.ts +9 -0
- package/hmr/server/websocket-vendor-unifier.js +46 -0
- package/hmr/server/websocket-vendor-unifier.js.map +1 -0
- package/hmr/server/websocket.d.ts +8 -39
- package/hmr/server/websocket.js +602 -6049
- package/hmr/server/websocket.js.map +1 -1
- package/hmr/shared/ns-globals.d.ts +118 -0
- package/hmr/shared/ns-globals.js +27 -0
- package/hmr/shared/ns-globals.js.map +1 -0
- package/hmr/shared/protocol.d.ts +136 -0
- package/hmr/shared/protocol.js +28 -0
- package/hmr/shared/protocol.js.map +1 -0
- package/hmr/shared/runtime/boot-placeholder-ui.d.ts +69 -0
- package/hmr/shared/runtime/boot-placeholder-ui.js +101 -0
- package/hmr/shared/runtime/boot-placeholder-ui.js.map +1 -0
- package/hmr/shared/runtime/boot-progress.d.ts +40 -0
- package/hmr/shared/runtime/boot-progress.js +128 -0
- package/hmr/shared/runtime/boot-progress.js.map +1 -0
- package/hmr/shared/runtime/boot-timeline.d.ts +18 -0
- package/hmr/shared/runtime/boot-timeline.js +52 -0
- package/hmr/shared/runtime/boot-timeline.js.map +1 -0
- package/hmr/shared/runtime/dev-overlay-snapshots.d.ts +31 -0
- package/hmr/shared/runtime/dev-overlay-snapshots.js +324 -0
- package/hmr/shared/runtime/dev-overlay-snapshots.js.map +1 -0
- package/hmr/shared/runtime/dev-overlay.d.ts +75 -26
- package/hmr/shared/runtime/dev-overlay.js +990 -260
- package/hmr/shared/runtime/dev-overlay.js.map +1 -1
- package/hmr/shared/runtime/module-provenance.js +1 -4
- package/hmr/shared/runtime/module-provenance.js.map +1 -1
- package/hmr/shared/runtime/root-placeholder-view.d.ts +19 -0
- package/hmr/shared/runtime/root-placeholder-view.js +310 -0
- package/hmr/shared/runtime/root-placeholder-view.js.map +1 -0
- package/hmr/shared/runtime/root-placeholder.js +352 -194
- package/hmr/shared/runtime/root-placeholder.js.map +1 -1
- package/hmr/shared/runtime/session-bootstrap.js +164 -1
- package/hmr/shared/runtime/session-bootstrap.js.map +1 -1
- package/hmr/shared/runtime/vendor-bootstrap.js +1 -9
- package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
- package/hmr/shared/vendor/manifest-collect.d.ts +4 -0
- package/hmr/shared/vendor/manifest-collect.js +512 -0
- package/hmr/shared/vendor/manifest-collect.js.map +1 -0
- package/hmr/shared/vendor/manifest-loader.d.ts +2 -1
- package/hmr/shared/vendor/manifest-loader.js +3 -2
- package/hmr/shared/vendor/manifest-loader.js.map +1 -1
- package/hmr/shared/vendor/manifest.d.ts +1 -7
- package/hmr/shared/vendor/manifest.js +102 -741
- package/hmr/shared/vendor/manifest.js.map +1 -1
- package/hmr/shared/vendor/vendor-device-shim.d.ts +1 -0
- package/hmr/shared/vendor/vendor-device-shim.js +208 -0
- package/hmr/shared/vendor/vendor-device-shim.js.map +1 -0
- package/hmr/shared/vendor/vendor-esbuild-plugins.d.ts +16 -0
- package/hmr/shared/vendor/vendor-esbuild-plugins.js +203 -0
- package/hmr/shared/vendor/vendor-esbuild-plugins.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +5 -0
- package/index.js.map +1 -1
- package/package.json +55 -11
- package/runtime/core-aliases-early.js +17 -41
- package/runtime/core-aliases-early.js.map +1 -1
- package/helpers/angular/angular-linker.d.ts +0 -13
- package/helpers/angular/angular-linker.js +0 -194
- package/helpers/angular/angular-linker.js.map +0 -1
- package/helpers/angular/inline-decorator-component-templates.js.map +0 -1
- package/helpers/angular/shared-linker.d.ts +0 -11
- package/helpers/angular/shared-linker.js +0 -75
- package/helpers/angular/shared-linker.js.map +0 -1
- package/helpers/angular/synthesize-decorator-ctor-parameters.js.map +0 -1
- package/helpers/angular/synthesize-injectable-factories.js.map +0 -1
- package/helpers/angular/util.js +0 -67
- package/helpers/angular/util.js.map +0 -1
- package/helpers/prelink-angular.d.ts +0 -2
- package/helpers/prelink-angular.js +0 -117
- package/helpers/prelink-angular.js.map +0 -1
- package/hmr/server/websocket-angular-entry.js.map +0 -1
- package/hmr/server/websocket-angular-hot-update.js +0 -239
- package/hmr/server/websocket-angular-hot-update.js.map +0 -1
- /package/{helpers/angular → hmr/frameworks/angular/build}/inline-decorator-component-templates.d.ts +0 -0
- /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-decorator-ctor-parameters.d.ts +0 -0
- /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-injectable-factories.d.ts +0 -0
- /package/hmr/{server → frameworks/angular/server}/websocket-angular-entry.d.ts +0 -0
|
@@ -1,19 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
title: BOOT_TITLE,
|
|
7
|
-
phase: '',
|
|
8
|
-
detail: '',
|
|
9
|
-
progress: null,
|
|
10
|
-
busy: false,
|
|
11
|
-
blocking: false,
|
|
12
|
-
tone: 'info',
|
|
13
|
-
};
|
|
1
|
+
import { BOOT_PLACEHOLDER_MOTION, computeBootProgressFillScale, formatBootDetailLine, formatBootPrimaryLine } from './boot-placeholder-ui.js';
|
|
2
|
+
import { BOOT_TITLE, DEFAULT_SNAPSHOT, createBootOverlaySnapshot, createConnectionOverlaySnapshot, createUpdateOverlaySnapshot } from './dev-overlay-snapshots.js';
|
|
3
|
+
// Re-export the snapshot model so existing `./dev-overlay.js` importers keep working.
|
|
4
|
+
export { createBootOverlaySnapshot, createConnectionOverlaySnapshot, createUpdateOverlaySnapshot } from './dev-overlay-snapshots.js';
|
|
5
|
+
const DEFAULT_OVERLAY_POSITION = 'top';
|
|
14
6
|
function getOverlayGlobal() {
|
|
15
7
|
return globalThis;
|
|
16
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the configured live-overlay position.
|
|
11
|
+
*
|
|
12
|
+
* Reads `globalThis.__NS_HMR_OVERLAY_POSITION__` so a project can
|
|
13
|
+
* override the default at boot time (e.g. inside `app.ts` before the
|
|
14
|
+
* Vite session bootstraps). Falls back to 'top' which gives the
|
|
15
|
+
* toast-style chip with a slide-in animation and safe-area padding.
|
|
16
|
+
*/
|
|
17
|
+
export function getHmrDevOverlayPosition() {
|
|
18
|
+
const g = getOverlayGlobal();
|
|
19
|
+
const stored = g.__NS_HMR_OVERLAY_POSITION__;
|
|
20
|
+
if (stored === 'top' || stored === 'bottom' || stored === 'center') {
|
|
21
|
+
return stored;
|
|
22
|
+
}
|
|
23
|
+
return DEFAULT_OVERLAY_POSITION;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Imperative setter for the live-overlay position. Re-applies the
|
|
27
|
+
* current snapshot so the change is visible without waiting for the
|
|
28
|
+
* next HMR cycle. Useful during dev to A/B between top/bottom/center
|
|
29
|
+
* without restarting the app.
|
|
30
|
+
*/
|
|
31
|
+
export function setHmrDevOverlayPosition(position) {
|
|
32
|
+
if (position !== 'top' && position !== 'bottom' && position !== 'center') {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const g = getOverlayGlobal();
|
|
36
|
+
g.__NS_HMR_OVERLAY_POSITION__ = position;
|
|
37
|
+
const state = getRuntimeState();
|
|
38
|
+
applyRuntimeSnapshot(state.snapshot);
|
|
39
|
+
}
|
|
17
40
|
function getRuntimeState() {
|
|
18
41
|
const g = getOverlayGlobal();
|
|
19
42
|
if (!g.__NS_HMR_DEV_OVERLAY_STATE__) {
|
|
@@ -21,240 +44,25 @@ function getRuntimeState() {
|
|
|
21
44
|
snapshot: { ...DEFAULT_SNAPSHOT },
|
|
22
45
|
bootRefs: null,
|
|
23
46
|
liveRefs: null,
|
|
47
|
+
iosRefs: null,
|
|
48
|
+
iosBuildFailed: false,
|
|
24
49
|
verbose: false,
|
|
50
|
+
updateAutoHideTimer: null,
|
|
51
|
+
updateCycleStartedAt: 0,
|
|
25
52
|
};
|
|
26
53
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const phaseInfo = {
|
|
40
|
-
placeholder: {
|
|
41
|
-
visible: true,
|
|
42
|
-
mode: 'boot',
|
|
43
|
-
badge: 'BOOT',
|
|
44
|
-
title: BOOT_TITLE,
|
|
45
|
-
phase: 'Preparing the HTTP HMR bootstrap',
|
|
46
|
-
progress: 4,
|
|
47
|
-
busy: true,
|
|
48
|
-
blocking: true,
|
|
49
|
-
tone: 'info',
|
|
50
|
-
},
|
|
51
|
-
'probing-origin': {
|
|
52
|
-
visible: true,
|
|
53
|
-
mode: 'boot',
|
|
54
|
-
badge: 'BOOT',
|
|
55
|
-
title: BOOT_TITLE,
|
|
56
|
-
phase: 'Contacting the Vite dev server',
|
|
57
|
-
progress: 8,
|
|
58
|
-
busy: true,
|
|
59
|
-
blocking: true,
|
|
60
|
-
tone: 'info',
|
|
61
|
-
},
|
|
62
|
-
'loading-entry-runtime': {
|
|
63
|
-
visible: true,
|
|
64
|
-
mode: 'boot',
|
|
65
|
-
badge: 'BOOT',
|
|
66
|
-
title: BOOT_TITLE,
|
|
67
|
-
phase: 'Loading the entry runtime bridge',
|
|
68
|
-
progress: 16,
|
|
69
|
-
busy: true,
|
|
70
|
-
blocking: true,
|
|
71
|
-
tone: 'info',
|
|
72
|
-
},
|
|
73
|
-
'configuring-import-map': {
|
|
74
|
-
visible: true,
|
|
75
|
-
mode: 'boot',
|
|
76
|
-
badge: 'BOOT',
|
|
77
|
-
title: BOOT_TITLE,
|
|
78
|
-
phase: 'Configuring the import map',
|
|
79
|
-
progress: 26,
|
|
80
|
-
busy: true,
|
|
81
|
-
blocking: true,
|
|
82
|
-
tone: 'info',
|
|
83
|
-
},
|
|
84
|
-
'loading-runtime-bridge': {
|
|
85
|
-
visible: true,
|
|
86
|
-
mode: 'boot',
|
|
87
|
-
badge: 'BOOT',
|
|
88
|
-
title: BOOT_TITLE,
|
|
89
|
-
phase: 'Loading the NativeScript runtime bridge',
|
|
90
|
-
progress: 40,
|
|
91
|
-
busy: true,
|
|
92
|
-
blocking: true,
|
|
93
|
-
tone: 'info',
|
|
94
|
-
},
|
|
95
|
-
'loading-core-bridge': {
|
|
96
|
-
visible: true,
|
|
97
|
-
mode: 'boot',
|
|
98
|
-
badge: 'BOOT',
|
|
99
|
-
title: BOOT_TITLE,
|
|
100
|
-
phase: 'Loading the unified core bridge',
|
|
101
|
-
progress: 54,
|
|
102
|
-
busy: true,
|
|
103
|
-
blocking: true,
|
|
104
|
-
tone: 'info',
|
|
105
|
-
},
|
|
106
|
-
'preloading-style-scope': {
|
|
107
|
-
visible: true,
|
|
108
|
-
mode: 'boot',
|
|
109
|
-
badge: 'BOOT',
|
|
110
|
-
title: BOOT_TITLE,
|
|
111
|
-
phase: 'Preparing the shared style scope',
|
|
112
|
-
progress: 62,
|
|
113
|
-
busy: true,
|
|
114
|
-
blocking: true,
|
|
115
|
-
tone: 'info',
|
|
116
|
-
},
|
|
117
|
-
'installing-css': {
|
|
118
|
-
visible: true,
|
|
119
|
-
mode: 'boot',
|
|
120
|
-
badge: 'BOOT',
|
|
121
|
-
title: BOOT_TITLE,
|
|
122
|
-
phase: 'Applying the app stylesheet',
|
|
123
|
-
progress: 70,
|
|
124
|
-
busy: true,
|
|
125
|
-
blocking: true,
|
|
126
|
-
tone: 'info',
|
|
127
|
-
},
|
|
128
|
-
'importing-main': {
|
|
129
|
-
visible: true,
|
|
130
|
-
mode: 'boot',
|
|
131
|
-
badge: 'BOOT',
|
|
132
|
-
title: BOOT_TITLE,
|
|
133
|
-
phase: 'Importing the app entry',
|
|
134
|
-
progress: 82,
|
|
135
|
-
busy: true,
|
|
136
|
-
blocking: true,
|
|
137
|
-
tone: 'info',
|
|
138
|
-
},
|
|
139
|
-
'waiting-for-app': {
|
|
140
|
-
visible: true,
|
|
141
|
-
mode: 'boot',
|
|
142
|
-
badge: 'BOOT',
|
|
143
|
-
title: BOOT_TITLE,
|
|
144
|
-
phase: 'Waiting for the app root view',
|
|
145
|
-
progress: 94,
|
|
146
|
-
busy: true,
|
|
147
|
-
blocking: true,
|
|
148
|
-
tone: 'info',
|
|
149
|
-
},
|
|
150
|
-
'app-root-committed': {
|
|
151
|
-
visible: true,
|
|
152
|
-
mode: 'boot',
|
|
153
|
-
badge: 'READY',
|
|
154
|
-
title: BOOT_TITLE,
|
|
155
|
-
phase: 'Real app root committed',
|
|
156
|
-
progress: 100,
|
|
157
|
-
busy: false,
|
|
158
|
-
blocking: true,
|
|
159
|
-
tone: 'info',
|
|
160
|
-
},
|
|
161
|
-
ready: {
|
|
162
|
-
visible: true,
|
|
163
|
-
mode: 'boot',
|
|
164
|
-
badge: 'READY',
|
|
165
|
-
title: BOOT_TITLE,
|
|
166
|
-
phase: 'Boot complete',
|
|
167
|
-
progress: 100,
|
|
168
|
-
busy: false,
|
|
169
|
-
blocking: true,
|
|
170
|
-
tone: 'info',
|
|
171
|
-
},
|
|
172
|
-
error: {
|
|
173
|
-
visible: true,
|
|
174
|
-
mode: 'boot',
|
|
175
|
-
badge: 'RETRY',
|
|
176
|
-
title: BOOT_TITLE,
|
|
177
|
-
phase: 'Retrying after a boot failure',
|
|
178
|
-
progress: null,
|
|
179
|
-
busy: true,
|
|
180
|
-
blocking: true,
|
|
181
|
-
tone: 'error',
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
const base = phaseInfo[stage];
|
|
185
|
-
let detail = info?.detail || '';
|
|
186
|
-
if (!detail && stage === 'probing-origin' && info?.origin) {
|
|
187
|
-
detail = `Trying ${info.origin}`;
|
|
188
|
-
}
|
|
189
|
-
if (attemptText) {
|
|
190
|
-
detail = detail ? `${detail} ${attemptText}` : attemptText;
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
...base,
|
|
194
|
-
detail,
|
|
195
|
-
progress: typeof info?.progress === 'number' || info?.progress === null ? info.progress : base.progress,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
export function createConnectionOverlaySnapshot(stage, info) {
|
|
199
|
-
if (stage === 'healthy') {
|
|
200
|
-
return { ...DEFAULT_SNAPSHOT };
|
|
201
|
-
}
|
|
202
|
-
const phaseInfo = {
|
|
203
|
-
connecting: {
|
|
204
|
-
visible: true,
|
|
205
|
-
mode: 'connection',
|
|
206
|
-
badge: 'SOCKET',
|
|
207
|
-
title: 'Waiting for Vite dev server',
|
|
208
|
-
phase: 'Opening websocket connection',
|
|
209
|
-
detail: 'Live updates are paused until the connection is healthy.',
|
|
210
|
-
progress: null,
|
|
211
|
-
busy: true,
|
|
212
|
-
blocking: false,
|
|
213
|
-
tone: 'warn',
|
|
214
|
-
},
|
|
215
|
-
reconnecting: {
|
|
216
|
-
visible: true,
|
|
217
|
-
mode: 'connection',
|
|
218
|
-
badge: 'SOCKET',
|
|
219
|
-
title: 'HMR connection lost',
|
|
220
|
-
phase: 'Reconnecting Vite websocket',
|
|
221
|
-
detail: 'The app may be stale until the dev server reconnects.',
|
|
222
|
-
progress: null,
|
|
223
|
-
busy: true,
|
|
224
|
-
blocking: false,
|
|
225
|
-
tone: 'warn',
|
|
226
|
-
},
|
|
227
|
-
synchronizing: {
|
|
228
|
-
visible: true,
|
|
229
|
-
mode: 'connection',
|
|
230
|
-
badge: 'SYNC',
|
|
231
|
-
title: 'HMR connection restored',
|
|
232
|
-
phase: 'Synchronizing live-update state',
|
|
233
|
-
detail: 'Finalizing the module graph before dismissing the overlay.',
|
|
234
|
-
progress: 95,
|
|
235
|
-
busy: true,
|
|
236
|
-
blocking: false,
|
|
237
|
-
tone: 'info',
|
|
238
|
-
},
|
|
239
|
-
offline: {
|
|
240
|
-
visible: true,
|
|
241
|
-
mode: 'connection',
|
|
242
|
-
badge: 'OFFLINE',
|
|
243
|
-
title: 'Vite dev server offline',
|
|
244
|
-
phase: 'Idle...',
|
|
245
|
-
detail: 'The websocket and HTTP HMR path are both unavailable right now.',
|
|
246
|
-
progress: null,
|
|
247
|
-
busy: true,
|
|
248
|
-
blocking: false,
|
|
249
|
-
tone: 'error',
|
|
250
|
-
},
|
|
251
|
-
};
|
|
252
|
-
const base = phaseInfo[stage];
|
|
253
|
-
return {
|
|
254
|
-
...base,
|
|
255
|
-
detail: info?.detail || base.detail,
|
|
256
|
-
progress: typeof info?.progress === 'number' || info?.progress === null ? info.progress : base.progress,
|
|
257
|
-
};
|
|
54
|
+
const state = g.__NS_HMR_DEV_OVERLAY_STATE__;
|
|
55
|
+
// Backfill newer fields for legacy state objects (e.g. after hot reload)
|
|
56
|
+
// so we never observe an undefined iosRefs/iosBuildFailed at runtime.
|
|
57
|
+
if (typeof state.iosRefs === 'undefined')
|
|
58
|
+
state.iosRefs = null;
|
|
59
|
+
if (typeof state.iosBuildFailed === 'undefined')
|
|
60
|
+
state.iosBuildFailed = false;
|
|
61
|
+
if (typeof state.updateAutoHideTimer === 'undefined')
|
|
62
|
+
state.updateAutoHideTimer = null;
|
|
63
|
+
if (typeof state.updateCycleStartedAt !== 'number')
|
|
64
|
+
state.updateCycleStartedAt = 0;
|
|
65
|
+
return state;
|
|
258
66
|
}
|
|
259
67
|
function resolveCoreExport(name) {
|
|
260
68
|
const g = getOverlayGlobal();
|
|
@@ -435,10 +243,28 @@ function findBootStatusLabel() {
|
|
|
435
243
|
catch { }
|
|
436
244
|
return null;
|
|
437
245
|
}
|
|
246
|
+
function findBootDetailLabel() {
|
|
247
|
+
const g = getOverlayGlobal();
|
|
248
|
+
return g.__NS_DEV_BOOT_DETAIL_LABEL__ || null;
|
|
249
|
+
}
|
|
250
|
+
function findBootProgressFill() {
|
|
251
|
+
const g = getOverlayGlobal();
|
|
252
|
+
return g.__NS_DEV_BOOT_PROGRESS_FILL__ || null;
|
|
253
|
+
}
|
|
438
254
|
function updateBootStatusLabel(snapshot) {
|
|
439
|
-
const newText = formatStatusText(snapshot) || 'Preparing the HTTP HMR bootstrap (4%)';
|
|
440
255
|
const statusLabel = findBootStatusLabel();
|
|
256
|
+
const detailLabel = findBootDetailLabel();
|
|
257
|
+
const progressFill = findBootProgressFill();
|
|
441
258
|
const activityIndicator = findBootActivityIndicator();
|
|
259
|
+
// New (card) layout: phase line + detail line live in separate
|
|
260
|
+
// labels so the typography can differ. Legacy (single-label)
|
|
261
|
+
// layout: keep the original combined "phase (X%)\ndetail" text so
|
|
262
|
+
// nothing visually regresses for runtimes still attached to the
|
|
263
|
+
// older placeholder shape.
|
|
264
|
+
const hasSplitLabels = !!detailLabel;
|
|
265
|
+
const phaseLine = formatBootPrimaryLine(snapshot);
|
|
266
|
+
const detailLine = formatBootDetailLine(snapshot);
|
|
267
|
+
const combinedText = formatStatusText(snapshot) || 'Preparing the HTTP HMR bootstrap (4%)';
|
|
442
268
|
if (!statusLabel) {
|
|
443
269
|
if (activityIndicator) {
|
|
444
270
|
try {
|
|
@@ -447,11 +273,16 @@ function updateBootStatusLabel(snapshot) {
|
|
|
447
273
|
}
|
|
448
274
|
catch { }
|
|
449
275
|
}
|
|
276
|
+
applyBootProgressFill(progressFill, snapshot);
|
|
450
277
|
return;
|
|
451
278
|
}
|
|
452
279
|
try {
|
|
453
|
-
statusLabel.text =
|
|
454
|
-
|
|
280
|
+
statusLabel.text = hasSplitLabels ? phaseLine || 'Preparing the HTTP HMR bootstrap' : combinedText;
|
|
281
|
+
// Card layout uses the calibrated phase-text colour from the
|
|
282
|
+
// palette; legacy single-label layout keeps the original muted
|
|
283
|
+
// brown so we don't visually regress mid-session.
|
|
284
|
+
const phaseColorHex = snapshot.tone === 'error' ? '#B91C1C' : hasSplitLabels ? '#475569' : '#563e3fb1';
|
|
285
|
+
statusLabel.color = asColor(phaseColorHex);
|
|
455
286
|
if (typeof statusLabel.requestLayout === 'function') {
|
|
456
287
|
statusLabel.requestLayout();
|
|
457
288
|
}
|
|
@@ -461,6 +292,15 @@ function updateBootStatusLabel(snapshot) {
|
|
|
461
292
|
}
|
|
462
293
|
}
|
|
463
294
|
catch { }
|
|
295
|
+
if (detailLabel) {
|
|
296
|
+
try {
|
|
297
|
+
detailLabel.text = detailLine;
|
|
298
|
+
detailLabel.color = asColor(snapshot.tone === 'error' ? '#DC2626' : '#94A3B8');
|
|
299
|
+
detailLabel.visibility = detailLine ? 'visible' : 'collapse';
|
|
300
|
+
}
|
|
301
|
+
catch { }
|
|
302
|
+
}
|
|
303
|
+
applyBootProgressFill(progressFill, snapshot);
|
|
464
304
|
if (activityIndicator) {
|
|
465
305
|
try {
|
|
466
306
|
activityIndicator.busy = !!snapshot.busy;
|
|
@@ -469,6 +309,50 @@ function updateBootStatusLabel(snapshot) {
|
|
|
469
309
|
catch { }
|
|
470
310
|
}
|
|
471
311
|
}
|
|
312
|
+
// Drive the progress fill scaleX from the snapshot. Uses NS's view
|
|
313
|
+
// animate API for a smooth 220 ms easeOut between heartbeat ticks; a
|
|
314
|
+
// monotonic ratchet on `globalThis.__NS_DEV_BOOT_PROGRESS_LAST_SCALE__`
|
|
315
|
+
// guards against the fill snapping backwards if a less-progressed
|
|
316
|
+
// snapshot ever lands between ticks (mirrors the JS-side
|
|
317
|
+
// `applyMonotonicBootProgress` contract).
|
|
318
|
+
function applyBootProgressFill(progressFill, snapshot) {
|
|
319
|
+
if (!progressFill)
|
|
320
|
+
return;
|
|
321
|
+
const g = getOverlayGlobal();
|
|
322
|
+
const isError = snapshot.tone === 'error';
|
|
323
|
+
progressFill.backgroundColor = asColor(isError ? '#B41810' : '#3B6FE5');
|
|
324
|
+
const targetScale = computeBootProgressFillScale(snapshot.progress ?? null);
|
|
325
|
+
const previousRaw = Number(g.__NS_DEV_BOOT_PROGRESS_LAST_SCALE__);
|
|
326
|
+
const previous = Number.isFinite(previousRaw) ? previousRaw : 0;
|
|
327
|
+
const next = Math.max(previous, targetScale);
|
|
328
|
+
g.__NS_DEV_BOOT_PROGRESS_LAST_SCALE__ = next;
|
|
329
|
+
try {
|
|
330
|
+
// NS view.animate scales around `originX`/`originY`; the
|
|
331
|
+
// placeholder builder pins `originX = 0` so the fill grows
|
|
332
|
+
// rightward. animate() may be unavailable in some headless
|
|
333
|
+
// test environments — fall through to a direct property set.
|
|
334
|
+
if (typeof progressFill.animate === 'function') {
|
|
335
|
+
progressFill
|
|
336
|
+
.animate({
|
|
337
|
+
scale: { x: next, y: 1 },
|
|
338
|
+
duration: BOOT_PLACEHOLDER_MOTION.progressDurationMs,
|
|
339
|
+
curve: 'easeOut',
|
|
340
|
+
})
|
|
341
|
+
.catch(() => {
|
|
342
|
+
try {
|
|
343
|
+
progressFill.scaleX = next;
|
|
344
|
+
}
|
|
345
|
+
catch { }
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
progressFill.scaleX = next;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
progressFill.scaleX = next;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
472
356
|
function resolveActivePage() {
|
|
473
357
|
try {
|
|
474
358
|
const Frame = resolveCoreExport('Frame');
|
|
@@ -505,8 +389,18 @@ function buildLiveOverlayView(snapshot) {
|
|
|
505
389
|
overlay.height = '100%';
|
|
506
390
|
overlay.horizontalAlignment = 'stretch';
|
|
507
391
|
overlay.verticalAlignment = 'stretch';
|
|
392
|
+
// Toast mode lets touches reach the underlying app. We flip
|
|
393
|
+
// isUserInteractionEnabled in applySnapshotToLiveRefs based on
|
|
394
|
+
// the resolved position, but keep it false here as a safe default
|
|
395
|
+
// (the panel itself is purely informational).
|
|
396
|
+
try {
|
|
397
|
+
overlay.isUserInteractionEnabled = false;
|
|
398
|
+
}
|
|
399
|
+
catch { }
|
|
508
400
|
const panel = new StackLayout();
|
|
509
401
|
panel.horizontalAlignment = 'center';
|
|
402
|
+
// Vertical alignment is overridden in applySnapshotToLiveRefs
|
|
403
|
+
// based on getHmrDevOverlayPosition(); 'middle' is the default
|
|
510
404
|
panel.verticalAlignment = 'middle';
|
|
511
405
|
panel.width = 320;
|
|
512
406
|
panel.margin = 24;
|
|
@@ -528,6 +422,8 @@ function buildLiveOverlayView(snapshot) {
|
|
|
528
422
|
overlay,
|
|
529
423
|
titleLabel,
|
|
530
424
|
statusLabel,
|
|
425
|
+
wasVisible: false,
|
|
426
|
+
currentPosition: getHmrDevOverlayPosition(),
|
|
531
427
|
};
|
|
532
428
|
applySnapshotToLiveRefs(refs, snapshot);
|
|
533
429
|
return refs;
|
|
@@ -584,27 +480,698 @@ function applySnapshotToLiveRefs(refs, snapshot) {
|
|
|
584
480
|
if (!refs) {
|
|
585
481
|
return;
|
|
586
482
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
483
|
+
// 'update' mode shares the live (in-tree) overlay chrome with
|
|
484
|
+
// 'connection'. Both render a small panel inside the page; only
|
|
485
|
+
// the colours, text, and (now) panel position change with the
|
|
486
|
+
// snapshot's tone and the configured overlay position.
|
|
487
|
+
const visible = snapshot.visible && (snapshot.mode === 'connection' || snapshot.mode === 'update');
|
|
488
|
+
const wasVisible = !!refs.wasVisible;
|
|
489
|
+
const position = getHmrDevOverlayPosition();
|
|
490
|
+
const previousPosition = refs.currentPosition || position;
|
|
491
|
+
const isToast = position !== 'center';
|
|
590
492
|
refs.titleLabel.text = snapshot.title;
|
|
591
|
-
refs.titleLabel.color = asColor(snapshot.tone === 'error' ? '#b41810e6' : '#563e3fb1');
|
|
592
493
|
refs.statusLabel.text = formatStatusText(snapshot);
|
|
593
|
-
|
|
494
|
+
const textColor = snapshot.tone === 'error' ? '#b41810e6' : snapshot.tone === 'success' ? '#0e6e2fff' : '#563e3fb1';
|
|
495
|
+
refs.titleLabel.color = asColor(textColor);
|
|
496
|
+
refs.statusLabel.color = asColor(textColor);
|
|
497
|
+
// Backdrop tints (centered modal only). Toast modes use a fully
|
|
498
|
+
// transparent backdrop so the rest of the app stays visible AND
|
|
499
|
+
// reachable; the panel itself carries enough colour to stand out.
|
|
500
|
+
if (isToast) {
|
|
501
|
+
refs.overlay.backgroundColor = asColor('transparent');
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
// Original wash-by-tone for centered:
|
|
505
|
+
// error → red wash (matches existing UX)
|
|
506
|
+
// success → richer green wash so the apply event is visible
|
|
507
|
+
// on bright app backgrounds
|
|
508
|
+
// default → warm orange (existing connection-overlay look)
|
|
509
|
+
const overlayBg = snapshot.tone === 'error' ? '#b4181068' : snapshot.tone === 'success' ? '#1f883d80' : '#a1771683';
|
|
510
|
+
refs.overlay.backgroundColor = asColor(overlayBg);
|
|
511
|
+
}
|
|
512
|
+
// Panel chrome — toast and centered share the same chip look,
|
|
513
|
+
// just position differs. We keep the slightly richer green tint
|
|
514
|
+
// for the HMR success state so it pops without needing the
|
|
515
|
+
// backdrop wash.
|
|
516
|
+
let panel = null;
|
|
594
517
|
try {
|
|
595
|
-
|
|
518
|
+
panel = refs.titleLabel.parent;
|
|
596
519
|
if (panel) {
|
|
597
|
-
|
|
520
|
+
const panelBg = snapshot.tone === 'success' ? '#E6F8E9FF' : '#FFFFFFFF';
|
|
521
|
+
panel.backgroundColor = asColor(panelBg);
|
|
598
522
|
panel.opacity = 1;
|
|
599
523
|
panel.padding = 16;
|
|
600
524
|
try {
|
|
601
525
|
panel.borderRadius = 12;
|
|
602
526
|
}
|
|
603
527
|
catch { }
|
|
528
|
+
// Position-aware alignment. The wrapper GridLayout fills
|
|
529
|
+
// the page content area, which on iOS is already inside
|
|
530
|
+
// the safe area; we add a small extra margin so the chip
|
|
531
|
+
// doesn't kiss the notch / home indicator.
|
|
532
|
+
try {
|
|
533
|
+
if (position === 'top') {
|
|
534
|
+
panel.verticalAlignment = 'top';
|
|
535
|
+
panel.margin = '12 16 0 16';
|
|
536
|
+
}
|
|
537
|
+
else if (position === 'bottom') {
|
|
538
|
+
panel.verticalAlignment = 'bottom';
|
|
539
|
+
panel.margin = '0 16 12 16';
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
panel.verticalAlignment = 'middle';
|
|
543
|
+
panel.margin = 24;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch { }
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch { }
|
|
550
|
+
// Touch passthrough for toast; centered mode keeps the
|
|
551
|
+
// blocking modal so the dim backdrop is meaningful.
|
|
552
|
+
try {
|
|
553
|
+
refs.overlay.isUserInteractionEnabled = !isToast;
|
|
554
|
+
}
|
|
555
|
+
catch { }
|
|
556
|
+
const positionChanged = previousPosition !== position;
|
|
557
|
+
const justAppeared = visible && (!wasVisible || positionChanged);
|
|
558
|
+
const justDismissed = !visible && wasVisible;
|
|
559
|
+
if (justAppeared) {
|
|
560
|
+
refs.overlay.visibility = 'visible';
|
|
561
|
+
if (isToast && panel && typeof panel.animate === 'function') {
|
|
562
|
+
animateLivePanelIn(panel, position);
|
|
563
|
+
}
|
|
564
|
+
else if (panel) {
|
|
565
|
+
try {
|
|
566
|
+
panel.translateY = 0;
|
|
567
|
+
panel.opacity = 1;
|
|
568
|
+
}
|
|
569
|
+
catch { }
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else if (justDismissed) {
|
|
573
|
+
if (isToast && panel && typeof panel.animate === 'function') {
|
|
574
|
+
animateLivePanelOut(panel, previousPosition, () => {
|
|
575
|
+
try {
|
|
576
|
+
refs.overlay.visibility = 'collapse';
|
|
577
|
+
}
|
|
578
|
+
catch { }
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
refs.overlay.visibility = 'collapse';
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
refs.overlay.visibility = visible ? 'visible' : 'collapse';
|
|
587
|
+
}
|
|
588
|
+
if (typeof refs.wasVisible !== 'undefined')
|
|
589
|
+
refs.wasVisible = visible;
|
|
590
|
+
if (typeof refs.currentPosition !== 'undefined')
|
|
591
|
+
refs.currentPosition = position;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Slide-in animation for the in-tree toast panel.
|
|
595
|
+
*
|
|
596
|
+
* NativeScript's `View.animate({ translate, opacity, duration, curve })`
|
|
597
|
+
* is widely available across Core versions, so we don't depend on any
|
|
598
|
+
* specific curve enum being importable here. We use a moderate-to-snappy
|
|
599
|
+
* 320ms ease-out which feels close to a UIView spring without needing
|
|
600
|
+
* platform-specific APIs.
|
|
601
|
+
*/
|
|
602
|
+
function animateLivePanelIn(panel, position) {
|
|
603
|
+
if (!panel || typeof panel.animate !== 'function')
|
|
604
|
+
return;
|
|
605
|
+
try {
|
|
606
|
+
const startY = position === 'bottom' ? 80 : -80;
|
|
607
|
+
panel.translateY = startY;
|
|
608
|
+
panel.opacity = 0;
|
|
609
|
+
const result = panel.animate({
|
|
610
|
+
translate: { x: 0, y: 0 },
|
|
611
|
+
opacity: 1,
|
|
612
|
+
duration: 320,
|
|
613
|
+
curve: 'easeOut',
|
|
614
|
+
});
|
|
615
|
+
if (result && typeof result.catch === 'function') {
|
|
616
|
+
result.catch(() => {
|
|
617
|
+
try {
|
|
618
|
+
panel.translateY = 0;
|
|
619
|
+
panel.opacity = 1;
|
|
620
|
+
}
|
|
621
|
+
catch { }
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
try {
|
|
627
|
+
panel.translateY = 0;
|
|
628
|
+
panel.opacity = 1;
|
|
629
|
+
}
|
|
630
|
+
catch { }
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function animateLivePanelOut(panel, position, onComplete) {
|
|
634
|
+
if (!panel || typeof panel.animate !== 'function') {
|
|
635
|
+
onComplete();
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
try {
|
|
639
|
+
const targetY = position === 'bottom' ? 80 : -80;
|
|
640
|
+
const result = panel.animate({
|
|
641
|
+
translate: { x: 0, y: targetY },
|
|
642
|
+
opacity: 0,
|
|
643
|
+
duration: 220,
|
|
644
|
+
curve: 'easeIn',
|
|
645
|
+
});
|
|
646
|
+
const finish = () => {
|
|
647
|
+
try {
|
|
648
|
+
panel.translateY = 0;
|
|
649
|
+
panel.opacity = 1;
|
|
650
|
+
}
|
|
651
|
+
catch { }
|
|
652
|
+
onComplete();
|
|
653
|
+
};
|
|
654
|
+
if (result && typeof result.then === 'function') {
|
|
655
|
+
result.then(finish, finish);
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
finish();
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
catch {
|
|
662
|
+
onComplete();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
// pure helpers for iOS window promotion. Factored out so the layout
|
|
666
|
+
// math and window-level selection stay unit-testable without booting a
|
|
667
|
+
// simulator. See `dev-overlay.spec.ts`.
|
|
668
|
+
/**
|
|
669
|
+
* Returns the UIWindow level we use for the live/connection overlay. We lift
|
|
670
|
+
* above `UIWindowLevelAlert` so system alerts (and any app-presented modal)
|
|
671
|
+
* stack underneath. When the platform does not expose `UIWindowLevelAlert`
|
|
672
|
+
* we fall back to the documented constant value (2000).
|
|
673
|
+
*/
|
|
674
|
+
export function computeIosOverlayWindowLevel(baseAlert) {
|
|
675
|
+
if (typeof baseAlert === 'number' && Number.isFinite(baseAlert)) {
|
|
676
|
+
return baseAlert + 1;
|
|
677
|
+
}
|
|
678
|
+
return 2000 + 1;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Layout math for the live overlay when it runs inside its own UIWindow.
|
|
682
|
+
* Pure, deterministic and independent of UIKit so we can verify the rules
|
|
683
|
+
* (max panel width, position-aware placement, safe-area clamping, sane
|
|
684
|
+
* defaults) from tests.
|
|
685
|
+
*
|
|
686
|
+
* `position` controls where the panel sits vertically:
|
|
687
|
+
* - 'top': hugs `safeInsets.top + toastVerticalInset` so the chip
|
|
688
|
+
* sits just below the notch / Dynamic Island.
|
|
689
|
+
* - 'bottom': hugs `viewHeight - safeInsets.bottom - panelHeight -
|
|
690
|
+
* toastVerticalInset` so the chip sits just above the
|
|
691
|
+
* home indicator / nav bar.
|
|
692
|
+
* - 'center': original modal placement (vertically centered, clamped
|
|
693
|
+
* so it never crosses the top safe-area inset).
|
|
694
|
+
*/
|
|
695
|
+
export function computeIosOverlayLayout(input) {
|
|
696
|
+
const viewWidth = Math.max(0, Number(input.viewWidth) || 0);
|
|
697
|
+
const viewHeight = Math.max(0, Number(input.viewHeight) || 0);
|
|
698
|
+
const safeInsets = {
|
|
699
|
+
top: Math.max(0, Number(input.safeInsets?.top ?? 0) || 0),
|
|
700
|
+
bottom: Math.max(0, Number(input.safeInsets?.bottom ?? 0) || 0),
|
|
701
|
+
left: Math.max(0, Number(input.safeInsets?.left ?? 0) || 0),
|
|
702
|
+
right: Math.max(0, Number(input.safeInsets?.right ?? 0) || 0),
|
|
703
|
+
};
|
|
704
|
+
const titleHeight = Math.max(0, Number(input.titleHeight) || 0);
|
|
705
|
+
const statusHeight = Math.max(0, Number(input.statusHeight) || 0);
|
|
706
|
+
const horizontalMargin = Math.max(0, Number(input.horizontalMargin ?? 24));
|
|
707
|
+
const maxPanelWidth = Math.max(0, Number(input.maxPanelWidth ?? 340));
|
|
708
|
+
const panelPadding = Math.max(0, Number(input.panelPadding ?? 16));
|
|
709
|
+
const interLabelSpacing = Math.max(0, Number(input.interLabelSpacing ?? 10));
|
|
710
|
+
const minTopInset = Math.max(0, Number(input.minTopInset ?? 20));
|
|
711
|
+
// Default to 'center' on the pure function so the existing
|
|
712
|
+
// snapshot/layout tests remain stable; the runtime call site
|
|
713
|
+
// (layoutIosOverlayRefs) reads the configured position from
|
|
714
|
+
// `getHmrDevOverlayPosition()` and forwards it explicitly.
|
|
715
|
+
const position = input.position ?? 'center';
|
|
716
|
+
// Distance between the panel and the safe-area edge in toast
|
|
717
|
+
// modes. 8pt mirrors the typical iOS notification chip inset and
|
|
718
|
+
// keeps the chip from hugging the notch / home indicator.
|
|
719
|
+
const toastVerticalInset = Math.max(0, Number(input.toastVerticalInset ?? 8));
|
|
720
|
+
const available = Math.max(0, viewWidth - 2 * horizontalMargin - safeInsets.left - safeInsets.right);
|
|
721
|
+
const panelWidth = Math.min(maxPanelWidth, available);
|
|
722
|
+
const innerWidth = Math.max(0, panelWidth - 2 * panelPadding);
|
|
723
|
+
const spacing = titleHeight > 0 && statusHeight > 0 ? interLabelSpacing : 0;
|
|
724
|
+
const panelHeight = panelPadding * 2 + titleHeight + spacing + statusHeight;
|
|
725
|
+
const panelX = Math.max(0, (viewWidth - panelWidth) / 2);
|
|
726
|
+
let panelY;
|
|
727
|
+
if (position === 'top') {
|
|
728
|
+
// Pin to the top safe-area inset (just below notch / Dynamic
|
|
729
|
+
// Island). Clamp non-negative for fully-NaN input.
|
|
730
|
+
panelY = Math.max(0, safeInsets.top + toastVerticalInset);
|
|
731
|
+
}
|
|
732
|
+
else if (position === 'bottom') {
|
|
733
|
+
// Pin to the bottom safe-area inset (just above home indicator
|
|
734
|
+
// / nav bar). If the panel can't fit between the safe-area
|
|
735
|
+
// insets we fall back to the top safe-area edge so the chip is
|
|
736
|
+
// always visible (rather than getting clipped off-screen).
|
|
737
|
+
const desired = viewHeight - safeInsets.bottom - panelHeight - toastVerticalInset;
|
|
738
|
+
panelY = Math.max(safeInsets.top + minTopInset, desired);
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
// Center vertically, but never cross the top safe-area inset
|
|
742
|
+
// (notch/Dynamic Island). Original modal placement.
|
|
743
|
+
const centered = (viewHeight - panelHeight) / 2;
|
|
744
|
+
panelY = Math.max(safeInsets.top + minTopInset, centered);
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
backdrop: { x: 0, y: 0, width: viewWidth, height: viewHeight },
|
|
748
|
+
panel: { x: panelX, y: panelY, width: panelWidth, height: panelHeight },
|
|
749
|
+
title: { x: panelPadding, y: panelPadding, width: innerWidth, height: titleHeight },
|
|
750
|
+
status: {
|
|
751
|
+
x: panelPadding,
|
|
752
|
+
y: panelPadding + titleHeight + spacing,
|
|
753
|
+
width: innerWidth,
|
|
754
|
+
height: statusHeight,
|
|
755
|
+
},
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Returns the iOS UIKit symbols we rely on if we're running on an iOS runtime
|
|
760
|
+
* with the metadata bridge available. Returns null on Android, web, or in
|
|
761
|
+
* tests so callers can gracefully fall back to the in-tree overlay.
|
|
762
|
+
*/
|
|
763
|
+
function getIosOverlayHost() {
|
|
764
|
+
const g = getOverlayGlobal();
|
|
765
|
+
if (typeof g.UIWindow === 'undefined' || typeof g.UIApplication === 'undefined' || typeof g.UIViewController === 'undefined' || typeof g.UIView === 'undefined' || typeof g.UILabel === 'undefined' || typeof g.UIColor === 'undefined' || typeof g.UIFont === 'undefined' || typeof g.UIScreen === 'undefined') {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
UIWindow: g.UIWindow,
|
|
770
|
+
UIViewController: g.UIViewController,
|
|
771
|
+
UIView: g.UIView,
|
|
772
|
+
UILabel: g.UILabel,
|
|
773
|
+
UIColor: g.UIColor,
|
|
774
|
+
UIFont: g.UIFont,
|
|
775
|
+
UIApplication: g.UIApplication,
|
|
776
|
+
UIScreen: g.UIScreen,
|
|
777
|
+
UIWindowLevelAlert: typeof g.UIWindowLevelAlert === 'number' ? g.UIWindowLevelAlert : undefined,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Walks UIApplication.sharedApplication windows and returns the first active
|
|
782
|
+
* UIWindowScene we can locate. On iOS 13+ every UIWindow is attached to a
|
|
783
|
+
* scene, and we must initialise our overlay window the same way or the OS
|
|
784
|
+
* will silently refuse to render it. Returns null when no scene is found
|
|
785
|
+
* (older iOS versions or non-UI environments).
|
|
786
|
+
*/
|
|
787
|
+
function findActiveWindowScene(host) {
|
|
788
|
+
try {
|
|
789
|
+
const app = host.UIApplication.sharedApplication;
|
|
790
|
+
const windows = app?.windows;
|
|
791
|
+
if (!windows || typeof windows.count !== 'number')
|
|
792
|
+
return null;
|
|
793
|
+
for (let i = 0; i < windows.count; i++) {
|
|
794
|
+
const w = windows.objectAtIndex(i);
|
|
795
|
+
const scene = w && w.windowScene;
|
|
796
|
+
if (scene)
|
|
797
|
+
return scene;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
catch { }
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
function buildIosOverlayRefs(state) {
|
|
804
|
+
const host = getIosOverlayHost();
|
|
805
|
+
if (!host)
|
|
806
|
+
return null;
|
|
807
|
+
// Without a scene we can't build a modern UIWindow that actually renders.
|
|
808
|
+
// Fall back to the in-tree overlay rather than show nothing.
|
|
809
|
+
const scene = findActiveWindowScene(host);
|
|
810
|
+
if (!scene) {
|
|
811
|
+
if (state.verbose) {
|
|
812
|
+
console.info('[ns-hmr-overlay] no active UIWindowScene; skipping iOS overlay promotion');
|
|
813
|
+
}
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
const { UIWindow, UIViewController, UIView, UILabel, UIColor, UIFont } = host;
|
|
818
|
+
const window = UIWindow.alloc().initWithWindowScene(scene);
|
|
819
|
+
window.windowLevel = computeIosOverlayWindowLevel(host.UIWindowLevelAlert ?? null);
|
|
820
|
+
window.backgroundColor = UIColor.clearColor;
|
|
821
|
+
window.hidden = true;
|
|
822
|
+
const controller = UIViewController.new();
|
|
823
|
+
controller.view.backgroundColor = UIColor.clearColor;
|
|
824
|
+
window.rootViewController = controller;
|
|
825
|
+
// UIViewAutoresizing bit masks. We mirror the UIKit constants here to
|
|
826
|
+
// avoid depending on symbols the metadata bridge does not always
|
|
827
|
+
// expose as top-level globals.
|
|
828
|
+
const FLEXIBLE_LEFT_MARGIN = 1 << 0;
|
|
829
|
+
const FLEXIBLE_WIDTH = 1 << 1;
|
|
830
|
+
const FLEXIBLE_RIGHT_MARGIN = 1 << 2;
|
|
831
|
+
const FLEXIBLE_TOP_MARGIN = 1 << 3;
|
|
832
|
+
const FLEXIBLE_HEIGHT = 1 << 4;
|
|
833
|
+
const FLEXIBLE_BOTTOM_MARGIN = 1 << 5;
|
|
834
|
+
const backdrop = UIView.new();
|
|
835
|
+
backdrop.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0, 0, 0, 0.35);
|
|
836
|
+
backdrop.autoresizingMask = FLEXIBLE_WIDTH | FLEXIBLE_HEIGHT;
|
|
837
|
+
controller.view.addSubview(backdrop);
|
|
838
|
+
const panel = UIView.new();
|
|
839
|
+
panel.backgroundColor = UIColor.whiteColor;
|
|
840
|
+
panel.autoresizingMask = FLEXIBLE_LEFT_MARGIN | FLEXIBLE_RIGHT_MARGIN | FLEXIBLE_TOP_MARGIN | FLEXIBLE_BOTTOM_MARGIN;
|
|
841
|
+
try {
|
|
842
|
+
panel.layer.cornerRadius = 14;
|
|
843
|
+
panel.layer.masksToBounds = true;
|
|
844
|
+
}
|
|
845
|
+
catch { }
|
|
846
|
+
controller.view.addSubview(panel);
|
|
847
|
+
const titleLabel = UILabel.new();
|
|
848
|
+
titleLabel.numberOfLines = 0;
|
|
849
|
+
titleLabel.textAlignment = 1; // NSTextAlignmentCenter
|
|
850
|
+
titleLabel.font = UIFont.boldSystemFontOfSize(16);
|
|
851
|
+
titleLabel.textColor = UIColor.blackColor;
|
|
852
|
+
panel.addSubview(titleLabel);
|
|
853
|
+
const statusLabel = UILabel.new();
|
|
854
|
+
statusLabel.numberOfLines = 0;
|
|
855
|
+
statusLabel.textAlignment = 1;
|
|
856
|
+
statusLabel.font = UIFont.systemFontOfSize(13);
|
|
857
|
+
statusLabel.textColor = UIColor.darkGrayColor;
|
|
858
|
+
panel.addSubview(statusLabel);
|
|
859
|
+
// Subtle drop-shadow so the toast chip reads against light app
|
|
860
|
+
// content (white-on-white is invisible). The error / centered
|
|
861
|
+
// branches still get the dim backdrop, so the shadow is mostly
|
|
862
|
+
// a no-op for them — but it's a one-time setup.
|
|
863
|
+
try {
|
|
864
|
+
panel.layer.shadowColor = UIColor.blackColor.CGColor;
|
|
865
|
+
panel.layer.shadowOpacity = 0.18;
|
|
866
|
+
panel.layer.shadowRadius = 8;
|
|
867
|
+
panel.layer.shadowOffset = { width: 0, height: 2 };
|
|
868
|
+
panel.layer.masksToBounds = false;
|
|
869
|
+
}
|
|
870
|
+
catch { }
|
|
871
|
+
// `wasVisible` / `currentPosition` are mutated by
|
|
872
|
+
// applySnapshotToIosRefs when the snapshot triggers a slide-in
|
|
873
|
+
// or slide-out. They start in the "hidden" state so the very
|
|
874
|
+
// first visible snapshot animates in cleanly.
|
|
875
|
+
return {
|
|
876
|
+
window,
|
|
877
|
+
controller,
|
|
878
|
+
backdrop,
|
|
879
|
+
panel,
|
|
880
|
+
titleLabel,
|
|
881
|
+
statusLabel,
|
|
882
|
+
wasVisible: false,
|
|
883
|
+
currentPosition: getHmrDevOverlayPosition(),
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
catch (err) {
|
|
887
|
+
console.warn('[ns-hmr-overlay] iOS overlay construction failed:', err?.message || err);
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function ensureIosOverlayRefs(state) {
|
|
892
|
+
if (state.iosRefs)
|
|
893
|
+
return state.iosRefs;
|
|
894
|
+
if (state.iosBuildFailed)
|
|
895
|
+
return null;
|
|
896
|
+
const built = buildIosOverlayRefs(state);
|
|
897
|
+
if (built) {
|
|
898
|
+
state.iosRefs = built;
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
// Remember failure so we don't hammer construction on every snapshot
|
|
902
|
+
// update — the in-tree path will take over for this session.
|
|
903
|
+
state.iosBuildFailed = true;
|
|
904
|
+
}
|
|
905
|
+
return state.iosRefs;
|
|
906
|
+
}
|
|
907
|
+
function layoutIosOverlayRefs(refs, position) {
|
|
908
|
+
try {
|
|
909
|
+
const bounds = refs.controller.view.bounds;
|
|
910
|
+
const viewWidth = Number(bounds?.size?.width) || 0;
|
|
911
|
+
const viewHeight = Number(bounds?.size?.height) || 0;
|
|
912
|
+
const raw = refs.controller.view.safeAreaInsets;
|
|
913
|
+
const safeInsets = raw
|
|
914
|
+
? {
|
|
915
|
+
top: Number(raw.top) || 0,
|
|
916
|
+
bottom: Number(raw.bottom) || 0,
|
|
917
|
+
left: Number(raw.left) || 0,
|
|
918
|
+
right: Number(raw.right) || 0,
|
|
919
|
+
}
|
|
920
|
+
: { top: 0, bottom: 0, left: 0, right: 0 };
|
|
921
|
+
// Ask UIKit what the labels want given the panel inner width. We use a
|
|
922
|
+
// generous height bound so nothing clips on long reconnect strings.
|
|
923
|
+
const panelPadding = 16;
|
|
924
|
+
const horizontalMargin = 24;
|
|
925
|
+
const maxPanelWidth = 340;
|
|
926
|
+
const innerWidth = Math.max(0, Math.min(maxPanelWidth, viewWidth - 2 * horizontalMargin - safeInsets.left - safeInsets.right) - 2 * panelPadding);
|
|
927
|
+
const titleFit = refs.titleLabel.sizeThatFits({ width: innerWidth, height: 10000 }) || { height: 0 };
|
|
928
|
+
const statusFit = refs.statusLabel.sizeThatFits({ width: innerWidth, height: 10000 }) || { height: 0 };
|
|
929
|
+
const layout = computeIosOverlayLayout({
|
|
930
|
+
viewWidth,
|
|
931
|
+
viewHeight,
|
|
932
|
+
safeInsets,
|
|
933
|
+
titleHeight: Number(titleFit.height) || 0,
|
|
934
|
+
statusHeight: Number(statusFit.height) || 0,
|
|
935
|
+
maxPanelWidth,
|
|
936
|
+
horizontalMargin,
|
|
937
|
+
panelPadding,
|
|
938
|
+
position,
|
|
939
|
+
});
|
|
940
|
+
const toCgRect = (rect) => ({
|
|
941
|
+
origin: { x: rect.x, y: rect.y },
|
|
942
|
+
size: { width: rect.width, height: rect.height },
|
|
943
|
+
});
|
|
944
|
+
refs.backdrop.frame = toCgRect(layout.backdrop);
|
|
945
|
+
refs.panel.frame = toCgRect(layout.panel);
|
|
946
|
+
refs.titleLabel.frame = toCgRect(layout.title);
|
|
947
|
+
refs.statusLabel.frame = toCgRect(layout.status);
|
|
948
|
+
return layout;
|
|
949
|
+
}
|
|
950
|
+
catch (err) {
|
|
951
|
+
console.warn('[ns-hmr-overlay] iOS overlay layout failed:', err?.message || err);
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Slide-in animation for the iOS toast panel. Off-screen start frame
|
|
957
|
+
* lives just above (top) or below (bottom) the visible area; the panel
|
|
958
|
+
* snaps to its target frame with a spring so the motion feels physical
|
|
959
|
+
* without the heavy "settle" overshoot of a hard spring (damping 0.85
|
|
960
|
+
* lands quickly with a small overshoot).
|
|
961
|
+
*/
|
|
962
|
+
function animateIosPanelIn(refs, position, layout) {
|
|
963
|
+
const g = getOverlayGlobal();
|
|
964
|
+
const UIView = g?.UIView;
|
|
965
|
+
if (!UIView)
|
|
966
|
+
return;
|
|
967
|
+
try {
|
|
968
|
+
const targetFrame = {
|
|
969
|
+
origin: { x: layout.panel.x, y: layout.panel.y },
|
|
970
|
+
size: { width: layout.panel.width, height: layout.panel.height },
|
|
971
|
+
};
|
|
972
|
+
// Off-screen start: distance includes a small fudge so the
|
|
973
|
+
// shadow blur tail isn't visible at t=0.
|
|
974
|
+
const startY = position === 'bottom' ? layout.backdrop.height + 24 : -(layout.panel.height + 24);
|
|
975
|
+
refs.panel.frame = {
|
|
976
|
+
origin: { x: layout.panel.x, y: startY },
|
|
977
|
+
size: { width: layout.panel.width, height: layout.panel.height },
|
|
978
|
+
};
|
|
979
|
+
refs.panel.alpha = 0;
|
|
980
|
+
try {
|
|
981
|
+
if (typeof UIView.animateWithDurationDelayUsingSpringWithDampingInitialSpringVelocityOptionsAnimationsCompletion === 'function') {
|
|
982
|
+
UIView.animateWithDurationDelayUsingSpringWithDampingInitialSpringVelocityOptionsAnimationsCompletion(0.42, 0, 0.85, 0.7, 0, () => {
|
|
983
|
+
refs.panel.frame = targetFrame;
|
|
984
|
+
refs.panel.alpha = 1;
|
|
985
|
+
}, null);
|
|
986
|
+
}
|
|
987
|
+
else if (typeof UIView.animateWithDurationAnimations === 'function') {
|
|
988
|
+
UIView.animateWithDurationAnimations(0.32, () => {
|
|
989
|
+
refs.panel.frame = targetFrame;
|
|
990
|
+
refs.panel.alpha = 1;
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
refs.panel.frame = targetFrame;
|
|
995
|
+
refs.panel.alpha = 1;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
catch {
|
|
999
|
+
refs.panel.frame = targetFrame;
|
|
1000
|
+
refs.panel.alpha = 1;
|
|
604
1001
|
}
|
|
605
1002
|
}
|
|
606
1003
|
catch { }
|
|
607
1004
|
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Slide-out animation for the iOS toast panel. Mirrors animateIosPanelIn:
|
|
1007
|
+
* the panel travels to the nearest off-screen edge while fading out so
|
|
1008
|
+
* the dismissal still feels intentional even on fast HMR cycles.
|
|
1009
|
+
*/
|
|
1010
|
+
function animateIosPanelOut(refs, position, onComplete) {
|
|
1011
|
+
const g = getOverlayGlobal();
|
|
1012
|
+
const UIView = g?.UIView;
|
|
1013
|
+
const currentFrame = refs.panel?.frame;
|
|
1014
|
+
if (!UIView || !currentFrame) {
|
|
1015
|
+
onComplete();
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
try {
|
|
1019
|
+
const bounds = refs.controller?.view?.bounds;
|
|
1020
|
+
const viewHeight = Number(bounds?.size?.height) || 0;
|
|
1021
|
+
const targetY = position === 'bottom' ? viewHeight + 24 : -(Number(currentFrame.size?.height) + 24);
|
|
1022
|
+
const startFrame = currentFrame;
|
|
1023
|
+
const targetFrame = {
|
|
1024
|
+
origin: { x: Number(startFrame.origin?.x) || 0, y: targetY },
|
|
1025
|
+
size: startFrame.size,
|
|
1026
|
+
};
|
|
1027
|
+
try {
|
|
1028
|
+
if (typeof UIView.animateWithDurationDelayOptionsAnimationsCompletion === 'function') {
|
|
1029
|
+
// UIViewAnimationOptionCurveEaseIn = 1 << 16 — accelerate
|
|
1030
|
+
// out so the dismissal doesn't drag on screen.
|
|
1031
|
+
UIView.animateWithDurationDelayOptionsAnimationsCompletion(0.22, 0, 1 << 16, () => {
|
|
1032
|
+
refs.panel.frame = targetFrame;
|
|
1033
|
+
refs.panel.alpha = 0;
|
|
1034
|
+
}, () => onComplete());
|
|
1035
|
+
}
|
|
1036
|
+
else if (typeof UIView.animateWithDurationAnimationsCompletion === 'function') {
|
|
1037
|
+
UIView.animateWithDurationAnimationsCompletion(0.22, () => {
|
|
1038
|
+
refs.panel.frame = targetFrame;
|
|
1039
|
+
refs.panel.alpha = 0;
|
|
1040
|
+
}, () => onComplete());
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
refs.panel.alpha = 0;
|
|
1044
|
+
onComplete();
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
catch {
|
|
1048
|
+
refs.panel.alpha = 0;
|
|
1049
|
+
onComplete();
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
catch {
|
|
1053
|
+
onComplete();
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function applySnapshotToIosRefs(refs, snapshot) {
|
|
1057
|
+
if (!refs)
|
|
1058
|
+
return false;
|
|
1059
|
+
try {
|
|
1060
|
+
// 'update' mode rides the same dedicated UIWindow as
|
|
1061
|
+
// 'connection' so the HMR apply overlay always stacks above
|
|
1062
|
+
// modals/sheets/system alerts. The window is constructed
|
|
1063
|
+
// lazily (ensureIosOverlayRefs) and reused for the lifetime of
|
|
1064
|
+
// the dev session.
|
|
1065
|
+
const visible = snapshot.visible && (snapshot.mode === 'connection' || snapshot.mode === 'update');
|
|
1066
|
+
const wasVisible = !!refs.wasVisible;
|
|
1067
|
+
const position = getHmrDevOverlayPosition();
|
|
1068
|
+
const previousPosition = refs.currentPosition;
|
|
1069
|
+
const isToast = position !== 'center';
|
|
1070
|
+
// Touches pass through the overlay window in toast mode so
|
|
1071
|
+
// the user can keep tapping the app while the HMR chip is
|
|
1072
|
+
// shown. In centered mode we keep the blocking
|
|
1073
|
+
// behaviour (the dim backdrop is itself a hint to wait).
|
|
1074
|
+
try {
|
|
1075
|
+
refs.window.userInteractionEnabled = !isToast;
|
|
1076
|
+
}
|
|
1077
|
+
catch { }
|
|
1078
|
+
if (!visible) {
|
|
1079
|
+
// Animate out before hiding the window so the dismissal
|
|
1080
|
+
// has a discoverable motion. Only animate when previously
|
|
1081
|
+
// visible and in toast mode — centered modal hides instantly.
|
|
1082
|
+
if (wasVisible && isToast) {
|
|
1083
|
+
animateIosPanelOut(refs, previousPosition, () => {
|
|
1084
|
+
try {
|
|
1085
|
+
refs.window.hidden = true;
|
|
1086
|
+
}
|
|
1087
|
+
catch { }
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
refs.window.hidden = true;
|
|
1092
|
+
}
|
|
1093
|
+
refs.wasVisible = false;
|
|
1094
|
+
refs.currentPosition = position;
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
refs.window.hidden = false;
|
|
1098
|
+
refs.titleLabel.text = snapshot.title || '';
|
|
1099
|
+
refs.statusLabel.text = formatStatusText(snapshot);
|
|
1100
|
+
const host = getIosOverlayHost();
|
|
1101
|
+
if (host) {
|
|
1102
|
+
const { UIColor } = host;
|
|
1103
|
+
const isError = snapshot.tone === 'error';
|
|
1104
|
+
const isSuccess = snapshot.tone === 'success';
|
|
1105
|
+
try {
|
|
1106
|
+
if (isError) {
|
|
1107
|
+
// Red panel + dark red text (existing UX).
|
|
1108
|
+
refs.panel.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(1, 0.96, 0.96, 1);
|
|
1109
|
+
refs.titleLabel.textColor = UIColor.colorWithRedGreenBlueAlpha(0.7, 0.1, 0.06, 1);
|
|
1110
|
+
refs.statusLabel.textColor = UIColor.colorWithRedGreenBlueAlpha(0.7, 0.1, 0.06, 0.9);
|
|
1111
|
+
}
|
|
1112
|
+
else if (isSuccess) {
|
|
1113
|
+
// Slightly more saturated green panel + dark-green
|
|
1114
|
+
// text. The previous 0.94/0.99/0.95 background was
|
|
1115
|
+
// nearly indistinguishable from white on most
|
|
1116
|
+
// devices; this bump keeps long detail strings
|
|
1117
|
+
// readable while making the apply event obviously
|
|
1118
|
+
// "happening right now".
|
|
1119
|
+
refs.panel.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0.9, 0.97, 0.91, 1);
|
|
1120
|
+
refs.titleLabel.textColor = UIColor.colorWithRedGreenBlueAlpha(0.05, 0.43, 0.18, 1);
|
|
1121
|
+
refs.statusLabel.textColor = UIColor.colorWithRedGreenBlueAlpha(0.05, 0.43, 0.18, 1);
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
// Default (info / warn) — existing connection look.
|
|
1125
|
+
refs.panel.backgroundColor = UIColor.whiteColor;
|
|
1126
|
+
refs.titleLabel.textColor = UIColor.blackColor;
|
|
1127
|
+
refs.statusLabel.textColor = UIColor.darkGrayColor;
|
|
1128
|
+
}
|
|
1129
|
+
// Backdrop dims only in centered mode; toast mode keeps
|
|
1130
|
+
// the rest of the app fully visible/usable. Errors get
|
|
1131
|
+
// a slightly stronger dim in centered mode because the
|
|
1132
|
+
// user MUST notice them.
|
|
1133
|
+
if (isToast) {
|
|
1134
|
+
refs.backdrop.backgroundColor = UIColor.clearColor;
|
|
1135
|
+
}
|
|
1136
|
+
else if (isError) {
|
|
1137
|
+
refs.backdrop.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0, 0, 0, 0.35);
|
|
1138
|
+
}
|
|
1139
|
+
else if (isSuccess) {
|
|
1140
|
+
refs.backdrop.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0, 0.15, 0.05, 0.28);
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
refs.backdrop.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0, 0, 0, 0.35);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
catch { }
|
|
1147
|
+
}
|
|
1148
|
+
const layout = layoutIosOverlayRefs(refs, position);
|
|
1149
|
+
// Slide-in animation only fires on the actual hidden→visible
|
|
1150
|
+
// transition (or on a position swap — e.g. dev toggling top
|
|
1151
|
+
// to bottom mid-cycle). Subsequent updates within the same
|
|
1152
|
+
// visible cycle just refresh text/colours without re-animating.
|
|
1153
|
+
const positionChanged = previousPosition !== position;
|
|
1154
|
+
const justAppeared = !wasVisible || positionChanged;
|
|
1155
|
+
if (justAppeared && isToast && layout) {
|
|
1156
|
+
animateIosPanelIn(refs, position, layout);
|
|
1157
|
+
}
|
|
1158
|
+
else if (justAppeared && !isToast) {
|
|
1159
|
+
// Centered modal: ensure alpha is reset to 1 in case a
|
|
1160
|
+
// previous toast-mode dismissal left it at 0.
|
|
1161
|
+
try {
|
|
1162
|
+
refs.panel.alpha = 1;
|
|
1163
|
+
}
|
|
1164
|
+
catch { }
|
|
1165
|
+
}
|
|
1166
|
+
refs.wasVisible = true;
|
|
1167
|
+
refs.currentPosition = position;
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
catch (err) {
|
|
1171
|
+
console.warn('[ns-hmr-overlay] iOS overlay apply failed:', err?.message || err);
|
|
1172
|
+
return false;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
608
1175
|
function applyRuntimeSnapshot(snapshot) {
|
|
609
1176
|
const state = getRuntimeState();
|
|
610
1177
|
state.snapshot = snapshot;
|
|
@@ -613,15 +1180,108 @@ function applyRuntimeSnapshot(snapshot) {
|
|
|
613
1180
|
updateBootStatusLabel(snapshot);
|
|
614
1181
|
}
|
|
615
1182
|
applySnapshotToBootRefs(state.bootRefs, snapshot);
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
1183
|
+
// prefer the dedicated UIWindow
|
|
1184
|
+
// path so the live/update overlays always stack on top of modals,
|
|
1185
|
+
// sheets, and other windows. Fall back to the in-tree overlay when
|
|
1186
|
+
// iOS APIs aren't available (Android, tests, or when scene
|
|
1187
|
+
// construction fails).
|
|
1188
|
+
let handledByIos = false;
|
|
1189
|
+
// Both 'connection' and 'update' use the small-panel surface
|
|
1190
|
+
// (UIWindow on iOS, in-tree overlay everywhere else). 'boot' uses
|
|
1191
|
+
// the placeholder root via applySnapshotToBootRefs above; 'hidden'
|
|
1192
|
+
// hides everything.
|
|
1193
|
+
const wantsOverlay = snapshot.visible && (snapshot.mode === 'connection' || snapshot.mode === 'update');
|
|
1194
|
+
if (getIosOverlayHost()) {
|
|
1195
|
+
if (wantsOverlay) {
|
|
1196
|
+
const iosRefs = ensureIosOverlayRefs(state);
|
|
1197
|
+
handledByIos = applySnapshotToIosRefs(iosRefs, snapshot);
|
|
1198
|
+
}
|
|
1199
|
+
else if (state.iosRefs) {
|
|
1200
|
+
handledByIos = applySnapshotToIosRefs(state.iosRefs, snapshot);
|
|
1201
|
+
}
|
|
619
1202
|
}
|
|
620
|
-
|
|
621
|
-
|
|
1203
|
+
if (!handledByIos) {
|
|
1204
|
+
if (wantsOverlay) {
|
|
1205
|
+
const liveRefs = ensureLiveOverlayRefs(snapshot);
|
|
1206
|
+
applySnapshotToLiveRefs(liveRefs, snapshot);
|
|
1207
|
+
}
|
|
1208
|
+
else {
|
|
1209
|
+
applySnapshotToLiveRefs(state.liveRefs, snapshot);
|
|
1210
|
+
}
|
|
622
1211
|
}
|
|
623
1212
|
return state.snapshot;
|
|
624
1213
|
}
|
|
1214
|
+
// How long the 'complete' frame stays on screen before we auto-hide.
|
|
1215
|
+
// The original 350ms was too tight: many HMR cycles complete in
|
|
1216
|
+
// 50–250ms, so the *total* overlay lifetime (received → complete +
|
|
1217
|
+
// 350ms) was often under 500ms, which is faster than the human eye
|
|
1218
|
+
// can comfortably register. 600ms gives the user time to read the
|
|
1219
|
+
// "Total Xms" line and confirm visually that something happened.
|
|
1220
|
+
const UPDATE_AUTO_HIDE_MS = 600;
|
|
1221
|
+
// Minimum perceptible duration for an entire update overlay cycle
|
|
1222
|
+
// (from 'received' to hide). If the cycle finished in 50ms (e.g., a
|
|
1223
|
+
// tiny HTML edit on a warm cache), we still hold for ~MIN_VISIBLE_MS
|
|
1224
|
+
// total before hiding so the overlay is actually seen. Combined with
|
|
1225
|
+
// UPDATE_AUTO_HIDE_MS, the *effective* hold-after-complete =
|
|
1226
|
+
// max(UPDATE_AUTO_HIDE_MS, MIN_VISIBLE_MS - elapsed-since-received).
|
|
1227
|
+
const UPDATE_MIN_VISIBLE_MS = 800;
|
|
1228
|
+
function clearUpdateAutoHideTimer(state) {
|
|
1229
|
+
if (state.updateAutoHideTimer) {
|
|
1230
|
+
try {
|
|
1231
|
+
clearTimeout(state.updateAutoHideTimer);
|
|
1232
|
+
}
|
|
1233
|
+
catch { }
|
|
1234
|
+
state.updateAutoHideTimer = null;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
function scheduleUpdateAutoHide(state) {
|
|
1238
|
+
clearUpdateAutoHideTimer(state);
|
|
1239
|
+
// Compute how much longer we need to hold the overlay so that the
|
|
1240
|
+
// total cycle visibility is at least UPDATE_MIN_VISIBLE_MS. For
|
|
1241
|
+
// fast cycles (50ms reboot) this stretches the hide; for slow
|
|
1242
|
+
// cycles (>UPDATE_MIN_VISIBLE_MS) it falls back to the standard
|
|
1243
|
+
// UPDATE_AUTO_HIDE_MS so we don't truncate the celebratory hold.
|
|
1244
|
+
const startedAt = state.updateCycleStartedAt || 0;
|
|
1245
|
+
const elapsed = startedAt > 0 ? Math.max(0, Date.now() - startedAt) : 0;
|
|
1246
|
+
const minRemainder = elapsed > 0 ? Math.max(0, UPDATE_MIN_VISIBLE_MS - elapsed) : UPDATE_MIN_VISIBLE_MS;
|
|
1247
|
+
const holdMs = Math.max(UPDATE_AUTO_HIDE_MS, minRemainder);
|
|
1248
|
+
try {
|
|
1249
|
+
state.updateAutoHideTimer = setTimeout(() => {
|
|
1250
|
+
state.updateAutoHideTimer = null;
|
|
1251
|
+
// Critical: only auto-hide if we're still on the 'complete'
|
|
1252
|
+
// frame. If a new HMR cycle has rotated the snapshot back
|
|
1253
|
+
// to 'update' / 'received' (e.g., user saved twice in
|
|
1254
|
+
// quick succession), the new cycle owns the overlay and
|
|
1255
|
+
// our timer must not steal it.
|
|
1256
|
+
const current = state.snapshot;
|
|
1257
|
+
if (current.mode === 'update' && current.tone === 'success' && current.progress === 100) {
|
|
1258
|
+
state.updateCycleStartedAt = 0;
|
|
1259
|
+
applyRuntimeSnapshot({ ...DEFAULT_SNAPSHOT });
|
|
1260
|
+
}
|
|
1261
|
+
}, holdMs);
|
|
1262
|
+
}
|
|
1263
|
+
catch {
|
|
1264
|
+
// setTimeout missing (extremely rare; some test envs). Fall
|
|
1265
|
+
// back to immediate hide so we never leave the overlay visible
|
|
1266
|
+
// forever after a 'complete'.
|
|
1267
|
+
state.updateCycleStartedAt = 0;
|
|
1268
|
+
applyRuntimeSnapshot({ ...DEFAULT_SNAPSHOT });
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
function logUpdateStageTransition(state, stage, info) {
|
|
1272
|
+
if (!state.verbose)
|
|
1273
|
+
return;
|
|
1274
|
+
try {
|
|
1275
|
+
const detail = info?.detail || '';
|
|
1276
|
+
const progress = typeof info?.progress === 'number' ? info.progress : null;
|
|
1277
|
+
const progressTag = progress !== null ? ` (${Math.round(progress)}%)` : '';
|
|
1278
|
+
// Single-line breadcrumb so a developer can correlate
|
|
1279
|
+
// overlay frames with the [ns-hmr][angular] timing log when
|
|
1280
|
+
// debugging "I don't see the overlay" reports.
|
|
1281
|
+
console.info(`[ns-hmr-overlay] update stage=${stage}${progressTag}${detail ? ` detail=${detail}` : ''}`);
|
|
1282
|
+
}
|
|
1283
|
+
catch { }
|
|
1284
|
+
}
|
|
625
1285
|
function createOverlayApi() {
|
|
626
1286
|
return {
|
|
627
1287
|
ensureBootPage(verbose) {
|
|
@@ -637,12 +1297,74 @@ function createOverlayApi() {
|
|
|
637
1297
|
return state.bootRefs?.page || null;
|
|
638
1298
|
},
|
|
639
1299
|
setBootStage(stage, info) {
|
|
640
|
-
|
|
1300
|
+
// A boot transition cancels any pending HMR auto-hide so
|
|
1301
|
+
// the boot phase always wins.
|
|
1302
|
+
const state = getRuntimeState();
|
|
1303
|
+
clearUpdateAutoHideTimer(state);
|
|
1304
|
+
state.updateCycleStartedAt = 0;
|
|
1305
|
+
const next = createBootOverlaySnapshot(stage, info);
|
|
1306
|
+
// Monotonic boot-progress ratchet: boot stages can fire out of
|
|
1307
|
+
// order across boot paths (native `__nsStartDevSession` vs the
|
|
1308
|
+
// http-bootloader fallback) and individual bases were tuned
|
|
1309
|
+
// independently, so clamp boot→boot transitions to never go
|
|
1310
|
+
// backwards. Non-boot snapshots (error/ready) bypass — they
|
|
1311
|
+
// genuinely want to reset the visual.
|
|
1312
|
+
if (next.mode === 'boot' && state.snapshot.mode === 'boot' && typeof next.progress === 'number' && typeof state.snapshot.progress === 'number' && next.progress < state.snapshot.progress) {
|
|
1313
|
+
next.progress = state.snapshot.progress;
|
|
1314
|
+
}
|
|
1315
|
+
return applyRuntimeSnapshot(next);
|
|
641
1316
|
},
|
|
642
1317
|
setConnectionStage(stage, info) {
|
|
1318
|
+
const state = getRuntimeState();
|
|
1319
|
+
clearUpdateAutoHideTimer(state);
|
|
1320
|
+
state.updateCycleStartedAt = 0;
|
|
643
1321
|
return applyRuntimeSnapshot(createConnectionOverlaySnapshot(stage, info));
|
|
644
1322
|
},
|
|
1323
|
+
setUpdateStage(stage, info) {
|
|
1324
|
+
const state = getRuntimeState();
|
|
1325
|
+
// Each new in-progress stage cancels any pending auto-hide
|
|
1326
|
+
// from a previous cycle. Without this, two saves in quick
|
|
1327
|
+
// succession could see cycle-2's progress overlay yanked
|
|
1328
|
+
// off by cycle-1's already-scheduled hide.
|
|
1329
|
+
clearUpdateAutoHideTimer(state);
|
|
1330
|
+
// Stamp the cycle start on 'received', but distinguish
|
|
1331
|
+
// between two cases:
|
|
1332
|
+
//
|
|
1333
|
+
// (a) Re-assertion of the SAME cycle (e.g., the server
|
|
1334
|
+
// emits both `ns:hmr-pending` AND `ns:angular-update`,
|
|
1335
|
+
// both of which call `setUpdateStage('received')`).
|
|
1336
|
+
// We must PRESERVE the original timestamp so the
|
|
1337
|
+
// minimum-visible-window math measures the FIRST
|
|
1338
|
+
// 'received' the user actually saw.
|
|
1339
|
+
//
|
|
1340
|
+
// (b) Genuinely-new cycle starting either from a hidden
|
|
1341
|
+
// overlay OR while the previous cycle is still on
|
|
1342
|
+
// its 'complete' frame (pre auto-hide). In both
|
|
1343
|
+
// sub-cases we MUST stamp a fresh start so the
|
|
1344
|
+
// new cycle's auto-hide math is sane.
|
|
1345
|
+
//
|
|
1346
|
+
// We treat the previous snapshot as "in-progress for the
|
|
1347
|
+
// same cycle" iff mode==='update' AND progress!==100.
|
|
1348
|
+
// 'complete' frames are a sign that the cycle finished;
|
|
1349
|
+
// any subsequent 'received' is a NEW cycle.
|
|
1350
|
+
if (stage === 'received') {
|
|
1351
|
+
const prev = state.snapshot;
|
|
1352
|
+
const isMidCycleReassertion = prev.mode === 'update' && prev.progress !== 100;
|
|
1353
|
+
if (!isMidCycleReassertion) {
|
|
1354
|
+
state.updateCycleStartedAt = Date.now();
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
logUpdateStageTransition(state, stage, info);
|
|
1358
|
+
const snapshot = applyRuntimeSnapshot(createUpdateOverlaySnapshot(stage, info));
|
|
1359
|
+
if (stage === 'complete') {
|
|
1360
|
+
scheduleUpdateAutoHide(state);
|
|
1361
|
+
}
|
|
1362
|
+
return snapshot;
|
|
1363
|
+
},
|
|
645
1364
|
hide() {
|
|
1365
|
+
const state = getRuntimeState();
|
|
1366
|
+
clearUpdateAutoHideTimer(state);
|
|
1367
|
+
state.updateCycleStartedAt = 0;
|
|
646
1368
|
applyRuntimeSnapshot({ ...DEFAULT_SNAPSHOT });
|
|
647
1369
|
},
|
|
648
1370
|
getSnapshot() {
|
|
@@ -668,6 +1390,14 @@ export function setHmrBootStage(stage, info) {
|
|
|
668
1390
|
export function setHmrConnectionStage(stage, info) {
|
|
669
1391
|
return ensureHmrDevOverlayRuntimeInstalled().setConnectionStage(stage, info);
|
|
670
1392
|
}
|
|
1393
|
+
// Public entry point for driving the HMR-applying overlay. Callers
|
|
1394
|
+
// walk through stages (received → evicting → reimporting → rebooting
|
|
1395
|
+
// → complete); 'complete' auto-hides after a short interval.
|
|
1396
|
+
// Soft-fails (no-op) if the runtime overlay was never installed
|
|
1397
|
+
// (e.g., production builds, test environments).
|
|
1398
|
+
export function setHmrUpdateStage(stage, info) {
|
|
1399
|
+
return ensureHmrDevOverlayRuntimeInstalled().setUpdateStage(stage, info);
|
|
1400
|
+
}
|
|
671
1401
|
export function hideHmrDevOverlay(reason) {
|
|
672
1402
|
void reason;
|
|
673
1403
|
ensureHmrDevOverlayRuntimeInstalled().hide(reason);
|