@nativescript/vite 8.0.0-alpha.4 → 8.0.0-alpha.40

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 (410) hide show
  1. package/README.md +51 -11
  2. package/bin/cli.cjs +8 -14
  3. package/configuration/angular.d.ts +34 -1
  4. package/configuration/angular.js +376 -165
  5. package/configuration/angular.js.map +1 -1
  6. package/configuration/base.js +256 -30
  7. package/configuration/base.js.map +1 -1
  8. package/configuration/javascript.js +12 -90
  9. package/configuration/javascript.js.map +1 -1
  10. package/configuration/solid.js +33 -4
  11. package/configuration/solid.js.map +1 -1
  12. package/configuration/typescript.js +10 -90
  13. package/configuration/typescript.js.map +1 -1
  14. package/helpers/app-components.d.ts +2 -1
  15. package/helpers/app-components.js.map +1 -1
  16. package/helpers/app-css-state.d.ts +8 -0
  17. package/helpers/app-css-state.js +8 -0
  18. package/helpers/app-css-state.js.map +1 -0
  19. package/helpers/bundler-context.d.ts +11 -0
  20. package/helpers/bundler-context.js +71 -0
  21. package/helpers/bundler-context.js.map +1 -0
  22. package/helpers/config-as-json.js +10 -0
  23. package/helpers/config-as-json.js.map +1 -1
  24. package/helpers/css-platform-plugin.d.ts +14 -0
  25. package/helpers/css-platform-plugin.js +43 -25
  26. package/helpers/css-platform-plugin.js.map +1 -1
  27. package/helpers/dev-host.d.ts +360 -0
  28. package/helpers/dev-host.js +692 -0
  29. package/helpers/dev-host.js.map +1 -0
  30. package/helpers/dynamic-import-plugin.js +1 -1
  31. package/helpers/dynamic-import-plugin.js.map +1 -1
  32. package/helpers/esbuild-platform-resolver.js +4 -1
  33. package/helpers/esbuild-platform-resolver.js.map +1 -1
  34. package/helpers/external-configs.d.ts +10 -12
  35. package/helpers/external-configs.js +54 -35
  36. package/helpers/external-configs.js.map +1 -1
  37. package/helpers/global-defines.d.ts +128 -0
  38. package/helpers/global-defines.js +174 -11
  39. package/helpers/global-defines.js.map +1 -1
  40. package/helpers/hmr-scope.d.ts +26 -0
  41. package/helpers/hmr-scope.js +67 -0
  42. package/helpers/hmr-scope.js.map +1 -0
  43. package/helpers/init.js +0 -18
  44. package/helpers/init.js.map +1 -1
  45. package/helpers/logging.d.ts +1 -0
  46. package/helpers/logging.js +65 -4
  47. package/helpers/logging.js.map +1 -1
  48. package/helpers/main-entry.d.ts +3 -1
  49. package/helpers/main-entry.js +444 -50
  50. package/helpers/main-entry.js.map +1 -1
  51. package/helpers/nativeclass-esbuild-plugin.d.ts +2 -1
  52. package/helpers/nativeclass-esbuild-plugin.js.map +1 -1
  53. package/helpers/nativeclass-transform.js +5 -6
  54. package/helpers/nativeclass-transform.js.map +1 -1
  55. package/helpers/nativeclass-transformer-plugin.d.ts +9 -2
  56. package/helpers/nativeclass-transformer-plugin.js +157 -14
  57. package/helpers/nativeclass-transformer-plugin.js.map +1 -1
  58. package/helpers/nativescript-package-resolver.js +10 -62
  59. package/helpers/nativescript-package-resolver.js.map +1 -1
  60. package/helpers/normalize-id.d.ts +42 -0
  61. package/helpers/normalize-id.js +60 -0
  62. package/helpers/normalize-id.js.map +1 -0
  63. package/helpers/ns-core-url.d.ts +106 -0
  64. package/helpers/ns-core-url.js +225 -0
  65. package/helpers/ns-core-url.js.map +1 -0
  66. package/helpers/optimize-deps.d.ts +16 -0
  67. package/helpers/optimize-deps.js +17 -0
  68. package/helpers/optimize-deps.js.map +1 -0
  69. package/helpers/package-platform-aliases.js +34 -49
  70. package/helpers/package-platform-aliases.js.map +1 -1
  71. package/helpers/platform-types.d.ts +2 -0
  72. package/helpers/platform-types.js +2 -0
  73. package/helpers/platform-types.js.map +1 -0
  74. package/helpers/postcss-platform-config.d.ts +17 -1
  75. package/helpers/postcss-platform-config.js +20 -37
  76. package/helpers/postcss-platform-config.js.map +1 -1
  77. package/helpers/project.d.ts +35 -0
  78. package/helpers/project.js +120 -2
  79. package/helpers/project.js.map +1 -1
  80. package/helpers/resolve-main-field-platform.d.ts +20 -0
  81. package/helpers/resolve-main-field-platform.js +49 -0
  82. package/helpers/resolve-main-field-platform.js.map +1 -0
  83. package/helpers/resolver.js +17 -2
  84. package/helpers/resolver.js.map +1 -1
  85. package/helpers/theme-core-plugins.js +1 -1
  86. package/helpers/theme-core-plugins.js.map +1 -1
  87. package/helpers/ts-config-paths.d.ts +14 -0
  88. package/helpers/ts-config-paths.js +90 -9
  89. package/helpers/ts-config-paths.js.map +1 -1
  90. package/helpers/typescript-check.d.ts +2 -1
  91. package/helpers/typescript-check.js.map +1 -1
  92. package/helpers/ui-registration.d.ts +21 -0
  93. package/helpers/ui-registration.js +156 -0
  94. package/helpers/ui-registration.js.map +1 -0
  95. package/helpers/utils.js +1 -2
  96. package/helpers/utils.js.map +1 -1
  97. package/helpers/workers.d.ts +20 -19
  98. package/helpers/workers.js +624 -4
  99. package/helpers/workers.js.map +1 -1
  100. package/hmr/client/css-handler.d.ts +2 -1
  101. package/hmr/client/css-handler.js +50 -26
  102. package/hmr/client/css-handler.js.map +1 -1
  103. package/hmr/client/css-update-overlay.d.ts +18 -0
  104. package/hmr/client/css-update-overlay.js +27 -0
  105. package/hmr/client/css-update-overlay.js.map +1 -0
  106. package/hmr/client/framework-client-strategy.d.ts +79 -0
  107. package/hmr/client/framework-client-strategy.js +19 -0
  108. package/hmr/client/framework-client-strategy.js.map +1 -0
  109. package/hmr/client/hmr-pending-overlay.d.ts +13 -0
  110. package/hmr/client/hmr-pending-overlay.js +60 -0
  111. package/hmr/client/hmr-pending-overlay.js.map +1 -0
  112. package/hmr/client/index.js +787 -211
  113. package/hmr/client/index.js.map +1 -1
  114. package/hmr/client/utils.d.ts +7 -1
  115. package/hmr/client/utils.js +207 -29
  116. package/hmr/client/utils.js.map +1 -1
  117. package/hmr/entry-runtime.d.ts +2 -1
  118. package/hmr/entry-runtime.js +256 -69
  119. package/hmr/entry-runtime.js.map +1 -1
  120. package/hmr/frameworks/angular/build/angular-linker.d.ts +12 -0
  121. package/hmr/frameworks/angular/build/angular-linker.js +109 -0
  122. package/hmr/frameworks/angular/build/angular-linker.js.map +1 -0
  123. package/hmr/frameworks/angular/build/inject-component-hmr-registration.d.ts +112 -0
  124. package/hmr/frameworks/angular/build/inject-component-hmr-registration.js +291 -0
  125. package/hmr/frameworks/angular/build/inject-component-hmr-registration.js.map +1 -0
  126. package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.d.ts +75 -0
  127. package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.js +221 -0
  128. package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.js.map +1 -0
  129. package/{helpers/angular → hmr/frameworks/angular/build}/inline-decorator-component-templates.js +1 -170
  130. package/hmr/frameworks/angular/build/inline-decorator-component-templates.js.map +1 -0
  131. package/hmr/frameworks/angular/build/js-lexer.d.ts +4 -0
  132. package/{helpers/angular/synthesize-decorator-ctor-parameters.js → hmr/frameworks/angular/build/js-lexer.js} +22 -96
  133. package/hmr/frameworks/angular/build/js-lexer.js.map +1 -0
  134. package/hmr/frameworks/angular/build/shared-linker.d.ts +39 -0
  135. package/hmr/frameworks/angular/build/shared-linker.js +128 -0
  136. package/hmr/frameworks/angular/build/shared-linker.js.map +1 -0
  137. package/hmr/frameworks/angular/build/synthesize-decorator-ctor-parameters.js +88 -0
  138. package/hmr/frameworks/angular/build/synthesize-decorator-ctor-parameters.js.map +1 -0
  139. package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-injectable-factories.js +1 -174
  140. package/hmr/frameworks/angular/build/synthesize-injectable-factories.js.map +1 -0
  141. package/{helpers/angular → hmr/frameworks/angular/build}/util.d.ts +1 -0
  142. package/hmr/frameworks/angular/build/util.js +155 -0
  143. package/hmr/frameworks/angular/build/util.js.map +1 -0
  144. package/hmr/frameworks/angular/client/index.d.ts +1 -0
  145. package/hmr/frameworks/angular/client/index.js +803 -21
  146. package/hmr/frameworks/angular/client/index.js.map +1 -1
  147. package/hmr/frameworks/angular/client/strategy.d.ts +9 -0
  148. package/hmr/frameworks/angular/client/strategy.js +19 -0
  149. package/hmr/frameworks/angular/client/strategy.js.map +1 -0
  150. package/hmr/frameworks/angular/server/angular-root-component.d.ts +79 -0
  151. package/hmr/frameworks/angular/server/angular-root-component.js +149 -0
  152. package/hmr/frameworks/angular/server/angular-root-component.js.map +1 -0
  153. package/hmr/frameworks/angular/server/linker.js +1 -4
  154. package/hmr/frameworks/angular/server/linker.js.map +1 -1
  155. package/hmr/frameworks/angular/server/strategy.js +460 -12
  156. package/hmr/frameworks/angular/server/strategy.js.map +1 -1
  157. package/hmr/{server → frameworks/angular/server}/websocket-angular-entry.js +2 -2
  158. package/hmr/frameworks/angular/server/websocket-angular-entry.js.map +1 -0
  159. package/hmr/{server → frameworks/angular/server}/websocket-angular-hot-update.d.ts +17 -11
  160. package/hmr/frameworks/angular/server/websocket-angular-hot-update.js +336 -0
  161. package/hmr/frameworks/angular/server/websocket-angular-hot-update.js.map +1 -0
  162. package/hmr/frameworks/solid/build/solid-jsx-deps.d.ts +15 -0
  163. package/hmr/frameworks/solid/build/solid-jsx-deps.js +178 -0
  164. package/hmr/frameworks/solid/build/solid-jsx-deps.js.map +1 -0
  165. package/hmr/frameworks/solid/server/strategy.js +291 -16
  166. package/hmr/frameworks/solid/server/strategy.js.map +1 -1
  167. package/hmr/frameworks/typescript/server/strategy.js +38 -14
  168. package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
  169. package/hmr/frameworks/vue/client/dep-propagation.d.ts +36 -0
  170. package/hmr/frameworks/vue/client/dep-propagation.js +101 -0
  171. package/hmr/frameworks/vue/client/dep-propagation.js.map +1 -0
  172. package/hmr/frameworks/vue/client/index.d.ts +8 -0
  173. package/hmr/frameworks/vue/client/index.js +56 -243
  174. package/hmr/frameworks/vue/client/index.js.map +1 -1
  175. package/hmr/frameworks/vue/client/strategy.d.ts +33 -0
  176. package/hmr/frameworks/vue/client/strategy.js +157 -0
  177. package/hmr/frameworks/vue/client/strategy.js.map +1 -0
  178. package/hmr/frameworks/vue/client/vue-sfc-update-overlay.d.ts +49 -0
  179. package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js +142 -0
  180. package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js.map +1 -0
  181. package/hmr/frameworks/vue/server/sfc-route-assemble.d.ts +7 -0
  182. package/hmr/frameworks/vue/server/sfc-route-assemble.js +747 -0
  183. package/hmr/frameworks/vue/server/sfc-route-assemble.js.map +1 -0
  184. package/hmr/frameworks/vue/server/sfc-route-meta.d.ts +7 -0
  185. package/hmr/frameworks/vue/server/sfc-route-meta.js +80 -0
  186. package/hmr/frameworks/vue/server/sfc-route-meta.js.map +1 -0
  187. package/hmr/frameworks/vue/server/sfc-route-serve.d.ts +8 -0
  188. package/hmr/frameworks/vue/server/sfc-route-serve.js +459 -0
  189. package/hmr/frameworks/vue/server/sfc-route-serve.js.map +1 -0
  190. package/hmr/frameworks/vue/server/sfc-route-shared.d.ts +38 -0
  191. package/hmr/frameworks/vue/server/sfc-route-shared.js +48 -0
  192. package/hmr/frameworks/vue/server/sfc-route-shared.js.map +1 -0
  193. package/hmr/frameworks/vue/server/strategy.d.ts +35 -0
  194. package/hmr/frameworks/vue/server/strategy.js +278 -1
  195. package/hmr/frameworks/vue/server/strategy.js.map +1 -1
  196. package/hmr/frameworks/vue/server/websocket-sfc.d.ts +15 -0
  197. package/hmr/frameworks/vue/server/websocket-sfc.js +20 -0
  198. package/hmr/frameworks/vue/server/websocket-sfc.js.map +1 -0
  199. package/hmr/helpers/ast-normalizer.d.ts +3 -1
  200. package/hmr/helpers/ast-normalizer.js +77 -10
  201. package/hmr/helpers/ast-normalizer.js.map +1 -1
  202. package/hmr/helpers/cjs-named-exports.d.ts +23 -0
  203. package/hmr/helpers/cjs-named-exports.js +152 -0
  204. package/hmr/helpers/cjs-named-exports.js.map +1 -0
  205. package/hmr/helpers/package-exports.d.ts +16 -0
  206. package/hmr/helpers/package-exports.js +396 -0
  207. package/hmr/helpers/package-exports.js.map +1 -0
  208. package/hmr/server/constants.js +20 -5
  209. package/hmr/server/constants.js.map +1 -1
  210. package/hmr/server/core-sanitize.d.ts +90 -7
  211. package/hmr/server/core-sanitize.js +211 -56
  212. package/hmr/server/core-sanitize.js.map +1 -1
  213. package/hmr/server/device-transform-helpers.d.ts +24 -0
  214. package/hmr/server/device-transform-helpers.js +369 -0
  215. package/hmr/server/device-transform-helpers.js.map +1 -0
  216. package/hmr/server/framework-strategy.d.ts +108 -11
  217. package/hmr/server/hmr-module-graph.d.ts +37 -0
  218. package/hmr/server/hmr-module-graph.js +214 -0
  219. package/hmr/server/hmr-module-graph.js.map +1 -0
  220. package/hmr/server/import-map.d.ts +11 -2
  221. package/hmr/server/import-map.js +95 -44
  222. package/hmr/server/import-map.js.map +1 -1
  223. package/hmr/server/index.js +7 -16
  224. package/hmr/server/index.js.map +1 -1
  225. package/hmr/server/ns-core-cjs-shape.d.ts +204 -0
  226. package/hmr/server/ns-core-cjs-shape.js +271 -0
  227. package/hmr/server/ns-core-cjs-shape.js.map +1 -0
  228. package/hmr/server/ns-rt-bridge.d.ts +51 -0
  229. package/hmr/server/ns-rt-bridge.js +131 -0
  230. package/hmr/server/ns-rt-bridge.js.map +1 -0
  231. package/hmr/server/ns-rt-route.d.ts +5 -0
  232. package/hmr/server/ns-rt-route.js +38 -0
  233. package/hmr/server/ns-rt-route.js.map +1 -0
  234. package/hmr/server/perf-instrumentation.d.ts +114 -0
  235. package/hmr/server/perf-instrumentation.js +197 -0
  236. package/hmr/server/perf-instrumentation.js.map +1 -0
  237. package/hmr/server/process-code-for-device.d.ts +14 -0
  238. package/hmr/server/process-code-for-device.js +702 -0
  239. package/hmr/server/process-code-for-device.js.map +1 -0
  240. package/hmr/server/require-guard.d.ts +1 -0
  241. package/hmr/server/require-guard.js +12 -0
  242. package/hmr/server/require-guard.js.map +1 -0
  243. package/hmr/server/rewrite-imports.d.ts +2 -0
  244. package/hmr/server/rewrite-imports.js +600 -0
  245. package/hmr/server/rewrite-imports.js.map +1 -0
  246. package/hmr/server/route-helpers.d.ts +7 -0
  247. package/hmr/server/route-helpers.js +13 -0
  248. package/hmr/server/route-helpers.js.map +1 -0
  249. package/hmr/server/server-origin.d.ts +2 -0
  250. package/hmr/server/server-origin.js +83 -0
  251. package/hmr/server/server-origin.js.map +1 -0
  252. package/hmr/server/shared-transform-request.js +13 -7
  253. package/hmr/server/shared-transform-request.js.map +1 -1
  254. package/hmr/server/transform-cache-invalidation.d.ts +37 -0
  255. package/hmr/server/transform-cache-invalidation.js +156 -0
  256. package/hmr/server/transform-cache-invalidation.js.map +1 -0
  257. package/hmr/server/vendor-bare-module-shims.d.ts +4 -0
  258. package/hmr/server/vendor-bare-module-shims.js +80 -0
  259. package/hmr/server/vendor-bare-module-shims.js.map +1 -0
  260. package/hmr/server/vite-plugin.js +72 -42
  261. package/hmr/server/vite-plugin.js.map +1 -1
  262. package/hmr/server/websocket-core-bridge.d.ts +45 -6
  263. package/hmr/server/websocket-core-bridge.js +81 -77
  264. package/hmr/server/websocket-core-bridge.js.map +1 -1
  265. package/hmr/server/websocket-css-hot-update.d.ts +33 -0
  266. package/hmr/server/websocket-css-hot-update.js +65 -0
  267. package/hmr/server/websocket-css-hot-update.js.map +1 -0
  268. package/hmr/server/websocket-device-transform.d.ts +3 -0
  269. package/hmr/server/websocket-device-transform.js +7 -0
  270. package/hmr/server/websocket-device-transform.js.map +1 -0
  271. package/hmr/server/websocket-graph-upsert.d.ts +15 -0
  272. package/hmr/server/websocket-graph-upsert.js +20 -0
  273. package/hmr/server/websocket-graph-upsert.js.map +1 -1
  274. package/hmr/server/websocket-hmr-pending.d.ts +37 -0
  275. package/hmr/server/websocket-hmr-pending.js +55 -0
  276. package/hmr/server/websocket-hmr-pending.js.map +1 -0
  277. package/hmr/server/websocket-hot-update.d.ts +77 -0
  278. package/hmr/server/websocket-hot-update.js +360 -0
  279. package/hmr/server/websocket-hot-update.js.map +1 -0
  280. package/hmr/server/websocket-import-map-route.d.ts +15 -0
  281. package/hmr/server/websocket-import-map-route.js +50 -0
  282. package/hmr/server/websocket-import-map-route.js.map +1 -0
  283. package/hmr/server/websocket-module-bindings.js +3 -3
  284. package/hmr/server/websocket-module-bindings.js.map +1 -1
  285. package/hmr/server/websocket-module-specifiers.d.ts +66 -2
  286. package/hmr/server/websocket-module-specifiers.js +203 -20
  287. package/hmr/server/websocket-module-specifiers.js.map +1 -1
  288. package/hmr/server/websocket-ns-core.d.ts +21 -0
  289. package/hmr/server/websocket-ns-core.js +311 -0
  290. package/hmr/server/websocket-ns-core.js.map +1 -0
  291. package/hmr/server/websocket-ns-entry.d.ts +21 -0
  292. package/hmr/server/websocket-ns-entry.js +164 -0
  293. package/hmr/server/websocket-ns-entry.js.map +1 -0
  294. package/hmr/server/websocket-ns-m-paths.d.ts +1 -1
  295. package/hmr/server/websocket-ns-m-paths.js +58 -13
  296. package/hmr/server/websocket-ns-m-paths.js.map +1 -1
  297. package/hmr/server/websocket-ns-m-request.d.ts +11 -1
  298. package/hmr/server/websocket-ns-m-request.js +18 -25
  299. package/hmr/server/websocket-ns-m-request.js.map +1 -1
  300. package/hmr/server/websocket-ns-m.d.ts +33 -0
  301. package/hmr/server/websocket-ns-m.js +752 -0
  302. package/hmr/server/websocket-ns-m.js.map +1 -0
  303. package/hmr/server/websocket-served-module-helpers.d.ts +82 -0
  304. package/hmr/server/websocket-served-module-helpers.js +879 -0
  305. package/hmr/server/websocket-served-module-helpers.js.map +1 -0
  306. package/hmr/server/websocket-txn.js +2 -8
  307. package/hmr/server/websocket-txn.js.map +1 -1
  308. package/hmr/server/websocket-vendor-unifier.d.ts +0 -1
  309. package/hmr/server/websocket-vendor-unifier.js +4 -9
  310. package/hmr/server/websocket-vendor-unifier.js.map +1 -1
  311. package/hmr/server/websocket.d.ts +8 -39
  312. package/hmr/server/websocket.js +514 -4030
  313. package/hmr/server/websocket.js.map +1 -1
  314. package/hmr/shared/ns-globals.d.ts +118 -0
  315. package/hmr/shared/ns-globals.js +29 -0
  316. package/hmr/shared/ns-globals.js.map +1 -0
  317. package/hmr/shared/protocol.d.ts +145 -0
  318. package/hmr/shared/protocol.js +28 -0
  319. package/hmr/shared/protocol.js.map +1 -0
  320. package/hmr/shared/runtime/boot-placeholder-ui.d.ts +69 -0
  321. package/hmr/shared/runtime/boot-placeholder-ui.js +101 -0
  322. package/hmr/shared/runtime/boot-placeholder-ui.js.map +1 -0
  323. package/hmr/shared/runtime/boot-progress.d.ts +44 -0
  324. package/hmr/shared/runtime/boot-progress.js +133 -0
  325. package/hmr/shared/runtime/boot-progress.js.map +1 -0
  326. package/hmr/shared/runtime/boot-timeline.d.ts +18 -0
  327. package/hmr/shared/runtime/boot-timeline.js +42 -0
  328. package/hmr/shared/runtime/boot-timeline.js.map +1 -0
  329. package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -1
  330. package/hmr/shared/runtime/dev-overlay-snapshots.d.ts +31 -0
  331. package/hmr/shared/runtime/dev-overlay-snapshots.js +324 -0
  332. package/hmr/shared/runtime/dev-overlay-snapshots.js.map +1 -0
  333. package/hmr/shared/runtime/dev-overlay.d.ts +75 -26
  334. package/hmr/shared/runtime/dev-overlay.js +992 -261
  335. package/hmr/shared/runtime/dev-overlay.js.map +1 -1
  336. package/hmr/shared/runtime/global-scope.d.ts +18 -0
  337. package/hmr/shared/runtime/global-scope.js +21 -0
  338. package/hmr/shared/runtime/global-scope.js.map +1 -0
  339. package/hmr/shared/runtime/hooks.js +2 -1
  340. package/hmr/shared/runtime/hooks.js.map +1 -1
  341. package/hmr/shared/runtime/http-only-boot.js +7 -6
  342. package/hmr/shared/runtime/http-only-boot.js.map +1 -1
  343. package/hmr/shared/runtime/module-provenance.js +4 -6
  344. package/hmr/shared/runtime/module-provenance.js.map +1 -1
  345. package/hmr/shared/runtime/root-placeholder-view.d.ts +19 -0
  346. package/hmr/shared/runtime/root-placeholder-view.js +311 -0
  347. package/hmr/shared/runtime/root-placeholder-view.js.map +1 -0
  348. package/hmr/shared/runtime/root-placeholder.js +371 -197
  349. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  350. package/hmr/shared/runtime/session-bootstrap.js +168 -4
  351. package/hmr/shared/runtime/session-bootstrap.js.map +1 -1
  352. package/hmr/shared/runtime/vendor-bootstrap.js +3 -10
  353. package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
  354. package/hmr/shared/vendor/manifest-collect.d.ts +4 -0
  355. package/hmr/shared/vendor/manifest-collect.js +512 -0
  356. package/hmr/shared/vendor/manifest-collect.js.map +1 -0
  357. package/hmr/shared/vendor/manifest-loader.d.ts +2 -1
  358. package/hmr/shared/vendor/manifest-loader.js +5 -3
  359. package/hmr/shared/vendor/manifest-loader.js.map +1 -1
  360. package/hmr/shared/vendor/manifest.d.ts +1 -7
  361. package/hmr/shared/vendor/manifest.js +102 -741
  362. package/hmr/shared/vendor/manifest.js.map +1 -1
  363. package/hmr/shared/vendor/vendor-device-shim.d.ts +1 -0
  364. package/hmr/shared/vendor/vendor-device-shim.js +208 -0
  365. package/hmr/shared/vendor/vendor-device-shim.js.map +1 -0
  366. package/hmr/shared/vendor/vendor-esbuild-plugins.d.ts +16 -0
  367. package/hmr/shared/vendor/vendor-esbuild-plugins.js +203 -0
  368. package/hmr/shared/vendor/vendor-esbuild-plugins.js.map +1 -0
  369. package/hmr/vendor-bootstrap.d.ts +1 -3
  370. package/hmr/vendor-bootstrap.js +4 -6
  371. package/hmr/vendor-bootstrap.js.map +1 -1
  372. package/index.d.ts +1 -0
  373. package/index.js +5 -0
  374. package/index.js.map +1 -1
  375. package/package.json +55 -11
  376. package/runtime/core-aliases-early.js +25 -55
  377. package/runtime/core-aliases-early.js.map +1 -1
  378. package/helpers/angular/angular-linker.d.ts +0 -13
  379. package/helpers/angular/angular-linker.js +0 -194
  380. package/helpers/angular/angular-linker.js.map +0 -1
  381. package/helpers/angular/inline-decorator-component-templates.js.map +0 -1
  382. package/helpers/angular/shared-linker.d.ts +0 -11
  383. package/helpers/angular/shared-linker.js +0 -75
  384. package/helpers/angular/shared-linker.js.map +0 -1
  385. package/helpers/angular/synthesize-decorator-ctor-parameters.js.map +0 -1
  386. package/helpers/angular/synthesize-injectable-factories.js.map +0 -1
  387. package/helpers/angular/util.js +0 -67
  388. package/helpers/angular/util.js.map +0 -1
  389. package/helpers/prelink-angular.d.ts +0 -2
  390. package/helpers/prelink-angular.js +0 -117
  391. package/helpers/prelink-angular.js.map +0 -1
  392. package/hmr/server/websocket-angular-entry.js.map +0 -1
  393. package/hmr/server/websocket-angular-hot-update.js +0 -239
  394. package/hmr/server/websocket-angular-hot-update.js.map +0 -1
  395. package/hmr/server/websocket-ns-m-finalize.d.ts +0 -32
  396. package/hmr/server/websocket-ns-m-finalize.js +0 -73
  397. package/hmr/server/websocket-ns-m-finalize.js.map +0 -1
  398. package/hmr/server/websocket-runtime-compat.d.ts +0 -19
  399. package/hmr/server/websocket-runtime-compat.js +0 -286
  400. package/hmr/server/websocket-runtime-compat.js.map +0 -1
  401. package/hmr/server/websocket-vue-sfc.d.ts +0 -35
  402. package/hmr/server/websocket-vue-sfc.js +0 -1116
  403. package/hmr/server/websocket-vue-sfc.js.map +0 -1
  404. package/transformers/NativeClass/index.d.ts +0 -2
  405. package/transformers/NativeClass/index.js +0 -222
  406. package/transformers/NativeClass/index.js.map +0 -1
  407. /package/{helpers/angular → hmr/frameworks/angular/build}/inline-decorator-component-templates.d.ts +0 -0
  408. /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-decorator-ctor-parameters.d.ts +0 -0
  409. /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-injectable-factories.d.ts +0 -0
  410. /package/hmr/{server → frameworks/angular/server}/websocket-angular-entry.d.ts +0 -0
