@nativescript/vite 8.0.0-alpha.2 → 8.0.0-alpha.20

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 (209) hide show
  1. package/configuration/angular.d.ts +34 -1
  2. package/configuration/angular.js +380 -34
  3. package/configuration/angular.js.map +1 -1
  4. package/configuration/base.js +171 -7
  5. package/configuration/base.js.map +1 -1
  6. package/configuration/solid.js +27 -1
  7. package/configuration/solid.js.map +1 -1
  8. package/configuration/typescript.js +1 -1
  9. package/configuration/typescript.js.map +1 -1
  10. package/helpers/angular/angular-linker.js +3 -12
  11. package/helpers/angular/angular-linker.js.map +1 -1
  12. package/helpers/angular/inject-component-hmr-registration.d.ts +112 -0
  13. package/helpers/angular/inject-component-hmr-registration.js +359 -0
  14. package/helpers/angular/inject-component-hmr-registration.js.map +1 -0
  15. package/helpers/angular/inject-hmr-vite-ignore.d.ts +75 -0
  16. package/helpers/angular/inject-hmr-vite-ignore.js +288 -0
  17. package/helpers/angular/inject-hmr-vite-ignore.js.map +1 -0
  18. package/helpers/angular/util.d.ts +1 -0
  19. package/helpers/angular/util.js +88 -0
  20. package/helpers/angular/util.js.map +1 -1
  21. package/helpers/commonjs-plugins.d.ts +5 -2
  22. package/helpers/commonjs-plugins.js +126 -0
  23. package/helpers/commonjs-plugins.js.map +1 -1
  24. package/helpers/config-as-json.js +10 -0
  25. package/helpers/config-as-json.js.map +1 -1
  26. package/helpers/dev-host.d.ts +274 -0
  27. package/helpers/dev-host.js +491 -0
  28. package/helpers/dev-host.js.map +1 -0
  29. package/helpers/global-defines.d.ts +51 -0
  30. package/helpers/global-defines.js +77 -0
  31. package/helpers/global-defines.js.map +1 -1
  32. package/helpers/logging.d.ts +1 -0
  33. package/helpers/logging.js +63 -3
  34. package/helpers/logging.js.map +1 -1
  35. package/helpers/main-entry.d.ts +3 -1
  36. package/helpers/main-entry.js +450 -125
  37. package/helpers/main-entry.js.map +1 -1
  38. package/helpers/nativeclass-transformer-plugin.d.ts +9 -2
  39. package/helpers/nativeclass-transformer-plugin.js +157 -14
  40. package/helpers/nativeclass-transformer-plugin.js.map +1 -1
  41. package/helpers/ns-core-url.d.ts +88 -0
  42. package/helpers/ns-core-url.js +191 -0
  43. package/helpers/ns-core-url.js.map +1 -0
  44. package/helpers/prelink-angular.js +1 -4
  45. package/helpers/prelink-angular.js.map +1 -1
  46. package/helpers/project.d.ts +35 -0
  47. package/helpers/project.js +120 -2
  48. package/helpers/project.js.map +1 -1
  49. package/helpers/resolver.js +9 -1
  50. package/helpers/resolver.js.map +1 -1
  51. package/helpers/solid-jsx-deps.d.ts +15 -0
  52. package/helpers/solid-jsx-deps.js +178 -0
  53. package/helpers/solid-jsx-deps.js.map +1 -0
  54. package/helpers/ts-config-paths.js +50 -2
  55. package/helpers/ts-config-paths.js.map +1 -1
  56. package/helpers/workers.d.ts +20 -19
  57. package/helpers/workers.js +620 -3
  58. package/helpers/workers.js.map +1 -1
  59. package/hmr/client/css-handler.d.ts +1 -0
  60. package/hmr/client/css-handler.js +34 -5
  61. package/hmr/client/css-handler.js.map +1 -1
  62. package/hmr/client/css-update-overlay.d.ts +18 -0
  63. package/hmr/client/css-update-overlay.js +27 -0
  64. package/hmr/client/css-update-overlay.js.map +1 -0
  65. package/hmr/client/hmr-pending-overlay.d.ts +27 -0
  66. package/hmr/client/hmr-pending-overlay.js +50 -0
  67. package/hmr/client/hmr-pending-overlay.js.map +1 -0
  68. package/hmr/client/index.js +483 -33
  69. package/hmr/client/index.js.map +1 -1
  70. package/hmr/client/utils.d.ts +5 -0
  71. package/hmr/client/utils.js +283 -12
  72. package/hmr/client/utils.js.map +1 -1
  73. package/hmr/client/vue-sfc-update-overlay.d.ts +82 -0
  74. package/hmr/client/vue-sfc-update-overlay.js +133 -0
  75. package/hmr/client/vue-sfc-update-overlay.js.map +1 -0
  76. package/hmr/entry-runtime.d.ts +2 -1
  77. package/hmr/entry-runtime.js +253 -66
  78. package/hmr/entry-runtime.js.map +1 -1
  79. package/hmr/frameworks/angular/client/index.d.ts +3 -1
  80. package/hmr/frameworks/angular/client/index.js +802 -10
  81. package/hmr/frameworks/angular/client/index.js.map +1 -1
  82. package/hmr/frameworks/angular/server/linker.js +1 -4
  83. package/hmr/frameworks/angular/server/linker.js.map +1 -1
  84. package/hmr/frameworks/angular/server/strategy.js +30 -6
  85. package/hmr/frameworks/angular/server/strategy.js.map +1 -1
  86. package/hmr/frameworks/typescript/server/strategy.js +8 -2
  87. package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
  88. package/hmr/frameworks/vue/client/index.js +18 -42
  89. package/hmr/frameworks/vue/client/index.js.map +1 -1
  90. package/hmr/helpers/ast-normalizer.js +52 -5
  91. package/hmr/helpers/ast-normalizer.js.map +1 -1
  92. package/hmr/helpers/cjs-named-exports.d.ts +23 -0
  93. package/hmr/helpers/cjs-named-exports.js +152 -0
  94. package/hmr/helpers/cjs-named-exports.js.map +1 -0
  95. package/hmr/helpers/package-exports.d.ts +16 -0
  96. package/hmr/helpers/package-exports.js +396 -0
  97. package/hmr/helpers/package-exports.js.map +1 -0
  98. package/hmr/server/constants.js +13 -4
  99. package/hmr/server/constants.js.map +1 -1
  100. package/hmr/server/core-sanitize.d.ts +93 -8
  101. package/hmr/server/core-sanitize.js +222 -49
  102. package/hmr/server/core-sanitize.js.map +1 -1
  103. package/hmr/server/import-map.js +80 -22
  104. package/hmr/server/import-map.js.map +1 -1
  105. package/hmr/server/index.d.ts +2 -1
  106. package/hmr/server/index.js.map +1 -1
  107. package/hmr/server/ns-core-cjs-shape.d.ts +204 -0
  108. package/hmr/server/ns-core-cjs-shape.js +271 -0
  109. package/hmr/server/ns-core-cjs-shape.js.map +1 -0
  110. package/hmr/server/ns-rt-bridge.d.ts +51 -0
  111. package/hmr/server/ns-rt-bridge.js +131 -0
  112. package/hmr/server/ns-rt-bridge.js.map +1 -0
  113. package/hmr/server/perf-instrumentation.d.ts +114 -0
  114. package/hmr/server/perf-instrumentation.js +195 -0
  115. package/hmr/server/perf-instrumentation.js.map +1 -0
  116. package/hmr/server/runtime-graph-filter.d.ts +5 -0
  117. package/hmr/server/runtime-graph-filter.js +21 -0
  118. package/hmr/server/runtime-graph-filter.js.map +1 -0
  119. package/hmr/server/shared-transform-request.d.ts +12 -0
  120. package/hmr/server/shared-transform-request.js +144 -0
  121. package/hmr/server/shared-transform-request.js.map +1 -0
  122. package/hmr/server/vite-plugin.d.ts +21 -1
  123. package/hmr/server/vite-plugin.js +497 -58
  124. package/hmr/server/vite-plugin.js.map +1 -1
  125. package/hmr/server/websocket-angular-entry.d.ts +2 -0
  126. package/hmr/server/websocket-angular-entry.js +68 -0
  127. package/hmr/server/websocket-angular-entry.js.map +1 -0
  128. package/hmr/server/websocket-angular-hot-update.d.ts +78 -0
  129. package/hmr/server/websocket-angular-hot-update.js +413 -0
  130. package/hmr/server/websocket-angular-hot-update.js.map +1 -0
  131. package/hmr/server/websocket-core-bridge.d.ts +58 -0
  132. package/hmr/server/websocket-core-bridge.js +368 -0
  133. package/hmr/server/websocket-core-bridge.js.map +1 -0
  134. package/hmr/server/websocket-css-hot-update.d.ts +33 -0
  135. package/hmr/server/websocket-css-hot-update.js +65 -0
  136. package/hmr/server/websocket-css-hot-update.js.map +1 -0
  137. package/hmr/server/websocket-graph-upsert.d.ts +21 -0
  138. package/hmr/server/websocket-graph-upsert.js +33 -0
  139. package/hmr/server/websocket-graph-upsert.js.map +1 -0
  140. package/hmr/server/websocket-hmr-pending.d.ts +43 -0
  141. package/hmr/server/websocket-hmr-pending.js +55 -0
  142. package/hmr/server/websocket-hmr-pending.js.map +1 -0
  143. package/hmr/server/websocket-module-bindings.d.ts +6 -0
  144. package/hmr/server/websocket-module-bindings.js +471 -0
  145. package/hmr/server/websocket-module-bindings.js.map +1 -0
  146. package/hmr/server/websocket-module-specifiers.d.ts +101 -0
  147. package/hmr/server/websocket-module-specifiers.js +820 -0
  148. package/hmr/server/websocket-module-specifiers.js.map +1 -0
  149. package/hmr/server/websocket-ns-m-finalize.d.ts +22 -0
  150. package/hmr/server/websocket-ns-m-finalize.js +88 -0
  151. package/hmr/server/websocket-ns-m-finalize.js.map +1 -0
  152. package/hmr/server/websocket-ns-m-paths.d.ts +3 -0
  153. package/hmr/server/websocket-ns-m-paths.js +92 -0
  154. package/hmr/server/websocket-ns-m-paths.js.map +1 -0
  155. package/hmr/server/websocket-ns-m-request.d.ts +45 -0
  156. package/hmr/server/websocket-ns-m-request.js +196 -0
  157. package/hmr/server/websocket-ns-m-request.js.map +1 -0
  158. package/hmr/server/websocket-served-module-helpers.d.ts +36 -0
  159. package/hmr/server/websocket-served-module-helpers.js +644 -0
  160. package/hmr/server/websocket-served-module-helpers.js.map +1 -0
  161. package/hmr/server/websocket-txn.d.ts +6 -0
  162. package/hmr/server/websocket-txn.js +45 -0
  163. package/hmr/server/websocket-txn.js.map +1 -0
  164. package/hmr/server/websocket-vendor-unifier.d.ts +10 -0
  165. package/hmr/server/websocket-vendor-unifier.js +51 -0
  166. package/hmr/server/websocket-vendor-unifier.js.map +1 -0
  167. package/hmr/server/websocket-vue-sfc.d.ts +26 -0
  168. package/hmr/server/websocket-vue-sfc.js +1053 -0
  169. package/hmr/server/websocket-vue-sfc.js.map +1 -0
  170. package/hmr/server/websocket.d.ts +58 -75
  171. package/hmr/server/websocket.js +2230 -1802
  172. package/hmr/server/websocket.js.map +1 -1
  173. package/hmr/shared/package-classifier.d.ts +9 -0
  174. package/hmr/shared/package-classifier.js +58 -0
  175. package/hmr/shared/package-classifier.js.map +1 -0
  176. package/hmr/shared/runtime/boot-placeholder-ui.d.ts +69 -0
  177. package/hmr/shared/runtime/boot-placeholder-ui.js +101 -0
  178. package/hmr/shared/runtime/boot-placeholder-ui.js.map +1 -0
  179. package/hmr/shared/runtime/boot-progress.d.ts +40 -0
  180. package/hmr/shared/runtime/boot-progress.js +128 -0
  181. package/hmr/shared/runtime/boot-progress.js.map +1 -0
  182. package/hmr/shared/runtime/boot-timeline.d.ts +18 -0
  183. package/hmr/shared/runtime/boot-timeline.js +52 -0
  184. package/hmr/shared/runtime/boot-timeline.js.map +1 -0
  185. package/hmr/shared/runtime/browser-runtime-contract.d.ts +64 -0
  186. package/hmr/shared/runtime/browser-runtime-contract.js +54 -0
  187. package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -0
  188. package/hmr/shared/runtime/dev-overlay.d.ts +78 -3
  189. package/hmr/shared/runtime/dev-overlay.js +1094 -26
  190. package/hmr/shared/runtime/dev-overlay.js.map +1 -1
  191. package/hmr/shared/runtime/module-provenance.js +1 -4
  192. package/hmr/shared/runtime/module-provenance.js.map +1 -1
  193. package/hmr/shared/runtime/root-placeholder.d.ts +1 -0
  194. package/hmr/shared/runtime/root-placeholder.js +1019 -151
  195. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  196. package/hmr/shared/runtime/session-bootstrap.d.ts +1 -0
  197. package/hmr/shared/runtime/session-bootstrap.js +309 -0
  198. package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
  199. package/hmr/shared/runtime/vendor-bootstrap.js +1 -9
  200. package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
  201. package/hmr/shared/vendor/manifest.d.ts +32 -0
  202. package/hmr/shared/vendor/manifest.js +411 -46
  203. package/hmr/shared/vendor/manifest.js.map +1 -1
  204. package/index.d.ts +1 -0
  205. package/index.js +5 -0
  206. package/index.js.map +1 -1
  207. package/package.json +9 -1
  208. package/runtime/core-aliases-early.js +94 -67
  209. package/runtime/core-aliases-early.js.map +1 -1
@@ -5,9 +5,42 @@
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 } from './utils.js';
9
9
  import { handleCssUpdates } from './css-handler.js';
10
+ import { buildCssApplyingDetail, buildCssAppliedDetail } from './css-update-overlay.js';
10
11
  const VERBOSE = typeof __NS_ENV_VERBOSE__ !== 'undefined' && __NS_ENV_VERBOSE__;
12
+ function resolveTargetFlavor() {
13
+ try {
14
+ if (typeof __NS_TARGET_FLAVOR__ !== 'undefined' && __NS_TARGET_FLAVOR__) {
15
+ return __NS_TARGET_FLAVOR__;
16
+ }
17
+ }
18
+ catch { }
19
+ try {
20
+ const g = globalThis;
21
+ if (typeof g.__NS_TARGET_FLAVOR__ === 'string' && g.__NS_TARGET_FLAVOR__) {
22
+ return g.__NS_TARGET_FLAVOR__;
23
+ }
24
+ if (typeof g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__ === 'string' && g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__) {
25
+ return g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__;
26
+ }
27
+ if (typeof g.__reboot_ng_modules__ === 'function') {
28
+ return 'angular';
29
+ }
30
+ if (g.__VUE_HMR_RUNTIME__ || g.__NS_HMR_VUE_SFC_REGISTRY__) {
31
+ return 'vue';
32
+ }
33
+ }
34
+ catch { }
35
+ return undefined;
36
+ }
37
+ const TARGET_FLAVOR = resolveTargetFlavor();
38
+ try {
39
+ if (TARGET_FLAVOR && !globalThis.__NS_TARGET_FLAVOR__) {
40
+ globalThis.__NS_TARGET_FLAVOR__ = TARGET_FLAVOR;
41
+ }
42
+ }
43
+ catch { }
11
44
  const APP_ROOT_VIRTUAL = typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__ ? __NS_APP_ROOT_VIRTUAL__ : '/src';