@@ -5,9 +5,11 @@
5
5
  * Always resolve core classes and Application from the vendor realm or globalThis at runtime.
6
6
  * The HMR client is evaluated via HTTP ESM on device; static imports would create secondary instances.
7
7
  */
8
- import { setHMRWsUrl, getHMRWsUrl, pendingModuleFetches, deriveHttpOrigin, setHttpOriginForVite, moduleFetchCache, requestModuleFromServer, getHttpOriginForVite, normalizeSpec, hmrMetrics, graph, setGraphVersion, getGraphVersion, getCurrentApp, getRootFrame, setCurrentApp, setRootFrame, getCore } from './utils.js';
8
+ import { setHMRWsUrl, getHMRWsUrl, pendingModuleFetches, deriveHttpOrigin, setHttpOriginForVite, moduleFetchCache, requestModuleFromServer, getHttpOriginForVite, normalizeSpec, hmrMetrics, graph, setGraphVersion, getGraphVersion, getCurrentApp, getRootFrame, setCurrentApp, setRootFrame, getCore, hasExplicitEviction, invalidateModulesByUrls, buildEvictionUrls, emitHmrModeBannerOnce, ENV_VERBOSE } from './utils.js';
9
9
  import { handleCssUpdates } from './css-handler.js';
10
- const VERBOSE = typeof __NS_ENV_VERBOSE__ !== 'undefined' && __NS_ENV_VERBOSE__;
10
+ import { buildCssApplyingDetail, buildCssAppliedDetail } from './css-update-overlay.js';
11
+ import { getGlobalScope } from '../shared/runtime/global-scope.js';
12
+ const VERBOSE = ENV_VERBOSE;
11
13
  function resolveTargetFlavor() {
12
14
  try {
13
15
  if (typeof __NS_TARGET_FLAVOR__ !== 'undefined' && __NS_TARGET_FLAVOR__) {
@@ -16,7 +18,7 @@ function resolveTargetFlavor() {
16
18
  }
17
19
  catch { }
18
20
  try {
19
- const g = globalThis;
21
+ const g = getGlobalScope();
20
22
  if (typeof g.__NS_TARGET_FLAVOR__ === 'string' && g.__NS_TARGET_FLAVOR__) {
21
23
  return g.__NS_TARGET_FLAVOR__;
22
24
  }
@@ -40,7 +42,11 @@ try {
40
42
  }
41
43
  }
42
44
  catch { }
43
- const APP_ROOT_VIRTUAL = typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__ ? __NS_APP_ROOT_VIRTUAL__ : '/src';
45
+ // Define substitution does NOT reach this file (served raw from node_modules),
46
+ // so prefer the globalThis seed planted by the entry's defines-seed module —
47
+ // the '/src' literal is a last-resort default and is WRONG for 'app/'-rooted
48
+ // projects.
49
+ const APP_ROOT_VIRTUAL = (typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__) || (typeof getGlobalScope().__NS_APP_ROOT_VIRTUAL__ === 'string' && getGlobalScope().__NS_APP_ROOT_VIRTUAL__) || '/src';
44
50
  const APP_VIRTUAL_WITH_SLASH = APP_ROOT_VIRTUAL.endsWith('/') ? APP_ROOT_VIRTUAL : `${APP_ROOT_VIRTUAL}/`;
45
51
  const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
46
52
  // Policy: by default, let the app's own main entry mount initially; HMR client handles updates/remounts only.
@@ -101,6 +107,79 @@ function hideConnectionOverlay() {
101
107
  }
102
108
  catch { }
103
109
  }
110
+ function setUpdateOverlayStage(stage, info) {
111
+ try {
112
+ const api = getHmrOverlayApi();
113
+ if (api && typeof api.setUpdateStage === 'function') {
114
+ api.setUpdateStage(stage, info);
115
+ }
116
+ }
117
+ catch { }
118
+ }
119
+ // Store the listener registry on globalThis (rather than in a module-private
120
+ // closure) because in NativeScript the HMR client module and the user app
121
+ // modules can resolve to different module instances depending on how the
122
+ // dev runtime loads them (HTTP client URL vs. the bundled vendor realm).
123
+ // A module-local Set would not be shared across instances; the global one
124
+ // is.
125
+ function getNsSolidHmrListenerSet() {
126
+ const g = getGlobalScope();
127
+ let set = g.__ns_solid_hmr_listener_set;
128
+ if (!set) {
129
+ set = new Set();
130
+ g.__ns_solid_hmr_listener_set = set;
131
+ }
132
+ return set;
133
+ }
134
+ function nsSolidHmrSubscribe(fn) {
135
+ const listeners = getNsSolidHmrListenerSet();
136
+ listeners.add(fn);
137
+ if (VERBOSE)
138
+ console.log('[hmr][solid] subscribe — listeners=', listeners.size);
139
+ return () => listeners.delete(fn);
140
+ }
141
+ function nsSolidHmrEmit(ev) {
142
+ const listeners = getNsSolidHmrListenerSet();
143
+ if (VERBOSE)
144
+ console.log('[hmr][solid] emit listeners=', listeners.size, 'changedFiles=', ev.changedFiles);
145
+ for (const fn of Array.from(listeners)) {
146
+ try {
147
+ fn(ev);
148
+ }
149
+ catch (err) {
150
+ if (VERBOSE)
151
+ console.warn('[hmr][solid] listener threw', err);
152
+ }
153
+ }
154
+ }
155
+ try {
156
+ const g = getGlobalScope();
157
+ g.__ns_solid_hmr_subscribe = nsSolidHmrSubscribe;
158
+ // Eagerly create the listener set so the global exists at module load time.
159
+ getNsSolidHmrListenerSet();
160
+ if (VERBOSE)
161
+ console.log('[hmr][solid] HMR client loaded. global set=', typeof g.__ns_solid_hmr_subscribe, 'listenerSet=', typeof g.__ns_solid_hmr_listener_set);
162
+ }
163
+ catch (err) {
164
+ console.warn('[hmr][solid] could not install global __ns_solid_hmr_subscribe', err);
165
+ }
166
+ // Eagerly drive the HMR-applying overlay's 'received' frame as soon
167
+ // as the server emits `ns:hmr-pending`, BEFORE the framework-specific
168
+ // (`ns:angular-update` / `ns:css-updates`) payload arrives. The
169
+ // flavor-specific handler later walks through 'evicting' →
170
+ // 'reimporting' → 'rebooting' → 'complete'. Calling 'received' twice
171
+ // in the same cycle is safe: the overlay preserves
172
+ // `updateCycleStartedAt` when a 'received' frame replaces an existing
173
+ // 'received' frame so the minimum-visible window is still timed
174
+ // against the FIRST frame.
175
+ //
176
+ // Soft-fails when the overlay isn't installed (production builds,
177
+ // vitest, etc.) or when the user opted out via
178
+ // `__NS_HMR_PROGRESS_OVERLAY_ENABLED__ === false`.
179
+ import { applyHmrPendingFrame } from './hmr-pending-overlay.js';
180
+ function setHmrPendingOverlay(filePath) {
181
+ applyHmrPendingFrame(filePath, { getOverlay: getHmrOverlayApi });
182
+ }
104
183
  let connectionOverlayTimer = null;
105
184
  let connectionOverlayVisible = false;
106
185
  let hasOpenedHmrSocket = false;
@@ -142,19 +221,19 @@ function markHmrConnectionHealthy() {
142
221
  hideConnectionOverlay();
143
222
  }
144
223
  }
145
- /**
146
- * Flavor hooks
147
- */
148
- import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate, sfcArtifactMap } from '../frameworks/vue/client/index.js';
149
- import { handleAngularHotUpdateMessage, installAngularHmrClientHooks } from '../frameworks/angular/client/index.js';
150
- switch (TARGET_FLAVOR) {
151
- case 'vue':
152
- installNsVueDevShims();
153
- break;
154
- case 'angular':
155
- installAngularHmrClientHooks();
156
- break;
157
- }
224
+ let CLIENT_STRATEGY;
225
+ const CLIENT_STRATEGY_READY = TARGET_FLAVOR === 'vue' || TARGET_FLAVOR === 'angular'
226
+ ? import(`../frameworks/${TARGET_FLAVOR}/client/strategy.js`)
227
+ .then((mod) => {
228
+ CLIENT_STRATEGY = mod && mod[`${TARGET_FLAVOR}ClientStrategy`];
229
+ if (VERBOSE)
230
+ console.log('[hmr-client] client strategy loaded for flavor:', TARGET_FLAVOR);
231
+ CLIENT_STRATEGY?.install();
232
+ })
233
+ .catch((err) => {
234
+ console.warn('[hmr-client] failed to load client strategy for', TARGET_FLAVOR, err);
235
+ })
236
+ : Promise.resolve();
158
237
  // Track whether we've mounted an initial app root yet in HTTP-only boot
159
238
  let initialMounted = !!globalThis.__NS_HMR_BOOT_COMPLETE__;
160
239
  // Prevent duplicate initial-mount scheduling across rapid full-graph broadcasts and re-evaluations
@@ -172,7 +251,7 @@ let processingPromise = null;
172
251
  // Detect whether the early placeholder root is still active on screen
173
252
  function isPlaceholderActive() {
174
253
  try {
175
- const g = globalThis;
254
+ const g = getGlobalScope();
176
255
  if (g.__NS_DEV_PLACEHOLDER_ROOT_VIEW__)
177
256
  return true;
178
257
  if (g.__NS_DEV_PLACEHOLDER_ROOT_EARLY__)
@@ -202,7 +281,7 @@ function applyFullGraph(payload) {
202
281
  // causes a double-mount race (rescue fires at 450ms, then main.ts fires ~1s later,
203
282
  // causing a visual flash and leaving the app in an inconsistent state).
204
283
  try {
205
- const g = globalThis;
284
+ const g = getGlobalScope();
206
285
  const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
207
286
  if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && TARGET_FLAVOR !== 'typescript') {
208
287
  // simple snapshot helpers
@@ -317,39 +396,7 @@ function applyFullGraph(payload) {
317
396
  }
318
397
  return;
319
398
  }
320
- let candidate = null;
321
- switch (TARGET_FLAVOR) {
322
- case 'vue': {
323
- const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
324
- if (appEntry && Array.isArray(appEntry.deps)) {
325
- const vueDep = appEntry.deps.find((d) => typeof d === 'string' && /\.vue$/i.test(d));
326
- if (vueDep)
327
- candidate = vueDep;
328
- }
329
- if (!candidate) {
330
- for (const id of graph.keys()) {
331
- if (/\.vue$/i.test(id)) {
332
- candidate = id;
333
- break;
334
- }
335
- }
336
- }
337
- // Fallback: when the module graph is empty (Vite 7+ may not populate it
338
- // before the first full-graph broadcast), check the SFC artifact registry
339
- // which is populated from the ns:vue-sfc-registry message.
340
- if (!candidate && sfcArtifactMap.size > 0) {
341
- for (const id of sfcArtifactMap.keys()) {
342
- if (/\.vue$/i.test(id)) {
343
- candidate = id;
344
- if (VERBOSE)
345
- console.log('[hmr][init] rescue candidate from SFC registry:', id);
346
- break;
347
- }
348
- }
349
- }
350
- break;
351
- }
352
- }
399
+ const candidate = CLIENT_STRATEGY?.selectMountCandidate?.({ graph, appMainEntrySpec: APP_MAIN_ENTRY_SPEC }) ?? null;
353
400
  if (!candidate)
354
401
  return;
355
402
  initialMounting = true;
@@ -360,10 +407,8 @@ function applyFullGraph(payload) {
360
407
  (async () => {
361
408
  try {
362
409
  let comp = null;
363
- switch (TARGET_FLAVOR) {
364
- case 'vue':
365
- comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
366
- break;
410
+ if (CLIENT_STRATEGY?.loadComponentForMount) {
411
+ comp = await CLIENT_STRATEGY.loadComponentForMount(candidate, 'initial_mount_rescue');
367
412
  }
368
413
  if (!comp)
369
414
  return;
@@ -404,41 +449,11 @@ function applyFullGraph(payload) {
404
449
  // Only allow initial mount when explicitly enabled. Rely on the app's own main entry start() for the first mount
405
450
  // to avoid double-mount races that can cause duplicate navigation logs.
406
451
  if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
407
- let candidate = null;
408
- switch (TARGET_FLAVOR) {
409
- case 'vue': {
410
- const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
411
- if (appEntry && Array.isArray(appEntry.deps)) {
412
- const vueDep = appEntry.deps.find((d) => typeof d === 'string' && /\.vue$/i.test(d));
413
- if (vueDep)
414
- candidate = vueDep;
415
- }
416
- if (!candidate) {
417
- for (const id of graph.keys()) {
418
- if (/\.vue$/i.test(id)) {
419
- candidate = id;
420
- break;
421
- }
422
- }
423
- }
424
- // Fallback: SFC registry (same as rescue mount above)
425
- if (!candidate && sfcArtifactMap.size > 0) {
426
- for (const id of sfcArtifactMap.keys()) {
427
- if (/\.vue$/i.test(id)) {
428
- candidate = id;
429
- if (VERBOSE)
430
- console.log('[hmr][init] initial mount candidate from SFC registry:', id);
431
- break;
432
- }
433
- }
434
- }
435
- break;
436
- }
437
- case 'typescript': {
438
- // For TS flavor, do not perform client-driven initial mount; rely on Application.run.
439
- return;
440
- }
452
+ if (TARGET_FLAVOR === 'typescript') {
453
+ // For TS flavor, do not perform client-driven initial mount; rely on Application.run.
454
+ return;
441
455
  }
456
+ const candidate = CLIENT_STRATEGY?.selectMountCandidate?.({ graph, appMainEntrySpec: APP_MAIN_ENTRY_SPEC }) ?? null;
442
457
  if (candidate) {
443
458
  // Mark initial-mount in progress (both module-local and global) BEFORE scheduling async work
444
459
  initialMounting = true;
@@ -452,7 +467,7 @@ function applyFullGraph(payload) {
452
467
  console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
453
468
  // Android-only: avoid racing entry-runtime reset and Activity bring-up
454
469
  try {
455
- const g = globalThis;
470
+ const g = getGlobalScope();
456
471
  const App = getCore('Application') || g.Application;
457
472
  const isAndroid = !!(App && App.android !== undefined);
458
473
  if (isAndroid) {
@@ -483,21 +498,19 @@ function applyFullGraph(payload) {
483
498
  }
484
499
  catch { }
485
500
  let comp = null;
486
- switch (TARGET_FLAVOR) {
487
- case 'vue':
488
- comp = await loadSfcComponent(candidate, 'initial_mount');
489
- break;
490
- case 'typescript':
491
- try {
492
- const url = await requestModuleFromServer(candidate);
493
- const mod = await import(/* @vite-ignore */ url);
494
- comp = mod && (mod.default || mod);
495
- }
496
- catch (e) {
497
- if (VERBOSE)
498
- console.warn('[hmr][init] TS initial mount failed to import', candidate, e);
499
- }
500
- break;
501
+ if (TARGET_FLAVOR === 'typescript') {
502
+ try {
503
+ const url = await requestModuleFromServer(candidate);
504
+ const mod = await import(/* @vite-ignore */ url);
505
+ comp = mod && (mod.default || mod);
506
+ }
507
+ catch (e) {
508
+ if (VERBOSE)
509
+ console.warn('[hmr][init] TS initial mount failed to import', candidate, e);
510
+ }
511
+ }
512
+ else if (CLIENT_STRATEGY?.loadComponentForMount) {
513
+ comp = await CLIENT_STRATEGY.loadComponentForMount(candidate, 'initial_mount');
501
514
  }
502
515
  if (comp) {
503
516
  const ok = await performResetRoot(comp);
@@ -558,11 +571,7 @@ function applyDelta(payload) {
558
571
  setGraphVersion(payload.newVersion);
559
572
  }
560
573
  const changed = payload.changed || [];
561
- switch (TARGET_FLAVOR) {
562
- case 'vue':
563
- recordVuePayloadChanges(changed, getGraphVersion());
564
- break;
565
- }
574
+ CLIENT_STRATEGY?.recordPayloadChanges?.(changed, getGraphVersion());
566
575
  (payload.changed || []).forEach((m) => {
567
576
  if (!m || !m.id)
568
577
  return;
@@ -629,7 +638,7 @@ function applyDelta(payload) {
629
638
  }
630
639
  if (isAppMainEntryId(id)) {
631
640
  try {
632
- const exists = globalThis.require?.(id) || globalThis.__nsGetModuleExports?.(id);
641
+ const exists = getGlobalScope().require?.(id) || globalThis.__nsGetModuleExports?.(id);
633
642
  if (!exists && VERBOSE)
634
643
  console.log(`[hmr][delta] skipping unresolved ${APP_MAIN_ENTRY_SPEC} change`);
635
644
  if (!exists)
@@ -645,14 +654,10 @@ function applyDelta(payload) {
645
654
  processQueue();
646
655
  }
647
656
  }
648
- // Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
657
+ // Deterministic navigation using the current Vue app instance rather than vendor-held rootApp.
649
658
  function __nsNavigateUsingApp(comp, opts = {}) {
650
- const g = globalThis;
651
- switch (TARGET_FLAVOR) {
652
- case 'vue':
653
- ensureVueGlobals();
654
- break;
655
- }
659
+ const g = getGlobalScope();
660
+ CLIENT_STRATEGY?.beforeNavigateBuild?.();
656
661
  const AppFactory = g.createApp;
657
662
  const RootCtor = g.NSVRoot;
658
663
  if (typeof AppFactory !== 'function' || typeof RootCtor !== 'function') {
@@ -675,12 +680,14 @@ function __nsNavigateUsingApp(comp, opts = {}) {
675
680
  const buildTarget = () => {
676
681
  const existingApp = getCurrentApp();
677
682
  const baseProvides = (existingApp && existingApp._context && existingApp._context.provides) || {};
678
- const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)));
679
- switch (TARGET_FLAVOR) {
680
- case 'vue':
681
- ensurePiniaOnApp(app);
682
- break;
683
- }
683
+ // Forward `opts.props` as Vue's rootProps so `$navigateTo(Comp, { props: { } })`
684
+ // reaches the destination component. nativescript-vue's stock `$navigateTo`
685
+ // does the same via `createNativeView(target, options?.props, …)` →
686
+ // `renderer.createApp(component, props)`. Dropping props here would surface
687
+ // at the destination as `[Vue warn]: Missing required prop` and any
688
+ // required-prop component would render with `undefined` bindings.
689
+ const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)), opts && opts.props);
690
+ CLIENT_STRATEGY?.onNavAppCreated?.(app);
684
691
  try {
685
692
  const registry = g.__nsVendorRegistry;
686
693
  const req = registry?.get ? g.__nsVendorRequire || g.__nsRequire || g.require : g.__nsRequire || g.require;
@@ -775,6 +782,184 @@ try {
775
782
  globalThis.__nsNavigateUsingApp = __nsNavigateUsingApp;
776
783
  }
777
784
  catch { }
785
+ const openModalRecords = [];
786
+ let modalTrackingInstalled = false;
787
+ /**
788
+ * Map a served/graph module id (e.g. `/app/modal-page.xml`) to its app-root
789
+ * relative path (`modal-page.xml`). Single mapping point — the raw-asset
790
+ * re-registration, page-navigation targets, and modal matching all derive
791
+ * from this; keep them in sync by construction.
792
+ */
793
+ function toAppRelativePath(id) {
794
+ try {
795
+ const spec = normalizeSpec(id);
796
+ const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
797
+ let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
798
+ if (relPath.startsWith(appVirtual))
799
+ relPath = relPath.slice(appVirtual.length);
800
+ return relPath || null;
801
+ }
802
+ catch {
803
+ return null;
804
+ }
805
+ }
806
+ /** App-root relative module name (no extension) for page-shaped files, else null. */
807
+ function toAppModuleName(id) {
808
+ const relPath = toAppRelativePath(id);
809
+ if (!relPath || !/\.(xml|ts|js)$/i.test(relPath))
810
+ return null;
811
+ return relPath.replace(/\.(xml|ts|js)$/i, '');
812
+ }
813
+ function ensureModalTracking() {
814
+ if (modalTrackingInstalled)
815
+ return;
816
+ try {
817
+ const View = getCore('View') || getGlobalScope().View;
818
+ const proto = View?.prototype;
819
+ if (!proto || typeof proto.showModal !== 'function')
820
+ return;
821
+ const orig = proto.showModal;
822
+ if (orig.__nsHmrModalTracked) {
823
+ modalTrackingInstalled = true;
824
+ return;
825
+ }
826
+ const wrapped = function (...args) {
827
+ const result = orig.apply(this, args);
828
+ try {
829
+ if (typeof args[0] === 'string' && result) {
830
+ // Mirror core's getModalOptions arg shapes: (moduleName, options)
831
+ // or the deprecated positional form.
832
+ const options = args.length === 2 && args[1] && typeof args[1] === 'object' ? args[1] : { context: args[1], closeCallback: args[2], fullscreen: args[3], animated: args[4], stretched: args[5] };
833
+ const moduleName = String(args[0])
834
+ .replace(/^\.\//, '')
835
+ .replace(/\.(xml|ts|js)$/i, '');
836
+ openModalRecords.push({ moduleName, options, parent: this, modal: result });
837
+ if (VERBOSE)
838
+ console.log('[hmr][modal] tracked open modal', moduleName);
839
+ }
840
+ }
841
+ catch { }
842
+ return result;
843
+ };
844
+ wrapped.__nsHmrModalTracked = true;
845
+ proto.showModal = wrapped;
846
+ modalTrackingInstalled = true;
847
+ }
848
+ catch (e) {
849
+ if (VERBOSE)
850
+ console.warn('[hmr][modal] tracking install failed', e);
851
+ }
852
+ }
853
+ /**
854
+ * Enumerate the modals that are currently presented AND were opened by module
855
+ * name, with everything needed to re-present them.
856
+ *
857
+ * Source of truth is core's live modal stack (`_getRootModalViews()`):
858
+ * - `modal._moduleName` — set by the Builder on every createViewFromEntry
859
+ * view (longstanding, used by livesync), so available on any core.
860
+ * - `modal._modalOptions` — the original ShowModalOptions, stored by core's
861
+ * `_showNativeModalView` (newer cores). For older cores the showModal
862
+ * wrap's records (see ensureModalTracking) fill the gap.
863
+ * - `modal._modalParent` — the presenting view.
864
+ * Stale wrap records are pruned against the live stack while we're here.
865
+ */
866
+ function getOpenStringModuleModals() {
867
+ const out = [];
868
+ try {
869
+ const App = getCore('Application');
870
+ const root = App?.getRootView?.() || App?._rootView;
871
+ const stack = root?._getRootModalViews?.() || [];
872
+ // Prune wrap records whose modal is gone (keeps the fallback list small).
873
+ for (let i = openModalRecords.length - 1; i >= 0; i--) {
874
+ if (!stack.includes(openModalRecords[i].modal)) {
875
+ openModalRecords.splice(i, 1);
876
+ }
877
+ }
878
+ for (const modal of stack) {
879
+ const record = openModalRecords.find((r) => r.modal === modal);
880
+ const rawModuleName = typeof modal?._moduleName === 'string' && modal._moduleName ? modal._moduleName : record?.moduleName;
881
+ const parent = modal?._modalParent || record?.parent;
882
+ const options = modal?._modalOptions || record?.options;
883
+ if (!rawModuleName || !parent)
884
+ continue;
885
+ const moduleName = String(rawModuleName)
886
+ .replace(/^\.\//, '')
887
+ .replace(/\.(xml|ts|js)$/i, '');
888
+ out.push({ moduleName, options, parent, modal });
889
+ }
890
+ }
891
+ catch (e) {
892
+ if (VERBOSE)
893
+ console.warn('[hmr][modal] open-modal enumeration failed', e);
894
+ }
895
+ return out;
896
+ }
897
+ /**
898
+ * Close and re-present an open modal so it rebuilds from the freshly
899
+ * re-registered XML/code-behind. Core clears the modal stack synchronously on
900
+ * close but the NATIVE dismissal completes asynchronously; iOS refuses a
901
+ * present while a dismissal is in flight. Newer cores fire `closedModally` on
902
+ * the modal at exactly that completion point — preferred signal. Older cores
903
+ * fall back to polling `isLoaded` (flipped by `_tearDownUI` in the same
904
+ * completion callback).
905
+ */
906
+ async function reshowOpenModal(record) {
907
+ const { parent, modal, moduleName, options } = record;
908
+ await new Promise((resolve) => {
909
+ let settled = false;
910
+ const finish = () => {
911
+ if (!settled) {
912
+ settled = true;
913
+ resolve();
914
+ }
915
+ };
916
+ let eventArmed = false;
917
+ try {
918
+ if (typeof modal.once === 'function') {
919
+ modal.once('closedModally', finish);
920
+ eventArmed = true;
921
+ }
922
+ }
923
+ catch { }
924
+ // Poll fallback (also the safety net if the event never fires —
925
+ // e.g. an interactive-dismiss cancellation).
926
+ const deadline = Date.now() + 2000;
927
+ const poll = () => {
928
+ if (settled)
929
+ return;
930
+ let stillLoaded = false;
931
+ try {
932
+ stillLoaded = !!modal.isLoaded;
933
+ }
934
+ catch { }
935
+ if ((!eventArmed && !stillLoaded) || Date.now() > deadline) {
936
+ finish();
937
+ return;
938
+ }
939
+ setTimeout(poll, 50);
940
+ };
941
+ setTimeout(poll, 50);
942
+ try {
943
+ modal.closeModal();
944
+ }
945
+ catch (e) {
946
+ if (VERBOSE)
947
+ console.warn('[hmr][modal] close failed for', moduleName, e);
948
+ finish();
949
+ }
950
+ });
951
+ // One settle beat so the platform finishes releasing the presentation
952
+ // before the new present begins.
953
+ await new Promise((resolve) => setTimeout(resolve, 100));
954
+ try {
955
+ parent.showModal(moduleName, { ...(options || {}), animated: false });
956
+ if (VERBOSE)
957
+ console.log('[hmr][modal] re-presented', moduleName);
958
+ }
959
+ catch (e) {
960
+ console.warn('[hmr][modal] re-present failed for', moduleName, e);
961
+ }
962
+ }
778
963
  async function processQueue() {
779
964
  if (!globalThis.__NS_HMR_BOOT_COMPLETE__) {
780
965
  if (VERBOSE)
@@ -810,7 +995,43 @@ async function processQueue() {
810
995
  return;
811
996
  if (VERBOSE)
812
997
  console.log('[hmr][queue] processing changed ids', drained);
998
+ // Track wall-clock so the 'complete' frame can show a meaningful
999
+ // total. Only the Solid + TypeScript flavors drive the overlay
1000
+ // from here; Angular has its own flow inside
1001
+ // `frameworks/angular/client/index.ts`.
1002
+ const tQueueStart = Date.now();
1003
+ const driveSolidOverlay = TARGET_FLAVOR === 'solid';
1004
+ // Explicit eviction step.
1005
+ //
1006
+ // On modern runtimes the URL canonicalizer collapses any
1007
+ // `__ns_hmr__/<tag>/` segment back to a stable cache key, so
1008
+ // without explicit eviction the upcoming `import(url)` would
1009
+ // resolve via V8's `g_moduleRegistry` and return the cached
1010
+ // stale module — making the queue drain a silent no-op for
1011
+ // every save after the first.
1012
+ //
1013
+ // We hand the canonical eviction URLs to the runtime first;
1014
+ // `invalidateModulesByUrls` is a no-op on older runtimes and
1015
+ // `requestModuleFromServer` automatically falls back to the
1016
+ // legacy `/ns/m/__ns_hmr__/v<N>/` URL versioning path in that
1017
+ // case. node_modules and virtual specs are filtered out by
1018
+ // `buildEvictionUrls` so vendor modules stay hot.
1019
+ if (driveSolidOverlay) {
1020
+ setUpdateOverlayStage('evicting', {
1021
+ detail: drained.length === 1 ? `Invalidating ${drained[0]}` : `Invalidating ${drained.length} modules`,
1022
+ });
1023
+ }
1024
+ const evictUrls = buildEvictionUrls(drained);
1025
+ const evicted = invalidateModulesByUrls(evictUrls);
1026
+ if (VERBOSE)
1027
+ console.log(`[hmr][queue] eviction count=${evictUrls.length} ok=${evicted}`);
813
1028
  // Evaluate changed modules best-effort; failures shouldn't completely break HMR.
1029
+ if (driveSolidOverlay) {
1030
+ setUpdateOverlayStage('reimporting', {
1031
+ detail: drained.length === 1 ? `Re-importing ${drained[0]}` : `Re-importing ${drained.length} modules`,
1032
+ });
1033
+ }
1034
+ let reimportFailures = 0;
814
1035
  for (const id of drained) {
815
1036
  try {
816
1037
  const spec = normalizeSpec(id);
@@ -820,18 +1041,56 @@ async function processQueue() {
820
1041
  if (VERBOSE)
821
1042
  console.log('[hmr][queue] re-import', { id, spec, url });
822
1043
  const mod = await import(/* @vite-ignore */ url);
1044
+ // TS/XML flavor: refresh the bundler module registry with the fresh
1045
+ // exports so Builder.createViewFromEntry / loadModule('<page>')
1046
+ // resolves the NEW code-behind (tap handlers, page events) instead
1047
+ // of the stale module captured in the boot bundle. Without this,
1048
+ // XML re-renders pick up new markup but keep old behavior.
1049
+ if (TARGET_FLAVOR === 'typescript' && mod && /\.(ts|js)$/i.test(id)) {
1050
+ try {
1051
+ const g = getGlobalScope();
1052
+ const moduleName = toAppModuleName(id);
1053
+ if (moduleName && typeof g.registerModule === 'function') {
1054
+ g.registerModule(moduleName, () => mod);
1055
+ g.registerModule('./' + moduleName, () => mod);
1056
+ if (VERBOSE)
1057
+ console.log('[hmr][queue] re-registered code-behind', moduleName);
1058
+ }
1059
+ }
1060
+ catch (e) {
1061
+ if (VERBOSE)
1062
+ console.warn('[hmr][queue] code-behind re-register failed for', id, e);
1063
+ }
1064
+ }
823
1065
  }
824
1066
  catch (e) {
825
- if (VERBOSE)
826
- console.warn('[hmr][queue] re-import failed for', id, e);
1067
+ // Never silent: a failed re-import means the device may keep
1068
+ // running the previous module body, so always surface it (not
1069
+ // verbose-gated) — otherwise the overlay reports success while
1070
+ // the app runs stale code.
1071
+ reimportFailures++;
1072
+ console.warn('[hmr][queue] re-import FAILED for', id, '-', e?.message ?? e);
827
1073
  }
828
1074
  }
1075
+ if (reimportFailures > 0) {
1076
+ console.warn(`[hmr][queue] ${reimportFailures}/${drained.length} module(s) failed to re-import; the applied update may be incomplete.`);
1077
+ }
829
1078
  // After evaluating the batch, perform flavor-specific UI refresh.
830
1079
  switch (TARGET_FLAVOR) {
831
1080
  case 'vue':
832
- // Vue SFCs are handled via the registry update path; nothing to do here.
1081
+ // graph + performResetRoot + getOverlay let the Vue strategy
1082
+ // propagate non-SFC dep changes to the nearest `.vue` boundary
1083
+ // and remount it (see `propagateDepChangeToSfcBoundary`).
1084
+ await CLIENT_STRATEGY?.refreshAfterBatch?.(drained, { setUpdateOverlayStage, startedAt: tQueueStart, graph, performResetRoot, getOverlay: getHmrOverlayApi });
833
1085
  break;
834
1086
  case 'solid': {
1087
+ // Boundaries discovered in this HMR cycle (tsx files reachable
1088
+ // via the reverse import graph from any changed file, plus route
1089
+ // files reachable from any tsx start point). Declared at the top
1090
+ // of the case block so the emit step below can include the
1091
+ // complete set in the listener event — framework integrations
1092
+ // use it to map route boundaries → fresh component references.
1093
+ const boundaries = new Set();
835
1094
  // Solid .tsx components are self-accepting via solid-refresh's inline
836
1095
  // patchRegistry — re-importing them is sufficient. For non-component
837
1096
  // .ts utility modules, we must propagate up the import graph to find
@@ -850,8 +1109,10 @@ async function processQueue() {
850
1109
  arr.push(id);
851
1110
  }
852
1111
  }
853
- // BFS from each non-tsx changed module up to tsx/jsx boundaries
854
- const boundaries = new Set();
1112
+ // Pass 1: BFS from each non-tsx changed module up to tsx/jsx
1113
+ // boundaries. These get re-imported below so solid-refresh's
1114
+ // inline patchRegistry runs and (best-effort) swaps the proxy
1115
+ // signals for any components defined in those tsx boundaries.
855
1116
  for (const id of drained) {
856
1117
  if (/\.(tsx|jsx)$/i.test(id))
857
1118
  continue; // already self-accepting
@@ -875,6 +1136,51 @@ async function processQueue() {
875
1136
  }
876
1137
  }
877
1138
  }
1139
+ // Pass 2: walk further from any tsx starting point (a tsx file
1140
+ // in `drained` OR a tsx boundary discovered in pass 1) to find
1141
+ // route files (`/src/routes/*.{tsx,jsx}`) that transitively
1142
+ // import them. Re-importing a route file refreshes its
1143
+ // `Route.options.component` to the freshly-imported reference
1144
+ // and the existing boundary loop below patches the live router
1145
+ // with that fresh reference.
1146
+ //
1147
+ // This is the key fix for "edit home.tsx → save → no visual
1148
+ // update": the old BFS skipped tsx files in `drained` (assuming
1149
+ // solid-refresh's in-place proxy patch was sufficient), but in
1150
+ // the universal-renderer + nested-context configuration that
1151
+ // patch does not always propagate to the visible page tree.
1152
+ // Adding the route file as a boundary lets us patch
1153
+ // `route.options.component` directly to a fresh module export,
1154
+ // which the framework subscriber then passes through to the
1155
+ // page remount — making the cycle robust to the proxy patch
1156
+ // silently failing.
1157
+ const tsxStarts = new Set();
1158
+ for (const id of drained) {
1159
+ if (/\.(tsx|jsx)$/i.test(id))
1160
+ tsxStarts.add(id);
1161
+ }
1162
+ for (const b of boundaries)
1163
+ tsxStarts.add(b);
1164
+ const ROUTE_FILE_RE = /\/src\/routes\/.+\.(tsx|jsx)$/i;
1165
+ for (const start of tsxStarts) {
1166
+ const visited = new Set();
1167
+ const queue = [start];
1168
+ while (queue.length) {
1169
+ const cur = queue.shift();
1170
+ if (visited.has(cur))
1171
+ continue;
1172
+ visited.add(cur);
1173
+ if (cur !== start && ROUTE_FILE_RE.test(cur)) {
1174
+ boundaries.add(cur);
1175
+ }
1176
+ const importers = reverseIndex.get(cur);
1177
+ if (!importers)
1178
+ continue;
1179
+ for (const imp of importers) {
1180
+ queue.push(imp);
1181
+ }
1182
+ }
1183
+ }
878
1184
  // Re-import each boundary so solid-refresh patchRegistry fires.
879
1185
  // For route files (TanStack Router), capture the new Route export
880
1186
  // and patch the router's existing route with the fresh loader.
@@ -885,7 +1191,7 @@ async function processQueue() {
885
1191
  const findRouter = () => {
886
1192
  if (discoveredRouter)
887
1193
  return discoveredRouter;
888
- const g = globalThis;
1194
+ const g = getGlobalScope();
889
1195
  if (g.__ns_router?.routesById)
890
1196
  return (discoveredRouter = g.__ns_router);
891
1197
  // Fallback: scan common global keys for router
@@ -924,6 +1230,15 @@ async function processQueue() {
924
1230
  }
925
1231
  return null;
926
1232
  };
1233
+ // Evict the boundary set so re-importing each .tsx
1234
+ // component actually picks up the new transitive
1235
+ // dependency code; without this V8 returns the
1236
+ // cached boundary module unchanged.
1237
+ const boundaryIds = Array.from(boundaries);
1238
+ const solidEvictUrls = buildEvictionUrls(boundaryIds);
1239
+ const solidEvicted = invalidateModulesByUrls(solidEvictUrls);
1240
+ if (VERBOSE)
1241
+ console.log(`[hmr][solid] eviction count=${solidEvictUrls.length} ok=${solidEvicted}`);
927
1242
  for (const id of boundaries) {
928
1243
  if (seen.has(id))
929
1244
  continue;
@@ -935,22 +1250,28 @@ async function processQueue() {
935
1250
  if (VERBOSE)
936
1251
  console.log('[hmr][solid] propagated to boundary', { id, url });
937
1252
  const mod = await import(/* @vite-ignore */ url);
938
- // Patch TanStack Router route loaders
1253
+ // Patch TanStack Router route options for any module
1254
+ // that exports a `Route`. We patch BOTH the component
1255
+ // and the loader (when present); components-only routes
1256
+ // were previously skipped because the gate required a
1257
+ // loader, which left their `options.component` pointing
1258
+ // at the stale module's exports after HMR.
939
1259
  try {
940
1260
  const newRoute = mod?.Route;
941
- if (newRoute?.options?.loader) {
1261
+ if (newRoute?.options) {
942
1262
  const router = findRouter();
943
1263
  const fullPath = boundaryToFullPath(id);
944
1264
  if (VERBOSE)
945
- console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, routesByIdKeys: router?.routesById ? Object.keys(router.routesById) : 'none' });
1265
+ console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, hasLoader: !!newRoute.options.loader, hasComponent: !!newRoute.options.component });
946
1266
  const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
947
1267
  if (existingRoute?.options) {
948
- existingRoute.options.loader = newRoute.options.loader;
1268
+ if (newRoute.options.loader)
1269
+ existingRoute.options.loader = newRoute.options.loader;
949
1270
  if (newRoute.options.component)
950
1271
  existingRoute.options.component = newRoute.options.component;
951
1272
  routesPatchCount++;
952
1273
  if (VERBOSE)
953
- console.log('[hmr][solid] patched route loader', existingRoute.id, 'fullPath=', fullPath);
1274
+ console.log('[hmr][solid] patched route', existingRoute.id, 'fullPath=', fullPath);
954
1275
  }
955
1276
  else if (VERBOSE) {
956
1277
  console.log('[hmr][solid] no matching route for fullPath', fullPath);
@@ -986,6 +1307,44 @@ async function processQueue() {
986
1307
  if (VERBOSE)
987
1308
  console.warn('[hmr][solid] propagation failed', e);
988
1309
  }
1310
+ // Notify any framework integrations (e.g.
1311
+ // `@nativescript/tanstack-router`) that a Solid HMR
1312
+ // cycle has completed. They use this signal to perform
1313
+ // framework-specific UI refresh (e.g. remount the active
1314
+ // router page) when solid-refresh's own reactive
1315
+ // propagation does not reach the visible tree under
1316
+ // the current renderer/context configuration.
1317
+ //
1318
+ // Boundaries include both the directly-changed tsx files
1319
+ // AND every tsx ancestor reachable via the reverse import
1320
+ // graph (route files in particular). The framework
1321
+ // listener uses the route-file boundaries to look up the
1322
+ // freshly-patched `route.options.component` and pass it
1323
+ // through to the page remount.
1324
+ try {
1325
+ const tsxChangedInDrained = drained.filter((id) => /\.(tsx|jsx)$/i.test(id));
1326
+ const allBoundaries = Array.from(new Set([...tsxChangedInDrained, ...boundaries]));
1327
+ nsSolidHmrEmit({
1328
+ kind: 'solid',
1329
+ changedFiles: drained.slice(),
1330
+ boundaries: allBoundaries,
1331
+ });
1332
+ }
1333
+ catch (err) {
1334
+ if (VERBOSE)
1335
+ console.warn('[hmr][solid] emit failed', err);
1336
+ }
1337
+ // Tell the overlay the cycle is done. solid-refresh's
1338
+ // inline patchRegistry has already flushed the new
1339
+ // component bodies into the live tree (the `case
1340
+ // 'solid'` block above re-imports each .tsx
1341
+ // boundary), so by the time we get here the user is
1342
+ // already looking at the new render. The 'complete'
1343
+ // frame surfaces the wall-clock total and triggers
1344
+ // the overlay's auto-hide.
1345
+ setUpdateOverlayStage('complete', {
1346
+ detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
1347
+ });
989
1348
  break;
990
1349
  }
991
1350
  case 'typescript': {
@@ -993,7 +1352,7 @@ async function processQueue() {
993
1352
  // This preserves the shell (Frame, ActionBar, etc.) that the app's
994
1353
  // own bootstrapping wires up via `Application.run`.
995
1354
  try {
996
- const g = globalThis;
1355
+ const g = getGlobalScope();
997
1356
  const App = getCore('Application') || g.Application;
998
1357
  if (!App || typeof App.resetRootView !== 'function') {
999
1358
  if (VERBOSE)
@@ -1019,10 +1378,9 @@ async function processQueue() {
1019
1378
  const rawContent = await resp.text();
1020
1379
  // Register under all nickname variants the module registry uses.
1021
1380
  // The bundler context registers XML as e.g., './main-page.xml' and 'main-page.xml'
1022
- const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1023
- let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1024
- if (relPath.startsWith(appVirtual))
1025
- relPath = relPath.slice(appVirtual.length);
1381
+ const relPath = toAppRelativePath(id);
1382
+ if (!relPath)
1383
+ continue;
1026
1384
  const nicknames = ['./' + relPath, relPath];
1027
1385
  // Also add without extension for CSS
1028
1386
  const extIdx = relPath.lastIndexOf('.');
@@ -1048,21 +1406,24 @@ async function processQueue() {
1048
1406
  }
1049
1407
  }
1050
1408
  }
1409
+ // Modal-aware refresh: pages currently PRESENTED AS MODALS must be
1410
+ // closed + re-presented in place — navigating the top frame to a
1411
+ // modal's page would push it as a frame page, and resetRootView
1412
+ // would dismiss the modal entirely. State comes from core's live
1413
+ // modal stack (_moduleName/_modalOptions/_modalParent); the
1414
+ // showModal wrap only backfills options on older cores.
1415
+ ensureModalTracking();
1416
+ const openModals = getOpenStringModuleModals();
1417
+ const changedModuleNames = new Set(drained.map(toAppModuleName).filter(Boolean));
1418
+ const modalsToReshow = openModals.filter((record) => changedModuleNames.has(record.moduleName));
1419
+ const reshowModuleNames = new Set(modalsToReshow.map((record) => record.moduleName));
1051
1420
  // Determine if we can navigate in-place to a changed page
1052
1421
  // instead of resetting all the way back to app-root.
1053
1422
  // This keeps the user on the page they're editing for faster iteration.
1054
1423
  const changedXmlPages = drained
1055
1424
  .filter((id) => /\.xml$/i.test(id))
1056
- .map((id) => {
1057
- const spec = normalizeSpec(id);
1058
- const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1059
- let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1060
- if (relPath.startsWith(appVirtual))
1061
- relPath = relPath.slice(appVirtual.length);
1062
- // Strip .xml extension to get the moduleName (e.g., 'pages/status-bar')
1063
- return relPath.replace(/\.xml$/i, '');
1064
- })
1065
- .filter((m) => m && m !== 'app-root');
1425
+ .map((id) => toAppModuleName(id))
1426
+ .filter((m) => m && m !== 'app-root' && !reshowModuleNames.has(m));
1066
1427
  // Resolve the topmost Frame from the bundled realm.
1067
1428
  // Frame.topmost() relies on an internal frameStack array, so we must
1068
1429
  // call it on the bundled-realm class. Multiple strategies to find it:
@@ -1098,7 +1459,7 @@ async function processQueue() {
1098
1459
  catch { }
1099
1460
  }
1100
1461
  if (VERBOSE)
1101
- console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame);
1462
+ console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame, 'modalsToReshow=', modalsToReshow.length);
1102
1463
  if (changedXmlPages.length > 0 && topFrame) {
1103
1464
  // Navigate the current frame to the changed page directly.
1104
1465
  // Use the last changed XML page (most specific).
@@ -1113,15 +1474,32 @@ async function processQueue() {
1113
1474
  App.resetRootView({ moduleName: 'app-root' });
1114
1475
  }
1115
1476
  }
1116
- else {
1477
+ else if (modalsToReshow.length === 0) {
1478
+ // No frame page to refresh and no open modal owns the change —
1479
+ // fall back to a full root reset. (Skipped when an open modal is
1480
+ // being re-presented below: resetRootView would dismiss it.)
1117
1481
  if (VERBOSE)
1118
1482
  console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
1119
1483
  App.resetRootView({ moduleName: 'app-root' });
1120
1484
  }
1485
+ // Re-present any open modals whose XML/code-behind changed. The
1486
+ // modules were already re-registered above (raw XML assets + fresh
1487
+ // code-behind exports), so the re-presented modal rebuilds from
1488
+ // the new content while the page beneath it stays put.
1489
+ for (const record of modalsToReshow) {
1490
+ await reshowOpenModal(record);
1491
+ }
1121
1492
  }
1122
1493
  catch (e) {
1123
1494
  console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
1124
1495
  }
1496
+ // Tell the overlay the cycle is done — same as the solid path
1497
+ // above. Without this the applying overlay sticks at
1498
+ // 'received' (5%) forever even though the in-place navigate /
1499
+ // resetRootView already applied the update.
1500
+ setUpdateOverlayStage('complete', {
1501
+ detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
1502
+ });
1125
1503
  break;
1126
1504
  }
1127
1505
  }
@@ -1129,11 +1507,37 @@ async function processQueue() {
1129
1507
  finally {
1130
1508
  processingQueue = false;
1131
1509
  processingPromise = null;
1510
+ // If a delta arrived mid-cycle (pushed onto changedQueue while
1511
+ // processingQueue was still true, so its processQueue() call early-
1512
+ // returned), re-drain — otherwise that save is stranded until the next
1513
+ // delta. Deferred via setTimeout to avoid synchronous re-entrancy as
1514
+ // this promise settles.
1515
+ if (changedQueue.length) {
1516
+ setTimeout(() => {
1517
+ try {
1518
+ processQueue();
1519
+ }
1520
+ catch { }
1521
+ }, 0);
1522
+ }
1132
1523
  }
1133
1524
  })();
1134
1525
  return processingPromise;
1135
1526
  }
1136
1527
  let hmrSocket = null;
1528
+ // Single reconnect timer. Overlapping close/timeout events used to each schedule
1529
+ // their own `setTimeout(connectHmr, …)`, stacking multiple pending reconnects
1530
+ // that could spawn (and leak) duplicate sockets/listeners. Route all reconnect
1531
+ // scheduling through here so only one is ever pending.
1532
+ let reconnectTimer = null;
1533
+ function scheduleReconnect(delayMs) {
1534
+ if (reconnectTimer)
1535
+ clearTimeout(reconnectTimer);
1536
+ reconnectTimer = setTimeout(() => {
1537
+ reconnectTimer = null;
1538
+ connectHmr();
1539
+ }, delayMs);
1540
+ }
1137
1541
  // Track server-announced batches for each version so we can import in-order client-side
1138
1542
  const txnClientBatches = new Map();
1139
1543
  // Public hook for NativeScript runtime to call from ImportModuleDynamicallyCallback later.
@@ -1161,10 +1565,16 @@ function connectHmr() {
1161
1565
  if (hmrSocket?.readyState === WebSocket.OPEN)
1162
1566
  return;
1163
1567
  if (hmrSocket?.readyState === WebSocket.CONNECTING) {
1164
- if (__NS_ENV_VERBOSE__)
1568
+ if (VERBOSE)
1165
1569
  console.log('[hmr-client] Already connecting to HMR WebSocket, skipping');
1166
1570
  return;
1167
1571
  }
1572
+ // A reconnect fired (or a manual connect raced one) — cancel any other pending
1573
+ // reconnect so we don't end up with overlapping connect attempts.
1574
+ if (reconnectTimer) {
1575
+ clearTimeout(reconnectTimer);
1576
+ reconnectTimer = null;
1577
+ }
1168
1578
  try {
1169
1579
  globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
1170
1580
  }
@@ -1172,7 +1582,7 @@ function connectHmr() {
1172
1582
  const overlayStage = hasOpenedHmrSocket ? 'reconnecting' : 'connecting';
1173
1583
  const baseUrl = getHMRWsUrl() || 'ws://localhost:5173/ns-hmr';
1174
1584
  const buildCandidates = (url) => {
1175
- let candidates = [];
1585
+ const candidates = [];
1176
1586
  try {
1177
1587
  const u = new URL(url);
1178
1588
  const proto = u.protocol === 'wss:' ? ['wss'] : ['ws'];
@@ -1180,7 +1590,7 @@ function connectHmr() {
1180
1590
  // Build ordered host candidates with preference to the active HTTP origin
1181
1591
  const orderedHosts = [];
1182
1592
  try {
1183
- const g = globalThis;
1593
+ const g = getGlobalScope();
1184
1594
  const httpOrigin = g && typeof g.__NS_HTTP_ORIGIN__ === 'string' ? g.__NS_HTTP_ORIGIN__ : undefined;
1185
1595
  if (httpOrigin) {
1186
1596
  try {
@@ -1231,7 +1641,7 @@ function connectHmr() {
1231
1641
  if (idx >= candidates.length) {
1232
1642
  showConnectionOverlayNow('offline', 'Waiting for the Vite websocket to come back.');
1233
1643
  console.warn('[hmr-client] All WS candidates failed:', candidates.join(', '));
1234
- setTimeout(connectHmr, 1500);
1644
+ scheduleReconnect(1500);
1235
1645
  return;
1236
1646
  }
1237
1647
  const url = candidates[idx++];
@@ -1243,7 +1653,7 @@ function connectHmr() {
1243
1653
  scheduleConnectionOverlay(overlayStage, connectionDetail);
1244
1654
  }
1245
1655
  try {
1246
- if (__NS_ENV_VERBOSE__)
1656
+ if (VERBOSE)
1247
1657
  console.log('[hmr-client] Connecting to HMR WebSocket:', url);
1248
1658
  const sock = new WebSocket(url);
1249
1659
  hmrSocket = sock;
@@ -1273,7 +1683,16 @@ function connectHmr() {
1273
1683
  if (connectionOverlayVisible) {
1274
1684
  showConnectionOverlayNow('synchronizing', 'Connected. Synchronizing the HMR graph.');
1275
1685
  }
1276
- VERBOSE && console.log('[hmr-client] Connected to HMR WebSocket');
1686
+ if (VERBOSE)
1687
+ console.log('[hmr-client] Connected to HMR WebSocket');
1688
+ // Print the active module reload mode once on first
1689
+ // successful connect so the user can correlate HMR latency
1690
+ // with runtime capability without grepping for protocol
1691
+ // details. The banner is verbose-gated.
1692
+ try {
1693
+ emitHmrModeBannerOnce();
1694
+ }
1695
+ catch { }
1277
1696
  };
1278
1697
  sock.onmessage = handleHmrMessage;
1279
1698
  sock.onerror = (error) => {
@@ -1297,7 +1716,7 @@ function connectHmr() {
1297
1716
  console.log('[hmr-client] WebSocket closed (code', ev?.code, '), will reconnect…');
1298
1717
  scheduleConnectionOverlay('reconnecting', 'The websocket closed. Waiting to reconnect.', 700);
1299
1718
  // try to reconnect with full candidate list again
1300
- setTimeout(connectHmr, 1000);
1719
+ scheduleReconnect(1000);
1301
1720
  }
1302
1721
  };
1303
1722
  }
@@ -1333,10 +1752,26 @@ async function handleHmrMessage(ev) {
1333
1752
  catch { }
1334
1753
  }
1335
1754
  if (msg) {
1755
+ // `ns:hmr-pending` is a fire-and-forget UX hint emitted by the
1756
+ // server at the START of handleHotUpdate. We drive the
1757
+ // HMR-applying overlay's 'received' frame here (synchronously),
1758
+ // well before the authoritative payload (`ns:angular-update` /
1759
+ // `ns:css-updates`) lands. Skip running any other handlers —
1760
+ // the pending message has no module payload and intentionally
1761
+ // does not bump the graph version.
1762
+ if (msg.type === 'ns:hmr-pending' && typeof msg.path === 'string') {
1763
+ setHmrPendingOverlay(msg.path);
1764
+ return;
1765
+ }
1766
+ // The per-flavor client strategy is loaded by a dynamic import(); make
1767
+ // sure it has resolved (and `install()` has run) before any handler that
1768
+ // delegates through it. After the first message this is an already-settled
1769
+ // promise (microtask only); for Solid/TypeScript it is `Promise.resolve()`.
1770
+ await CLIENT_STRATEGY_READY;
1336
1771
  if (msg.type === 'ns:hmr-full-graph') {
1337
1772
  // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
1338
1773
  try {
1339
- const g = globalThis;
1774
+ const g = getGlobalScope();
1340
1775
  g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
1341
1776
  }
1342
1777
  catch { }
@@ -1402,6 +1837,14 @@ async function handleHmrMessage(ev) {
1402
1837
  });
1403
1838
  if (toReimport.length && VERBOSE)
1404
1839
  console.log('[hmr][full-graph] inferred changed modules; re-importing', toReimport);
1840
+ // Evict the inferred changed set before re-importing.
1841
+ // See `processQueue` for the architectural rationale; the
1842
+ // full-graph code path is the resync fallback (server chose
1843
+ // not to send a delta) and shares the same V8 cache pitfall.
1844
+ const fgEvictUrls = buildEvictionUrls(toReimport);
1845
+ const fgEvicted = invalidateModulesByUrls(fgEvictUrls);
1846
+ if (VERBOSE)
1847
+ console.log(`[hmr][full-graph] eviction count=${fgEvictUrls.length} ok=${fgEvicted}`);
1405
1848
  for (const id of toReimport) {
1406
1849
  try {
1407
1850
  const spec = normalizeSpec(id);
@@ -1453,7 +1896,7 @@ async function handleHmrMessage(ev) {
1453
1896
  if (msg.type === 'ns:hmr-delta') {
1454
1897
  // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
1455
1898
  try {
1456
- const g = globalThis;
1899
+ const g = getGlobalScope();
1457
1900
  g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
1458
1901
  }
1459
1902
  catch { }
@@ -1475,10 +1918,108 @@ async function handleHmrMessage(ev) {
1475
1918
  return;
1476
1919
  }
1477
1920
  else {
1921
+ // Vite custom-event dispatch.
1922
+ //
1923
+ // `server.ws.send('event-name', payload)` from any Vite plugin lands
1924
+ // on the wire as `{ type: 'custom', event: 'event-name', data: payload }`.
1925
+ // On the web, Vite's stock client owns a `customListenersMap` that
1926
+ // fires every `import.meta.hot.on('event-name', cb)` callback. We
1927
+ // don't run Vite's stock client on device — the iOS runtime owns
1928
+ // the listener registry via `__NS_DISPATCH_HOT_EVENT__` (the
1929
+ // counterpart to `import.meta.hot.on` populated by user code +
1930
+ // compiled Angular components). Forwarding `type: 'custom'` here
1931
+ // is the only thing standing between server-emitted events and
1932
+ // the listeners they were meant for.
1933
+ //
1934
+ // `angular:component-update` is the canonical example. Analog's
1935
+ // plugin sends it on `.html` / component-style edits; the
1936
+ // compiled component `.mjs` registered a listener that
1937
+ // dynamic-imports `/@ng/component?c=<id>&t=<ts>` and calls
1938
+ // `ɵɵreplaceMetadata` on the live class — swapping the template
1939
+ // definition AND walking live `LView`s to recreate matching views
1940
+ // in-place. The page stays mounted and only the changed bits
1941
+ // re-render. We MUST `return` after dispatch so the reboot path
1942
+ // (`handleAngularHotUpdateMessage` → `__reboot_ng_modules__`)
1943
+ // never runs for these updates — that's the whole point of the
1944
+ // component-replacement pipeline.
1945
+ //
1946
+ // All other custom events are forwarded but NOT short-circuited
1947
+ // (Vite spec: custom events are additive — they don't replace
1948
+ // any framework-specific handling). The reboot path falls through
1949
+ // for `ns:angular-update` (the legacy/`.ts`-edit broadcast) and
1950
+ // for any framework not yet using the in-place replacement path.
1951
+ if (msg.type === 'custom' && typeof msg.event === 'string') {
1952
+ // Dispatch every Vite "custom" event through the runtime's
1953
+ // `__NS_DISPATCH_HOT_EVENT__` bridge so `import.meta.hot.on(event, cb)`
1954
+ // callbacks fire on the device. Critical contract: this is the
1955
+ // ONLY route by which Analog's `angular:component-update` reaches
1956
+ // the compiled component's `(d) => d.id === id && Component_HmrLoad(...)`
1957
+ // listener — without it, server-side broadcasts log green
1958
+ // (`(client) hmr update`) while the device sees nothing happen.
1959
+ //
1960
+ // Diagnostic policy: log "no dispatcher" loud (boot-time rt-bridge
1961
+ // failure), and listener exceptions loud (compiled HmrLoad
1962
+ // fetch/parse error). Successful dispatches are silent — the
1963
+ // runtime's `[import.meta.hot] dispatch summary` line carries
1964
+ // the per-event match-count diagnostic.
1965
+ try {
1966
+ const dispatch = globalThis.__NS_DISPATCH_HOT_EVENT__;
1967
+ if (typeof dispatch === 'function') {
1968
+ dispatch(msg.event, msg.data);
1969
+ }
1970
+ else {
1971
+ console.warn(`[hmr-client][custom] no __NS_DISPATCH_HOT_EVENT__ available for '${msg.event}'`);
1972
+ }
1973
+ }
1974
+ catch (err) {
1975
+ console.warn('[hmr-client][custom] dispatch threw for', msg.event, err);
1976
+ }
1977
+ if (msg.event === 'angular:component-update') {
1978
+ if (VERBOSE)
1979
+ console.log('[hmr-client][custom] dispatched angular:component-update — skipping reboot path');
1980
+ // Walk the apply-progress overlay through its
1981
+ // remaining stages for the in-place template-swap
1982
+ // path. The full reboot path
1983
+ // (`handleAngularHotUpdateMessage`) drives the
1984
+ // overlay itself ('received' → 'evicting' →
1985
+ // 'reimporting' → 'rebooting' → 'complete'); the
1986
+ // in-place path bypasses that handler entirely
1987
+ // because the work happens inside Angular's
1988
+ // `ɵɵreplaceMetadata` after the runtime forwards the
1989
+ // `angular:component-update` event to the compiled
1990
+ // component's listener. Without this update the
1991
+ // overlay would freeze at 5% ('received') even
1992
+ // though the visual swap completes a few frames
1993
+ // later — exactly the "Preparing update (5%)" stuck
1994
+ // frame we have been chasing.
1995
+ //
1996
+ // We transition straight to 'reimporting' to
1997
+ // communicate that metadata is being fetched (the
1998
+ // runtime listener fires `__ns_import('/@ng/component?c=...&t=...')`),
1999
+ // then schedule 'complete' on the next macrotask so
2000
+ // the auto-hide timer kicks in. The actual
2001
+ // template swap is fire-and-forget from this point;
2002
+ // the user sees the overlay close at the same time
2003
+ // as Angular re-renders the bound text/structure.
2004
+ try {
2005
+ const filePath = typeof msg.data?.id === 'string' ? decodeURIComponent(msg.data.id).split('@')[0] : undefined;
2006
+ const detail = filePath ? `Applying template update to ${filePath}` : 'Applying template update';
2007
+ setUpdateOverlayStage('reimporting', { detail });
2008
+ setTimeout(() => {
2009
+ try {
2010
+ setUpdateOverlayStage('complete', { detail: filePath ? `Updated ${filePath}` : 'Update applied' });
2011
+ }
2012
+ catch { }
2013
+ }, 16);
2014
+ }
2015
+ catch { }
2016
+ return;
2017
+ }
2018
+ }
1478
2019
  if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
1479
2020
  setGraphVersion(Number(msg.version || getGraphVersion() || 0));
1480
2021
  }
1481
- if (await handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
2022
+ if (CLIENT_STRATEGY?.handleHotUpdateMessage && (await CLIENT_STRATEGY.handleHotUpdateMessage(msg, { getCore, verbose: VERBOSE, performResetRoot, getOverlay: getHmrOverlayApi }))) {
1482
2023
  return;
1483
2024
  }
1484
2025
  }
@@ -1512,27 +2053,47 @@ async function handleHmrMessage(ev) {
1512
2053
  return;
1513
2054
  }
1514
2055
  if (msg.type === 'ns:css-updates' && Array.isArray(msg.updates)) {
2056
+ // Drive the HMR-applying overlay past the 'received' (5%) frame
2057
+ // that `ns:hmr-pending` set earlier in the cycle. Without this
2058
+ // the overlay sticks at "Preparing update" forever for CSS-only
2059
+ // edits because `handleCssUpdates` is a leaf — there's no
2060
+ // downstream module-evaluation path that would hit the queue's
2061
+ // 'complete' transition.
2062
+ const cssCount = msg.updates.length;
2063
+ try {
2064
+ setUpdateOverlayStage('reimporting', { detail: buildCssApplyingDetail(cssCount) });
2065
+ }
2066
+ catch { }
1515
2067
  try {
1516
2068
  const origin = msg.origin || getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
1517
2069
  await handleCssUpdates(msg.updates, origin);
2070
+ try {
2071
+ setUpdateOverlayStage('complete', { detail: buildCssAppliedDetail(cssCount) });
2072
+ }
2073
+ catch { }
1518
2074
  return;
1519
2075
  }
1520
2076
  catch (e) {
1521
2077
  console.warn('[hmr-client] CSS updates handling failed:', e);
2078
+ try {
2079
+ setUpdateOverlayStage('complete', { detail: 'CSS update failed' });
2080
+ }
2081
+ catch { }
1522
2082
  return;
1523
2083
  }
1524
2084
  }
1525
2085
  if (msg.type === 'ns:vue-sfc-registry') {
1526
- handleVueSfcRegistry(msg);
2086
+ CLIENT_STRATEGY?.handleSfcRegistry?.(msg);
1527
2087
  return;
1528
2088
  }
1529
2089
  if (msg.type === 'ns:vue-sfc-registry-update') {
1530
2090
  if (typeof msg.version === 'number')
1531
2091
  setGraphVersion(msg.version);
1532
- const comp = await handleVueSfcRegistryUpdate(msg, getGraphVersion());
1533
- if (comp) {
1534
- await performResetRoot(comp);
1535
- }
2092
+ // `ns:hmr-pending` already set the overlay to 'received' (5%). The Vue
2093
+ // strategy walks 'evicting' → 'reimporting' → 'rebooting' → 'complete'
2094
+ // around the SFC load + reset so the toast always lands on 'complete'
2095
+ // (or a failure detail) and the auto-hide timer can dismiss it.
2096
+ await CLIENT_STRATEGY?.handleSfcRegistryUpdate?.(msg, getGraphVersion(), { getCore, verbose: VERBOSE, performResetRoot, getOverlay: getHmrOverlayApi });
1536
2097
  return;
1537
2098
  }
1538
2099
  }
@@ -1552,9 +2113,9 @@ function normalizeComponent(input, nameHint) {
1552
2113
  }
1553
2114
  // If provided a render function, wrap with defineComponent
1554
2115
  if (typeof input === 'function') {
1555
- ensureVueGlobals();
1556
- const comp = globalThis.defineComponent
1557
- ? globalThis.defineComponent({
2116
+ CLIENT_STRATEGY?.beforeNavigateBuild?.();
2117
+ const comp = getGlobalScope().defineComponent
2118
+ ? getGlobalScope().defineComponent({
1558
2119
  name: nameHint || input.name || 'AnonymousSFC',
1559
2120
  render: input,
1560
2121
  })
@@ -1563,9 +2124,9 @@ function normalizeComponent(input, nameHint) {
1563
2124
  }
1564
2125
  // If object has a render function property
1565
2126
  if (input?.render && typeof input.render === 'function') {
1566
- ensureVueGlobals();
1567
- const comp = globalThis.defineComponent
1568
- ? globalThis.defineComponent({
2127
+ CLIENT_STRATEGY?.beforeNavigateBuild?.();
2128
+ const comp = getGlobalScope().defineComponent
2129
+ ? getGlobalScope().defineComponent({
1569
2130
  name: nameHint || input.name || 'AnonymousSFC',
1570
2131
  render: input.render,
1571
2132
  })
@@ -1621,35 +2182,32 @@ async function performResetRoot(newComponent) {
1621
2182
  if (cachedRoot)
1622
2183
  return cachedRoot;
1623
2184
  try {
1624
- switch (TARGET_FLAVOR) {
1625
- case 'vue':
1626
- cachedRoot = getRootForVue(newComponent, state);
1627
- break;
1628
- case 'typescript': {
1629
- // For TS flavor, treat the component as a factory or direct NS view.
1630
- let root = null;
1631
- try {
1632
- if (typeof newComponent === 'function') {
1633
- root = newComponent();
1634
- }
1635
- else {
1636
- root = newComponent;
1637
- }
2185
+ if (CLIENT_STRATEGY?.createRoot) {
2186
+ cachedRoot = CLIENT_STRATEGY.createRoot(newComponent, state);
2187
+ }
2188
+ else if (TARGET_FLAVOR === 'typescript') {
2189
+ // For TS flavor, treat the component as a factory or direct NS view.
2190
+ let root = null;
2191
+ try {
2192
+ if (typeof newComponent === 'function') {
2193
+ root = newComponent();
1638
2194
  }
1639
- catch (e) {
1640
- console.warn('[hmr-client][ts] root factory invocation failed', e);
2195
+ else {
2196
+ root = newComponent;
1641
2197
  }
1642
- cachedRoot = root || {};
1643
- // Heuristic: if the root "looks" like a Frame, prefer frame semantics
1644
- try {
1645
- const name = String(cachedRoot?.constructor?.name || '').replace(/^_+/, '');
1646
- if (/^Frame(\$\d+)?$/.test(name)) {
1647
- rootKind = 'frame';
1648
- }
2198
+ }
2199
+ catch (e) {
2200
+ console.warn('[hmr-client][ts] root factory invocation failed', e);
2201
+ }
2202
+ cachedRoot = root || {};
2203
+ // Heuristic: if the root "looks" like a Frame, prefer frame semantics
2204
+ try {
2205
+ const name = String(cachedRoot?.constructor?.name || '').replace(/^_+/, '');
2206
+ if (/^Frame(\$\d+)?$/.test(name)) {
2207
+ rootKind = 'frame';
1649
2208
  }
1650
- catch { }
1651
- break;
1652
2209
  }
2210
+ catch { }
1653
2211
  }
1654
2212
  return cachedRoot;
1655
2213
  }
@@ -1667,7 +2225,7 @@ async function performResetRoot(newComponent) {
1667
2225
  return factory;
1668
2226
  }
1669
2227
  // Android readiness before any root changes
1670
- const App = getCore('Application') || globalThis.Application;
2228
+ const App = getCore('Application') || getGlobalScope().Application;
1671
2229
  const isAndroid = !!(App && App.android !== undefined);
1672
2230
  if (isAndroid) {
1673
2231
  const isReady = () => {
@@ -1769,7 +2327,7 @@ async function performResetRoot(newComponent) {
1769
2327
  }
1770
2328
  catch { }
1771
2329
  try {
1772
- const AppAny = getCore('Application') || globalThis.Application;
2330
+ const AppAny = getCore('Application') || getGlobalScope().Application;
1773
2331
  isIOS = !!(AppAny && AppAny.ios !== undefined);
1774
2332
  }
1775
2333
  catch { }
@@ -1779,7 +2337,7 @@ async function performResetRoot(newComponent) {
1779
2337
  // - Otherwise (subsequent HMR updates with an authoritative Frame already in place), re-use the
1780
2338
  // current app Frame and navigate to the new Page. This avoids a brief flash that can occur
1781
2339
  // when swapping the entire root view on Android. The placeholder is never involved here.
1782
- const gAnyForPolicy = globalThis;
2340
+ const gAnyForPolicy = getGlobalScope();
1783
2341
  const placeholderFrame = (() => {
1784
2342
  try {
1785
2343
  return gAnyForPolicy.__NS_DEV_PLACEHOLDER_ROOT_VIEW__ || null;
@@ -1794,7 +2352,14 @@ async function performResetRoot(newComponent) {
1794
2352
  }
1795
2353
  catch { }
1796
2354
  const isAuthoritativeFrame = !!existingAppFrame && existingAppFrame !== placeholderFrame;
1797
- if (!hadPlaceholder && !isFrameRoot && isAuthoritativeFrame && typeof existingAppFrame.navigate === 'function') {
2355
+ // Vue: skip the in-place navigate path. After `app.mount(NSVRoot)` in getRootForVue the
2356
+ // new Page already has a parent (the freshly-constructed NSVRoot), so an attempt to navigate
2357
+ // the existing app Frame to that same Page completes silently without ever rebinding the
2358
+ // page to the Frame — the screen keeps showing the previous render. resetRootView with a
2359
+ // fresh Frame correctly reparents the Page and is the proven path that produces visible
2360
+ // in-place updates for SFC HMR cycles. Non-Vue flavors keep the legacy navigate fast path.
2361
+ const allowNavigateFastPath = CLIENT_STRATEGY?.allowNavigateFastPath ?? true;
2362
+ if (allowNavigateFastPath && !hadPlaceholder && !isFrameRoot && isAuthoritativeFrame && typeof existingAppFrame.navigate === 'function') {
1798
2363
  try {
1799
2364
  const navEntry = {
1800
2365
  create: () => preparedRoot,
@@ -1813,7 +2378,7 @@ async function performResetRoot(newComponent) {
1813
2378
  console.log('[hmr-client] full root replacement via resetRootView (placeholder will be discarded)', { isFrameRoot, isIOS, hadPlaceholder });
1814
2379
  // Fallback or preferred path: resetRootView with a creator that builds a fresh Frame and navigates to the new Page
1815
2380
  try {
1816
- const App2 = getCore('Application') || globalThis.Application;
2381
+ const App2 = getCore('Application') || getGlobalScope().Application;
1817
2382
  if (!App2 || typeof App2.resetRootView !== 'function') {
1818
2383
  console.warn('[hmr-client] Application.resetRootView unavailable');
1819
2384
  return false;
@@ -1832,7 +2397,7 @@ async function performResetRoot(newComponent) {
1832
2397
  if (VERBOSE)
1833
2398
  console.warn('[hmr-client] iOS Application.window is boolean false; attempting to clear cached window');
1834
2399
  try {
1835
- const g = globalThis;
2400
+ const g = getGlobalScope();
1836
2401
  const reg = g.__nsVendorRegistry;
1837
2402
  const req = reg?.get ? g.__nsVendorRequire || g.__nsRequire || g.require : g.__nsRequire || g.require;
1838
2403
  let helpers = null;
@@ -1936,6 +2501,20 @@ export function initHmrClient(opts) {
1936
2501
  }
1937
2502
  g.__NS_HMR_CLIENT_ACTIVE__ = true;
1938
2503
  ensureCoreAliasesOnGlobalThis();
2504
+ // XML flavor: record string-module modals from the moment the client is up
2505
+ // so an already-open modal can be re-presented when its files change.
2506
+ // Installed at init (not first-update time) because the wrap can only
2507
+ // observe showModal calls made AFTER it lands. Retried briefly because
2508
+ // getCore('View') may not resolve until the vendor realm finishes booting.
2509
+ if (TARGET_FLAVOR === 'typescript') {
2510
+ const tryInstallModalTracking = (attempts) => {
2511
+ ensureModalTracking();
2512
+ if (!modalTrackingInstalled && attempts > 0) {
2513
+ setTimeout(() => tryInstallModalTracking(attempts - 1), 250);
2514
+ }
2515
+ };
2516
+ tryInstallModalTracking(40);
2517
+ }
1939
2518
  // Defer WebSocket connection until boot completes to avoid native V8 crashes
1940
2519
  // caused by concurrent WebSocket message handling + HTTP fetch during early startup.
1941
2520
  // The WebSocket is only needed for HMR updates, not the initial boot sequence.
@@ -1957,12 +2536,9 @@ export function initHmrClient(opts) {
1957
2536
  console.log('[hmr-client] deferring WebSocket connection until boot completes');
1958
2537
  setTimeout(waitForBoot, 100);
1959
2538
  }
1960
- // Best-effort: install back wrapper even before first remount; original root may be captured later
1961
- switch (TARGET_FLAVOR) {
1962
- case 'vue':
1963
- ensureBackWrapperInstalled(performResetRoot, getCore);
1964
- break;
1965
- }
2539
+ // Best-effort: install back wrapper even before first remount; original root may be captured later.
2540
+ // Deferred until the dynamically-imported strategy resolves.
2541
+ void CLIENT_STRATEGY_READY.then(() => CLIENT_STRATEGY?.installBackWrapper?.(performResetRoot, getCore));
1966
2542
  }
1967
2543
  export default function startViteHMR(opts) {
1968
2544
  if (VERBOSE)