12
45
  const APP_VIRTUAL_WITH_SLASH = APP_ROOT_VIRTUAL.endsWith('/') ? APP_ROOT_VIRTUAL : `${APP_ROOT_VIRTUAL}/`;
13
46
  const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
@@ -29,8 +62,9 @@ function ensureCoreAliasesOnGlobalThis() {
29
62
  }
30
63
  catch { }
31
64
  try {
32
- if (A && !g.Application)
65
+ if (A && (!g.Application || typeof g.Application.run !== 'function' || typeof g.Application.on !== 'function' || typeof g.Application.resetRootView !== 'function')) {
33
66
  g.Application = A;
67
+ }
34
68
  }
35
69
  catch { }
36
70
  try {
@@ -68,6 +102,80 @@ function hideConnectionOverlay() {
68
102
  }
69
103
  catch { }
70
104
  }
105
+ function setUpdateOverlayStage(stage, info) {
106
+ try {
107
+ const api = getHmrOverlayApi();
108
+ if (api && typeof api.setUpdateStage === 'function') {
109
+ api.setUpdateStage(stage, info);
110
+ }
111
+ }
112
+ catch { }
113
+ }
114
+ // Store the listener registry on globalThis (rather than in a module-private
115
+ // closure) because in NativeScript the HMR client module and the user app
116
+ // modules can resolve to different module instances depending on how the
117
+ // dev runtime loads them (HTTP client URL vs. the bundled vendor realm).
118
+ // A module-local Set would not be shared across instances; the global one
119
+ // is.
120
+ function getNsSolidHmrListenerSet() {
121
+ const g = globalThis;
122
+ let set = g.__ns_solid_hmr_listener_set;
123
+ if (!set) {
124
+ set = new Set();
125
+ g.__ns_solid_hmr_listener_set = set;
126
+ }
127
+ return set;
128
+ }
129
+ function nsSolidHmrSubscribe(fn) {
130
+ const listeners = getNsSolidHmrListenerSet();
131
+ listeners.add(fn);
132
+ if (VERBOSE)
133
+ console.log('[hmr][solid] subscribe — listeners=', listeners.size);
134
+ return () => listeners.delete(fn);
135
+ }
136
+ function nsSolidHmrEmit(ev) {
137
+ const listeners = getNsSolidHmrListenerSet();
138
+ if (VERBOSE)
139
+ console.log('[hmr][solid] emit listeners=', listeners.size, 'changedFiles=', ev.changedFiles);
140
+ for (const fn of Array.from(listeners)) {
141
+ try {
142
+ fn(ev);
143
+ }
144
+ catch (err) {
145
+ if (VERBOSE)
146
+ console.warn('[hmr][solid] listener threw', err);
147
+ }
148
+ }
149
+ }
150
+ try {
151
+ const g = globalThis;
152
+ g.__ns_solid_hmr_subscribe = nsSolidHmrSubscribe;
153
+ // Eagerly create the listener set so the global exists at module load time.
154
+ getNsSolidHmrListenerSet();
155
+ if (VERBOSE)
156
+ console.log('[hmr][solid] HMR client loaded. global set=', typeof g.__ns_solid_hmr_subscribe, 'listenerSet=', typeof g.__ns_solid_hmr_listener_set);
157
+ }
158
+ catch (err) {
159
+ console.warn('[hmr][solid] could not install global __ns_solid_hmr_subscribe', err);
160
+ }
161
+ // Eagerly drive the HMR-applying overlay's 'received' frame as soon
162
+ // as the server emits `ns:hmr-pending`, BEFORE the framework-specific
163
+ // (`ns:angular-update` / `ns:css-updates`) payload arrives. The
164
+ // flavor-specific handler later walks through 'evicting' →
165
+ // 'reimporting' → 'rebooting' → 'complete'. Calling 'received' twice
166
+ // in the same cycle is safe: the overlay preserves
167
+ // `updateCycleStartedAt` when a 'received' frame replaces an existing
168
+ // 'received' frame so the minimum-visible window is still timed
169
+ // against the FIRST frame.
170
+ //
171
+ // Soft-fails when the overlay isn't installed (production builds,
172
+ // vitest, etc.) or when the user opted out via
173
+ // `__NS_HMR_PROGRESS_OVERLAY_ENABLED__ === false`.
174
+ import { applyHmrPendingFrame } from './hmr-pending-overlay.js';
175
+ import { driveVueSfcUpdateOverlay } from './vue-sfc-update-overlay.js';
176
+ function setHmrPendingOverlay(filePath) {
177
+ applyHmrPendingFrame(filePath, { getOverlay: getHmrOverlayApi });
178
+ }
71
179
  let connectionOverlayTimer = null;
72
180
  let connectionOverlayVisible = false;
73
181
  let hasOpenedHmrSocket = false;
@@ -114,7 +222,7 @@ function markHmrConnectionHealthy() {
114
222
  */
115
223
  import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate, sfcArtifactMap } from '../frameworks/vue/client/index.js';
116
224
  import { handleAngularHotUpdateMessage, installAngularHmrClientHooks } from '../frameworks/angular/client/index.js';
117
- switch (__NS_TARGET_FLAVOR__) {
225
+ switch (TARGET_FLAVOR) {
118
226
  case 'vue':
119
227
  installNsVueDevShims();
120
228
  break;
@@ -171,7 +279,7 @@ function applyFullGraph(payload) {
171
279
  try {
172
280
  const g = globalThis;
173
281
  const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
174
- if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && __NS_TARGET_FLAVOR__ !== 'typescript') {
282
+ if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && TARGET_FLAVOR !== 'typescript') {
175
283
  // simple snapshot helpers
176
284
  const getTopmost = () => {
177
285
  try {
@@ -244,7 +352,7 @@ function applyFullGraph(payload) {
244
352
  if (VERBOSE)
245
353
  console.log('[hmr][init] placeholder persists after delay; evaluating rescue policy');
246
354
  // Flavor-specific rescue handling
247
- if (__NS_TARGET_FLAVOR__ === 'typescript') {
355
+ if (TARGET_FLAVOR === 'typescript') {
248
356
  // For TS apps, perform a one-time resetRootView to the conventional
249
357
  // app root module. This mimics what Application.run would do and
250
358
  // replaces the placeholder with the real UI without trying to
@@ -285,7 +393,7 @@ function applyFullGraph(payload) {
285
393
  return;
286
394
  }
287
395
  let candidate = null;
288
- switch (__NS_TARGET_FLAVOR__) {
396
+ switch (TARGET_FLAVOR) {
289
397
  case 'vue': {
290
398
  const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
291
399
  if (appEntry && Array.isArray(appEntry.deps)) {
@@ -327,7 +435,7 @@ function applyFullGraph(payload) {
327
435
  (async () => {
328
436
  try {
329
437
  let comp = null;
330
- switch (__NS_TARGET_FLAVOR__) {
438
+ switch (TARGET_FLAVOR) {
331
439
  case 'vue':
332
440
  comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
333
441
  break;
@@ -372,7 +480,7 @@ function applyFullGraph(payload) {
372
480
  // to avoid double-mount races that can cause duplicate navigation logs.
373
481
  if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
374
482
  let candidate = null;
375
- switch (__NS_TARGET_FLAVOR__) {
483
+ switch (TARGET_FLAVOR) {
376
484
  case 'vue': {
377
485
  const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
378
486
  if (appEntry && Array.isArray(appEntry.deps)) {
@@ -416,7 +524,7 @@ function applyFullGraph(payload) {
416
524
  (async () => {
417
525
  try {
418
526
  if (VERBOSE)
419
- console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', __NS_TARGET_FLAVOR__);
527
+ console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
420
528
  // Android-only: avoid racing entry-runtime reset and Activity bring-up
421
529
  try {
422
530
  const g = globalThis;
@@ -450,7 +558,7 @@ function applyFullGraph(payload) {
450
558
  }
451
559
  catch { }
452
560
  let comp = null;
453
- switch (__NS_TARGET_FLAVOR__) {
561
+ switch (TARGET_FLAVOR) {
454
562
  case 'vue':
455
563
  comp = await loadSfcComponent(candidate, 'initial_mount');
456
564
  break;
@@ -499,7 +607,7 @@ function applyFullGraph(payload) {
499
607
  })();
500
608
  }
501
609
  else if (VERBOSE) {
502
- console.warn('[hmr][init] no component found in graph to mount initially for flavor', __NS_TARGET_FLAVOR__);
610
+ console.warn('[hmr][init] no component found in graph to mount initially for flavor', TARGET_FLAVOR);
503
611
  }
504
612
  }
505
613
  }
@@ -525,7 +633,7 @@ function applyDelta(payload) {
525
633
  setGraphVersion(payload.newVersion);
526
634
  }
527
635
  const changed = payload.changed || [];
528
- switch (__NS_TARGET_FLAVOR__) {
636
+ switch (TARGET_FLAVOR) {
529
637
  case 'vue':
530
638
  recordVuePayloadChanges(changed, getGraphVersion());
531
639
  break;
@@ -615,7 +723,7 @@ function applyDelta(payload) {
615
723
  // Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
616
724
  function __nsNavigateUsingApp(comp, opts = {}) {
617
725
  const g = globalThis;
618
- switch (__NS_TARGET_FLAVOR__) {
726
+ switch (TARGET_FLAVOR) {
619
727
  case 'vue':
620
728
  ensureVueGlobals();
621
729
  break;
@@ -642,8 +750,14 @@ function __nsNavigateUsingApp(comp, opts = {}) {
642
750
  const buildTarget = () => {
643
751
  const existingApp = getCurrentApp();
644
752
  const baseProvides = (existingApp && existingApp._context && existingApp._context.provides) || {};
645
- const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)));
646
- switch (__NS_TARGET_FLAVOR__) {
753
+ // Forward `opts.props` as Vue's rootProps so `$navigateTo(Comp, { props: { } })`
754
+ // reaches the destination component. nativescript-vue's stock `$navigateTo`
755
+ // does the same via `createNativeView(target, options?.props, …)` →
756
+ // `renderer.createApp(component, props)`. Dropping props here would surface
757
+ // at the destination as `[Vue warn]: Missing required prop` and any
758
+ // required-prop component would render with `undefined` bindings.
759
+ const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)), opts && opts.props);
760
+ switch (TARGET_FLAVOR) {
647
761
  case 'vue':
648
762
  ensurePiniaOnApp(app);
649
763
  break;
@@ -777,7 +891,42 @@ async function processQueue() {
777
891
  return;
778
892
  if (VERBOSE)
779
893
  console.log('[hmr][queue] processing changed ids', drained);
894
+ // Track wall-clock so the 'complete' frame can show a meaningful
895
+ // total. Only the Solid + TypeScript flavors drive the overlay
896
+ // from here; Angular has its own flow inside
897
+ // `frameworks/angular/client/index.ts`.
898
+ const tQueueStart = Date.now();
899
+ const driveSolidOverlay = TARGET_FLAVOR === 'solid';
900
+ // Explicit eviction step.
901
+ //
902
+ // On modern runtimes the URL canonicalizer collapses any
903
+ // `__ns_hmr__/<tag>/` segment back to a stable cache key, so
904
+ // without explicit eviction the upcoming `import(url)` would
905
+ // resolve via V8's `g_moduleRegistry` and return the cached
906
+ // stale module — making the queue drain a silent no-op for
907
+ // every save after the first.
908
+ //
909
+ // We hand the canonical eviction URLs to the runtime first;
910
+ // `invalidateModulesByUrls` is a no-op on older runtimes and
911
+ // `requestModuleFromServer` automatically falls back to the
912
+ // legacy `/ns/m/__ns_hmr__/v<N>/` URL versioning path in that
913
+ // case. node_modules and virtual specs are filtered out by
914
+ // `buildEvictionUrls` so vendor modules stay hot.
915
+ if (driveSolidOverlay) {
916
+ setUpdateOverlayStage('evicting', {
917
+ detail: drained.length === 1 ? `Invalidating ${drained[0]}` : `Invalidating ${drained.length} modules`,
918
+ });
919
+ }
920
+ const evictUrls = buildEvictionUrls(drained);
921
+ const evicted = invalidateModulesByUrls(evictUrls);
922
+ if (VERBOSE)
923
+ console.log(`[hmr][queue] eviction count=${evictUrls.length} ok=${evicted}`);
780
924
  // Evaluate changed modules best-effort; failures shouldn't completely break HMR.
925
+ if (driveSolidOverlay) {
926
+ setUpdateOverlayStage('reimporting', {
927
+ detail: drained.length === 1 ? `Re-importing ${drained[0]}` : `Re-importing ${drained.length} modules`,
928
+ });
929
+ }
781
930
  for (const id of drained) {
782
931
  try {
783
932
  const spec = normalizeSpec(id);
@@ -794,11 +943,36 @@ async function processQueue() {
794
943
  }
795
944
  }
796
945
  // After evaluating the batch, perform flavor-specific UI refresh.
797
- switch (__NS_TARGET_FLAVOR__) {
946
+ switch (TARGET_FLAVOR) {
798
947
  case 'vue':
799
- // Vue SFCs are handled via the registry update path; nothing to do here.
948
+ // Vue SFCs are handled via the registry update path
949
+ // (which drives its own overlay completion through
950
+ // `driveVueSfcUpdateOverlay`); nothing else to do
951
+ // for the view-tree refresh here.
952
+ //
953
+ // However, when a non-SFC file (e.g. a `.ts`
954
+ // utility module imported by an SFC) is the only
955
+ // changed entry, no `ns:vue-sfc-registry-update`
956
+ // will follow — and without an explicit 'complete'
957
+ // the overlay would stick on "Preparing update
958
+ // (5%)". Drive the closing frame here so the
959
+ // auto-hide timer can dismiss the toast in the
960
+ // pure-TS-change case. The detail surfaces the
961
+ // changed count so a user can correlate the
962
+ // overlay with the server-side `[hmr-ws][update]`
963
+ // log.
964
+ setUpdateOverlayStage('complete', {
965
+ detail: drained.length === 1 ? `Updated ${drained[0]} in ${Math.max(0, Date.now() - tQueueStart)}ms` : `Updated ${drained.length} modules in ${Math.max(0, Date.now() - tQueueStart)}ms`,
966
+ });
800
967
  break;
801
968
  case 'solid': {
969
+ // Boundaries discovered in this HMR cycle (tsx files reachable
970
+ // via the reverse import graph from any changed file, plus route
971
+ // files reachable from any tsx start point). Declared at the top
972
+ // of the case block so the emit step below can include the
973
+ // complete set in the listener event — framework integrations
974
+ // use it to map route boundaries → fresh component references.
975
+ const boundaries = new Set();
802
976
  // Solid .tsx components are self-accepting via solid-refresh's inline
803
977
  // patchRegistry — re-importing them is sufficient. For non-component
804
978
  // .ts utility modules, we must propagate up the import graph to find
@@ -817,8 +991,10 @@ async function processQueue() {
817
991
  arr.push(id);
818
992
  }
819
993
  }
820
- // BFS from each non-tsx changed module up to tsx/jsx boundaries
821
- const boundaries = new Set();
994
+ // Pass 1: BFS from each non-tsx changed module up to tsx/jsx
995
+ // boundaries. These get re-imported below so solid-refresh's
996
+ // inline patchRegistry runs and (best-effort) swaps the proxy
997
+ // signals for any components defined in those tsx boundaries.
822
998
  for (const id of drained) {
823
999
  if (/\.(tsx|jsx)$/i.test(id))
824
1000
  continue; // already self-accepting
@@ -842,6 +1018,51 @@ async function processQueue() {
842
1018
  }
843
1019
  }
844
1020
  }
1021
+ // Pass 2: walk further from any tsx starting point (a tsx file
1022
+ // in `drained` OR a tsx boundary discovered in pass 1) to find
1023
+ // route files (`/src/routes/*.{tsx,jsx}`) that transitively
1024
+ // import them. Re-importing a route file refreshes its
1025
+ // `Route.options.component` to the freshly-imported reference
1026
+ // and the existing boundary loop below patches the live router
1027
+ // with that fresh reference.
1028
+ //
1029
+ // This is the key fix for "edit home.tsx → save → no visual
1030
+ // update": the old BFS skipped tsx files in `drained` (assuming
1031
+ // solid-refresh's in-place proxy patch was sufficient), but in
1032
+ // the universal-renderer + nested-context configuration that
1033
+ // patch does not always propagate to the visible page tree.
1034
+ // Adding the route file as a boundary lets us patch
1035
+ // `route.options.component` directly to a fresh module export,
1036
+ // which the framework subscriber then passes through to the
1037
+ // page remount — making the cycle robust to the proxy patch
1038
+ // silently failing.
1039
+ const tsxStarts = new Set();
1040
+ for (const id of drained) {
1041
+ if (/\.(tsx|jsx)$/i.test(id))
1042
+ tsxStarts.add(id);
1043
+ }
1044
+ for (const b of boundaries)
1045
+ tsxStarts.add(b);
1046
+ const ROUTE_FILE_RE = /\/src\/routes\/.+\.(tsx|jsx)$/i;
1047
+ for (const start of tsxStarts) {
1048
+ const visited = new Set();
1049
+ const queue = [start];
1050
+ while (queue.length) {
1051
+ const cur = queue.shift();
1052
+ if (visited.has(cur))
1053
+ continue;
1054
+ visited.add(cur);
1055
+ if (cur !== start && ROUTE_FILE_RE.test(cur)) {
1056
+ boundaries.add(cur);
1057
+ }
1058
+ const importers = reverseIndex.get(cur);
1059
+ if (!importers)
1060
+ continue;
1061
+ for (const imp of importers) {
1062
+ queue.push(imp);
1063
+ }
1064
+ }
1065
+ }
845
1066
  // Re-import each boundary so solid-refresh patchRegistry fires.
846
1067
  // For route files (TanStack Router), capture the new Route export
847
1068
  // and patch the router's existing route with the fresh loader.
@@ -891,6 +1112,15 @@ async function processQueue() {
891
1112
  }
892
1113
  return null;
893
1114
  };
1115
+ // Evict the boundary set so re-importing each .tsx
1116
+ // component actually picks up the new transitive
1117
+ // dependency code; without this V8 returns the
1118
+ // cached boundary module unchanged.
1119
+ const boundaryIds = Array.from(boundaries);
1120
+ const solidEvictUrls = buildEvictionUrls(boundaryIds);
1121
+ const solidEvicted = invalidateModulesByUrls(solidEvictUrls);
1122
+ if (VERBOSE)
1123
+ console.log(`[hmr][solid] eviction count=${solidEvictUrls.length} ok=${solidEvicted}`);
894
1124
  for (const id of boundaries) {
895
1125
  if (seen.has(id))
896
1126
  continue;
@@ -902,22 +1132,28 @@ async function processQueue() {
902
1132
  if (VERBOSE)
903
1133
  console.log('[hmr][solid] propagated to boundary', { id, url });
904
1134
  const mod = await import(/* @vite-ignore */ url);
905
- // Patch TanStack Router route loaders
1135
+ // Patch TanStack Router route options for any module
1136
+ // that exports a `Route`. We patch BOTH the component
1137
+ // and the loader (when present); components-only routes
1138
+ // were previously skipped because the gate required a
1139
+ // loader, which left their `options.component` pointing
1140
+ // at the stale module's exports after HMR.
906
1141
  try {
907
1142
  const newRoute = mod?.Route;
908
- if (newRoute?.options?.loader) {
1143
+ if (newRoute?.options) {
909
1144
  const router = findRouter();
910
1145
  const fullPath = boundaryToFullPath(id);
911
1146
  if (VERBOSE)
912
- console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, routesByIdKeys: router?.routesById ? Object.keys(router.routesById) : 'none' });
1147
+ console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, hasLoader: !!newRoute.options.loader, hasComponent: !!newRoute.options.component });
913
1148
  const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
914
1149
  if (existingRoute?.options) {
915
- existingRoute.options.loader = newRoute.options.loader;
1150
+ if (newRoute.options.loader)
1151
+ existingRoute.options.loader = newRoute.options.loader;
916
1152
  if (newRoute.options.component)
917
1153
  existingRoute.options.component = newRoute.options.component;
918
1154
  routesPatchCount++;
919
1155
  if (VERBOSE)
920
- console.log('[hmr][solid] patched route loader', existingRoute.id, 'fullPath=', fullPath);
1156
+ console.log('[hmr][solid] patched route', existingRoute.id, 'fullPath=', fullPath);
921
1157
  }
922
1158
  else if (VERBOSE) {
923
1159
  console.log('[hmr][solid] no matching route for fullPath', fullPath);
@@ -953,6 +1189,44 @@ async function processQueue() {
953
1189
  if (VERBOSE)
954
1190
  console.warn('[hmr][solid] propagation failed', e);
955
1191
  }
1192
+ // Notify any framework integrations (e.g.
1193
+ // `@nativescript/tanstack-router`) that a Solid HMR
1194
+ // cycle has completed. They use this signal to perform
1195
+ // framework-specific UI refresh (e.g. remount the active
1196
+ // router page) when solid-refresh's own reactive
1197
+ // propagation does not reach the visible tree under
1198
+ // the current renderer/context configuration.
1199
+ //
1200
+ // Boundaries include both the directly-changed tsx files
1201
+ // AND every tsx ancestor reachable via the reverse import
1202
+ // graph (route files in particular). The framework
1203
+ // listener uses the route-file boundaries to look up the
1204
+ // freshly-patched `route.options.component` and pass it
1205
+ // through to the page remount.
1206
+ try {
1207
+ const tsxChangedInDrained = drained.filter((id) => /\.(tsx|jsx)$/i.test(id));
1208
+ const allBoundaries = Array.from(new Set([...tsxChangedInDrained, ...boundaries]));
1209
+ nsSolidHmrEmit({
1210
+ kind: 'solid',
1211
+ changedFiles: drained.slice(),
1212
+ boundaries: allBoundaries,
1213
+ });
1214
+ }
1215
+ catch (err) {
1216
+ if (VERBOSE)
1217
+ console.warn('[hmr][solid] emit failed', err);
1218
+ }
1219
+ // Tell the overlay the cycle is done. solid-refresh's
1220
+ // inline patchRegistry has already flushed the new
1221
+ // component bodies into the live tree (the `case
1222
+ // 'solid'` block above re-imports each .tsx
1223
+ // boundary), so by the time we get here the user is
1224
+ // already looking at the new render. The 'complete'
1225
+ // frame surfaces the wall-clock total and triggers
1226
+ // the overlay's auto-hide.
1227
+ setUpdateOverlayStage('complete', {
1228
+ detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
1229
+ });
956
1230
  break;
957
1231
  }
958
1232
  case 'typescript': {
@@ -1132,6 +1406,10 @@ function connectHmr() {
1132
1406
  console.log('[hmr-client] Already connecting to HMR WebSocket, skipping');
1133
1407
  return;
1134
1408
  }
1409
+ try {
1410
+ globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
1411
+ }
1412
+ catch { }
1135
1413
  const overlayStage = hasOpenedHmrSocket ? 'reconnecting' : 'connecting';
1136
1414
  const baseUrl = getHMRWsUrl() || 'ws://localhost:5173/ns-hmr';
1137
1415
  const buildCandidates = (url) => {
@@ -1176,7 +1454,7 @@ function connectHmr() {
1176
1454
  if (seen.has(key))
1177
1455
  continue;
1178
1456
  seen.add(key);
1179
- const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}`;
1457
+ const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}${u.search || ''}`;
1180
1458
  candidates.push(cand);
1181
1459
  }
1182
1460
  }
@@ -1229,10 +1507,22 @@ function connectHmr() {
1229
1507
  clearConnectionOverlayTimer();
1230
1508
  hasOpenedHmrSocket = true;
1231
1509
  awaitingHealthyHmrMessage = true;
1510
+ try {
1511
+ globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = true;
1512
+ }
1513
+ catch { }
1232
1514
  if (connectionOverlayVisible) {
1233
1515
  showConnectionOverlayNow('synchronizing', 'Connected. Synchronizing the HMR graph.');
1234
1516
  }
1235
1517
  VERBOSE && console.log('[hmr-client] Connected to HMR WebSocket');
1518
+ // Print the active module reload mode once on first
1519
+ // successful connect so the user can correlate HMR latency
1520
+ // with runtime capability without grepping for protocol
1521
+ // details. The banner is verbose-gated.
1522
+ try {
1523
+ emitHmrModeBannerOnce();
1524
+ }
1525
+ catch { }
1236
1526
  };
1237
1527
  sock.onmessage = handleHmrMessage;
1238
1528
  sock.onerror = (error) => {
@@ -1241,6 +1531,10 @@ function connectHmr() {
1241
1531
  };
1242
1532
  sock.onclose = (ev) => {
1243
1533
  clearTimeout(timeout);
1534
+ try {
1535
+ globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
1536
+ }
1537
+ catch { }
1244
1538
  if (!opened) {
1245
1539
  // immediate failure during connect → try another candidate
1246
1540
  if (VERBOSE)
@@ -1274,6 +1568,9 @@ async function handleHmrMessage(ev) {
1274
1568
  if (awaitingHealthyHmrMessage && msg) {
1275
1569
  markHmrConnectionHealthy();
1276
1570
  }
1571
+ if (VERBOSE && msg?.type) {
1572
+ console.log('[hmr-client] received message', msg.type);
1573
+ }
1277
1574
  // Notify optional app-level hook after an HMR batch is applied.
1278
1575
  function notifyAppHmrUpdate(kind, changedIds) {
1279
1576
  try {
@@ -1285,6 +1582,17 @@ async function handleHmrMessage(ev) {
1285
1582
  catch { }
1286
1583
  }
1287
1584
  if (msg) {
1585
+ // `ns:hmr-pending` is a fire-and-forget UX hint emitted by the
1586
+ // server at the START of handleHotUpdate. We drive the
1587
+ // HMR-applying overlay's 'received' frame here (synchronously),
1588
+ // well before the authoritative payload (`ns:angular-update` /
1589
+ // `ns:css-updates`) lands. Skip running any other handlers —
1590
+ // the pending message has no module payload and intentionally
1591
+ // does not bump the graph version.
1592
+ if (msg.type === 'ns:hmr-pending' && typeof msg.path === 'string') {
1593
+ setHmrPendingOverlay(msg.path);
1594
+ return;
1595
+ }
1288
1596
  if (msg.type === 'ns:hmr-full-graph') {
1289
1597
  // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
1290
1598
  try {
@@ -1354,6 +1662,14 @@ async function handleHmrMessage(ev) {
1354
1662
  });
1355
1663
  if (toReimport.length && VERBOSE)
1356
1664
  console.log('[hmr][full-graph] inferred changed modules; re-importing', toReimport);
1665
+ // Evict the inferred changed set before re-importing.
1666
+ // See `processQueue` for the architectural rationale; the
1667
+ // full-graph code path is the resync fallback (server chose
1668
+ // not to send a delta) and shares the same V8 cache pitfall.
1669
+ const fgEvictUrls = buildEvictionUrls(toReimport);
1670
+ const fgEvicted = invalidateModulesByUrls(fgEvictUrls);
1671
+ if (VERBOSE)
1672
+ console.log(`[hmr][full-graph] eviction count=${fgEvictUrls.length} ok=${fgEvicted}`);
1357
1673
  for (const id of toReimport) {
1358
1674
  try {
1359
1675
  const spec = normalizeSpec(id);
@@ -1426,8 +1742,111 @@ async function handleHmrMessage(ev) {
1426
1742
  notifyAppHmrUpdate('delta', deltaIds);
1427
1743
  return;
1428
1744
  }
1429
- else if (handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
1430
- return;
1745
+ else {
1746
+ // Vite custom-event dispatch.
1747
+ //
1748
+ // `server.ws.send('event-name', payload)` from any Vite plugin lands
1749
+ // on the wire as `{ type: 'custom', event: 'event-name', data: payload }`.
1750
+ // On the web, Vite's stock client owns a `customListenersMap` that
1751
+ // fires every `import.meta.hot.on('event-name', cb)` callback. We
1752
+ // don't run Vite's stock client on device — the iOS runtime owns
1753
+ // the listener registry via `__NS_DISPATCH_HOT_EVENT__` (the
1754
+ // counterpart to `import.meta.hot.on` populated by user code +
1755
+ // compiled Angular components). Forwarding `type: 'custom'` here
1756
+ // is the only thing standing between server-emitted events and
1757
+ // the listeners they were meant for.
1758
+ //
1759
+ // `angular:component-update` is the canonical example. Analog's
1760
+ // plugin sends it on `.html` / component-style edits; the
1761
+ // compiled component `.mjs` registered a listener that
1762
+ // dynamic-imports `/@ng/component?c=<id>&t=<ts>` and calls
1763
+ // `ɵɵreplaceMetadata` on the live class — swapping the template
1764
+ // definition AND walking live `LView`s to recreate matching views
1765
+ // in-place. The page stays mounted and only the changed bits
1766
+ // re-render. We MUST `return` after dispatch so the reboot path
1767
+ // (`handleAngularHotUpdateMessage` → `__reboot_ng_modules__`)
1768
+ // never runs for these updates — that's the whole point of the
1769
+ // component-replacement pipeline.
1770
+ //
1771
+ // All other custom events are forwarded but NOT short-circuited
1772
+ // (Vite spec: custom events are additive — they don't replace
1773
+ // any framework-specific handling). The reboot path falls through
1774
+ // for `ns:angular-update` (the legacy/`.ts`-edit broadcast) and
1775
+ // for any framework not yet using the in-place replacement path.
1776
+ if (msg.type === 'custom' && typeof msg.event === 'string') {
1777
+ // Dispatch every Vite "custom" event through the runtime's
1778
+ // `__NS_DISPATCH_HOT_EVENT__` bridge so `import.meta.hot.on(event, cb)`
1779
+ // callbacks fire on the device. Critical contract: this is the
1780
+ // ONLY route by which Analog's `angular:component-update` reaches
1781
+ // the compiled component's `(d) => d.id === id && Component_HmrLoad(...)`
1782
+ // listener — without it, server-side broadcasts log green
1783
+ // (`(client) hmr update`) while the device sees nothing happen.
1784
+ //
1785
+ // Diagnostic policy: log "no dispatcher" loud (boot-time rt-bridge
1786
+ // failure), and listener exceptions loud (compiled HmrLoad
1787
+ // fetch/parse error). Successful dispatches are silent — the
1788
+ // runtime's `[import.meta.hot] dispatch summary` line carries
1789
+ // the per-event match-count diagnostic.
1790
+ try {
1791
+ const dispatch = globalThis.__NS_DISPATCH_HOT_EVENT__;
1792
+ if (typeof dispatch === 'function') {
1793
+ dispatch(msg.event, msg.data);
1794
+ }
1795
+ else {
1796
+ console.warn(`[hmr-client][custom] no __NS_DISPATCH_HOT_EVENT__ available for '${msg.event}'`);
1797
+ }
1798
+ }
1799
+ catch (err) {
1800
+ console.warn('[hmr-client][custom] dispatch threw for', msg.event, err);
1801
+ }
1802
+ if (msg.event === 'angular:component-update') {
1803
+ if (VERBOSE)
1804
+ console.log('[hmr-client][custom] dispatched angular:component-update — skipping reboot path');
1805
+ // Walk the apply-progress overlay through its
1806
+ // remaining stages for the in-place template-swap
1807
+ // path. The full reboot path
1808
+ // (`handleAngularHotUpdateMessage`) drives the
1809
+ // overlay itself ('received' → 'evicting' →
1810
+ // 'reimporting' → 'rebooting' → 'complete'); the
1811
+ // in-place path bypasses that handler entirely
1812
+ // because the work happens inside Angular's
1813
+ // `ɵɵreplaceMetadata` after the runtime forwards the
1814
+ // `angular:component-update` event to the compiled
1815
+ // component's listener. Without this update the
1816
+ // overlay would freeze at 5% ('received') even
1817
+ // though the visual swap completes a few frames
1818
+ // later — exactly the "Preparing update (5%)" stuck
1819
+ // frame we have been chasing.
1820
+ //
1821
+ // We transition straight to 'reimporting' to
1822
+ // communicate that metadata is being fetched (the
1823
+ // runtime listener fires `__ns_import('/@ng/component?c=...&t=...')`),
1824
+ // then schedule 'complete' on the next macrotask so
1825
+ // the auto-hide timer kicks in. The actual
1826
+ // template swap is fire-and-forget from this point;
1827
+ // the user sees the overlay close at the same time
1828
+ // as Angular re-renders the bound text/structure.
1829
+ try {
1830
+ const filePath = typeof msg.data?.id === 'string' ? decodeURIComponent(msg.data.id).split('@')[0] : undefined;
1831
+ const detail = filePath ? `Applying template update to ${filePath}` : 'Applying template update';
1832
+ setUpdateOverlayStage('reimporting', { detail });
1833
+ setTimeout(() => {
1834
+ try {
1835
+ setUpdateOverlayStage('complete', { detail: filePath ? `Updated ${filePath}` : 'Update applied' });
1836
+ }
1837
+ catch { }
1838
+ }, 16);
1839
+ }
1840
+ catch { }
1841
+ return;
1842
+ }
1843
+ }
1844
+ if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
1845
+ setGraphVersion(Number(msg.version || getGraphVersion() || 0));
1846
+ }
1847
+ if (await handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
1848
+ return;
1849
+ }
1431
1850
  }
1432
1851
  }
1433
1852
  // On-demand module fetch response (Option A)
@@ -1459,13 +1878,32 @@ async function handleHmrMessage(ev) {
1459
1878
  return;
1460
1879
  }
1461
1880
  if (msg.type === 'ns:css-updates' && Array.isArray(msg.updates)) {
1881
+ // Drive the HMR-applying overlay past the 'received' (5%) frame
1882
+ // that `ns:hmr-pending` set earlier in the cycle. Without this
1883
+ // the overlay sticks at "Preparing update" forever for CSS-only
1884
+ // edits because `handleCssUpdates` is a leaf — there's no
1885
+ // downstream module-evaluation path that would hit the queue's
1886
+ // 'complete' transition.
1887
+ const cssCount = msg.updates.length;
1888
+ try {
1889
+ setUpdateOverlayStage('reimporting', { detail: buildCssApplyingDetail(cssCount) });
1890
+ }
1891
+ catch { }
1462
1892
  try {
1463
1893
  const origin = msg.origin || getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
1464
1894
  await handleCssUpdates(msg.updates, origin);
1895
+ try {
1896
+ setUpdateOverlayStage('complete', { detail: buildCssAppliedDetail(cssCount) });
1897
+ }
1898
+ catch { }
1465
1899
  return;
1466
1900
  }
1467
1901
  catch (e) {
1468
1902
  console.warn('[hmr-client] CSS updates handling failed:', e);
1903
+ try {
1904
+ setUpdateOverlayStage('complete', { detail: 'CSS update failed' });
1905
+ }
1906
+ catch { }
1469
1907
  return;
1470
1908
  }
1471
1909
  }
@@ -1476,10 +1914,22 @@ async function handleHmrMessage(ev) {
1476
1914
  if (msg.type === 'ns:vue-sfc-registry-update') {
1477
1915
  if (typeof msg.version === 'number')
1478
1916
  setGraphVersion(msg.version);
1479
- const comp = await handleVueSfcRegistryUpdate(msg, getGraphVersion());
1480
- if (comp) {
1481
- await performResetRoot(comp);
1482
- }
1917
+ // `ns:hmr-pending` already set the overlay to 'received' (5%).
1918
+ // Without the explicit stage walk below the overlay would stick
1919
+ // at "Preparing update" forever after a successful SFC swap —
1920
+ // the Vue path was missing the framework-specific completion
1921
+ // hooks that Angular drives via `handleAngularHotUpdateMessage`
1922
+ // and CSS drives inline above. `driveVueSfcUpdateOverlay` is a
1923
+ // thin orchestrator that walks 'evicting' → 'reimporting' →
1924
+ // 'rebooting' → 'complete' around the load + reset steps and
1925
+ // always lands on 'complete' (or a failure detail) so the
1926
+ // auto-hide timer can dismiss the toast.
1927
+ const sfcFilePath = typeof msg.path === 'string' ? msg.path : undefined;
1928
+ await driveVueSfcUpdateOverlay({
1929
+ filePath: sfcFilePath,
1930
+ loadComponent: () => handleVueSfcRegistryUpdate(msg, getGraphVersion()),
1931
+ applyComponent: (component) => performResetRoot(component),
1932
+ }, { getOverlay: getHmrOverlayApi });
1483
1933
  return;
1484
1934
  }
1485
1935
  }
@@ -1568,7 +2018,7 @@ async function performResetRoot(newComponent) {
1568
2018
  if (cachedRoot)
1569
2019
  return cachedRoot;
1570
2020
  try {
1571
- switch (__NS_TARGET_FLAVOR__) {
2021
+ switch (TARGET_FLAVOR) {
1572
2022
  case 'vue':
1573
2023
  cachedRoot = getRootForVue(newComponent, state);
1574
2024
  break;
@@ -1905,7 +2355,7 @@ export function initHmrClient(opts) {
1905
2355
  setTimeout(waitForBoot, 100);
1906
2356
  }
1907
2357
  // Best-effort: install back wrapper even before first remount; original root may be captured later
1908
- switch (__NS_TARGET_FLAVOR__) {
2358
+ switch (TARGET_FLAVOR) {
1909
2359
  case 'vue':
1910
2360
  ensureBackWrapperInstalled(performResetRoot, getCore);
1911
2361
  break;