@nativescript/vite 8.0.0-alpha.0 → 8.0.0-alpha.10

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 (222) hide show
  1. package/configuration/angular.d.ts +1 -1
  2. package/configuration/angular.js +486 -140
  3. package/configuration/angular.js.map +1 -1
  4. package/configuration/base.js +159 -41
  5. package/configuration/base.js.map +1 -1
  6. package/configuration/javascript.js +3 -3
  7. package/configuration/javascript.js.map +1 -1
  8. package/configuration/solid.js +7 -0
  9. package/configuration/solid.js.map +1 -1
  10. package/configuration/typescript.js +4 -4
  11. package/configuration/typescript.js.map +1 -1
  12. package/helpers/angular/angular-linker.js +38 -42
  13. package/helpers/angular/angular-linker.js.map +1 -1
  14. package/helpers/angular/inject-component-hmr-registration.d.ts +112 -0
  15. package/helpers/angular/inject-component-hmr-registration.js +359 -0
  16. package/helpers/angular/inject-component-hmr-registration.js.map +1 -0
  17. package/helpers/angular/inline-decorator-component-templates.d.ts +3 -0
  18. package/helpers/angular/inline-decorator-component-templates.js +400 -0
  19. package/helpers/angular/inline-decorator-component-templates.js.map +1 -0
  20. package/helpers/angular/shared-linker.d.ts +7 -0
  21. package/helpers/angular/shared-linker.js +37 -1
  22. package/helpers/angular/shared-linker.js.map +1 -1
  23. package/helpers/angular/synthesize-decorator-ctor-parameters.d.ts +1 -0
  24. package/helpers/angular/synthesize-decorator-ctor-parameters.js +256 -0
  25. package/helpers/angular/synthesize-decorator-ctor-parameters.js.map +1 -0
  26. package/helpers/angular/synthesize-injectable-factories.d.ts +3 -0
  27. package/helpers/angular/synthesize-injectable-factories.js +414 -0
  28. package/helpers/angular/synthesize-injectable-factories.js.map +1 -0
  29. package/helpers/angular/util.d.ts +1 -0
  30. package/helpers/angular/util.js +88 -0
  31. package/helpers/angular/util.js.map +1 -1
  32. package/helpers/commonjs-plugins.d.ts +5 -2
  33. package/helpers/commonjs-plugins.js +126 -0
  34. package/helpers/commonjs-plugins.js.map +1 -1
  35. package/helpers/config-as-json.js +10 -0
  36. package/helpers/config-as-json.js.map +1 -1
  37. package/helpers/esbuild-platform-resolver.js +5 -5
  38. package/helpers/esbuild-platform-resolver.js.map +1 -1
  39. package/helpers/external-configs.d.ts +9 -1
  40. package/helpers/external-configs.js +31 -6
  41. package/helpers/external-configs.js.map +1 -1
  42. package/helpers/global-defines.d.ts +51 -0
  43. package/helpers/global-defines.js +77 -0
  44. package/helpers/global-defines.js.map +1 -1
  45. package/helpers/import-meta-path.d.ts +4 -0
  46. package/helpers/import-meta-path.js +5 -0
  47. package/helpers/import-meta-path.js.map +1 -0
  48. package/helpers/import-specifier.d.ts +1 -0
  49. package/helpers/import-specifier.js +18 -0
  50. package/helpers/import-specifier.js.map +1 -0
  51. package/helpers/logging.d.ts +1 -0
  52. package/helpers/logging.js +63 -3
  53. package/helpers/logging.js.map +1 -1
  54. package/helpers/main-entry.d.ts +5 -2
  55. package/helpers/main-entry.js +365 -116
  56. package/helpers/main-entry.js.map +1 -1
  57. package/helpers/nativeclass-transform.js +8 -127
  58. package/helpers/nativeclass-transform.js.map +1 -1
  59. package/helpers/nativeclass-transformer-plugin.d.ts +19 -1
  60. package/helpers/nativeclass-transformer-plugin.js +318 -31
  61. package/helpers/nativeclass-transformer-plugin.js.map +1 -1
  62. package/helpers/ns-core-url.d.ts +83 -0
  63. package/helpers/ns-core-url.js +167 -0
  64. package/helpers/ns-core-url.js.map +1 -0
  65. package/helpers/prelink-angular.js +1 -4
  66. package/helpers/prelink-angular.js.map +1 -1
  67. package/helpers/preserve-imports.js +2 -17
  68. package/helpers/preserve-imports.js.map +1 -1
  69. package/helpers/project.d.ts +35 -0
  70. package/helpers/project.js +120 -2
  71. package/helpers/project.js.map +1 -1
  72. package/helpers/ts-config-paths.js +50 -2
  73. package/helpers/ts-config-paths.js.map +1 -1
  74. package/helpers/workers.d.ts +20 -19
  75. package/helpers/workers.js +620 -3
  76. package/helpers/workers.js.map +1 -1
  77. package/hmr/client/css-handler.js +60 -19
  78. package/hmr/client/css-handler.js.map +1 -1
  79. package/hmr/client/hmr-pending-overlay.d.ts +27 -0
  80. package/hmr/client/hmr-pending-overlay.js +50 -0
  81. package/hmr/client/hmr-pending-overlay.js.map +1 -0
  82. package/hmr/client/index.js +597 -24
  83. package/hmr/client/index.js.map +1 -1
  84. package/hmr/client/utils.d.ts +5 -0
  85. package/hmr/client/utils.js +212 -21
  86. package/hmr/client/utils.js.map +1 -1
  87. package/hmr/entry-runtime.d.ts +10 -0
  88. package/hmr/entry-runtime.js +330 -42
  89. package/hmr/entry-runtime.js.map +1 -1
  90. package/hmr/frameworks/angular/client/index.d.ts +3 -1
  91. package/hmr/frameworks/angular/client/index.js +821 -25
  92. package/hmr/frameworks/angular/client/index.js.map +1 -1
  93. package/hmr/frameworks/angular/server/linker.js +37 -6
  94. package/hmr/frameworks/angular/server/linker.js.map +1 -1
  95. package/hmr/frameworks/angular/server/strategy.js +30 -6
  96. package/hmr/frameworks/angular/server/strategy.js.map +1 -1
  97. package/hmr/frameworks/typescript/server/strategy.js +8 -2
  98. package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
  99. package/hmr/frameworks/vue/client/index.js +18 -42
  100. package/hmr/frameworks/vue/client/index.js.map +1 -1
  101. package/hmr/helpers/ast-normalizer.js +22 -10
  102. package/hmr/helpers/ast-normalizer.js.map +1 -1
  103. package/hmr/helpers/cjs-named-exports.d.ts +23 -0
  104. package/hmr/helpers/cjs-named-exports.js +152 -0
  105. package/hmr/helpers/cjs-named-exports.js.map +1 -0
  106. package/hmr/server/constants.d.ts +1 -0
  107. package/hmr/server/constants.js +14 -3
  108. package/hmr/server/constants.js.map +1 -1
  109. package/hmr/server/core-sanitize.d.ts +49 -2
  110. package/hmr/server/core-sanitize.js +267 -24
  111. package/hmr/server/core-sanitize.js.map +1 -1
  112. package/hmr/server/import-map.d.ts +65 -0
  113. package/hmr/server/import-map.js +222 -0
  114. package/hmr/server/import-map.js.map +1 -0
  115. package/hmr/server/index.d.ts +2 -1
  116. package/hmr/server/index.js.map +1 -1
  117. package/hmr/server/ns-core-cjs-shape.d.ts +204 -0
  118. package/hmr/server/ns-core-cjs-shape.js +271 -0
  119. package/hmr/server/ns-core-cjs-shape.js.map +1 -0
  120. package/hmr/server/perf-instrumentation.d.ts +114 -0
  121. package/hmr/server/perf-instrumentation.js +195 -0
  122. package/hmr/server/perf-instrumentation.js.map +1 -0
  123. package/hmr/server/runtime-graph-filter.d.ts +5 -0
  124. package/hmr/server/runtime-graph-filter.js +21 -0
  125. package/hmr/server/runtime-graph-filter.js.map +1 -0
  126. package/hmr/server/shared-transform-request.d.ts +12 -0
  127. package/hmr/server/shared-transform-request.js +144 -0
  128. package/hmr/server/shared-transform-request.js.map +1 -0
  129. package/hmr/server/vite-plugin.d.ts +21 -1
  130. package/hmr/server/vite-plugin.js +461 -22
  131. package/hmr/server/vite-plugin.js.map +1 -1
  132. package/hmr/server/websocket-angular-entry.d.ts +2 -0
  133. package/hmr/server/websocket-angular-entry.js +68 -0
  134. package/hmr/server/websocket-angular-entry.js.map +1 -0
  135. package/hmr/server/websocket-angular-hot-update.d.ts +78 -0
  136. package/hmr/server/websocket-angular-hot-update.js +413 -0
  137. package/hmr/server/websocket-angular-hot-update.js.map +1 -0
  138. package/hmr/server/websocket-core-bridge.d.ts +21 -0
  139. package/hmr/server/websocket-core-bridge.js +357 -0
  140. package/hmr/server/websocket-core-bridge.js.map +1 -0
  141. package/hmr/server/websocket-graph-upsert.d.ts +21 -0
  142. package/hmr/server/websocket-graph-upsert.js +33 -0
  143. package/hmr/server/websocket-graph-upsert.js.map +1 -0
  144. package/hmr/server/websocket-hmr-pending.d.ts +43 -0
  145. package/hmr/server/websocket-hmr-pending.js +55 -0
  146. package/hmr/server/websocket-hmr-pending.js.map +1 -0
  147. package/hmr/server/websocket-module-bindings.d.ts +6 -0
  148. package/hmr/server/websocket-module-bindings.js +471 -0
  149. package/hmr/server/websocket-module-bindings.js.map +1 -0
  150. package/hmr/server/websocket-module-specifiers.d.ts +101 -0
  151. package/hmr/server/websocket-module-specifiers.js +820 -0
  152. package/hmr/server/websocket-module-specifiers.js.map +1 -0
  153. package/hmr/server/websocket-ns-m-finalize.d.ts +22 -0
  154. package/hmr/server/websocket-ns-m-finalize.js +88 -0
  155. package/hmr/server/websocket-ns-m-finalize.js.map +1 -0
  156. package/hmr/server/websocket-ns-m-paths.d.ts +3 -0
  157. package/hmr/server/websocket-ns-m-paths.js +92 -0
  158. package/hmr/server/websocket-ns-m-paths.js.map +1 -0
  159. package/hmr/server/websocket-ns-m-request.d.ts +45 -0
  160. package/hmr/server/websocket-ns-m-request.js +196 -0
  161. package/hmr/server/websocket-ns-m-request.js.map +1 -0
  162. package/hmr/server/websocket-runtime-compat.d.ts +19 -0
  163. package/hmr/server/websocket-runtime-compat.js +287 -0
  164. package/hmr/server/websocket-runtime-compat.js.map +1 -0
  165. package/hmr/server/websocket-served-module-helpers.d.ts +36 -0
  166. package/hmr/server/websocket-served-module-helpers.js +631 -0
  167. package/hmr/server/websocket-served-module-helpers.js.map +1 -0
  168. package/hmr/server/websocket-txn.d.ts +6 -0
  169. package/hmr/server/websocket-txn.js +45 -0
  170. package/hmr/server/websocket-txn.js.map +1 -0
  171. package/hmr/server/websocket-vendor-unifier.d.ts +10 -0
  172. package/hmr/server/websocket-vendor-unifier.js +51 -0
  173. package/hmr/server/websocket-vendor-unifier.js.map +1 -0
  174. package/hmr/server/websocket-vue-sfc.d.ts +27 -0
  175. package/hmr/server/websocket-vue-sfc.js +1069 -0
  176. package/hmr/server/websocket-vue-sfc.js.map +1 -0
  177. package/hmr/server/websocket.d.ts +26 -3
  178. package/hmr/server/websocket.js +2233 -796
  179. package/hmr/server/websocket.js.map +1 -1
  180. package/hmr/shared/package-classifier.d.ts +9 -0
  181. package/hmr/shared/package-classifier.js +58 -0
  182. package/hmr/shared/package-classifier.js.map +1 -0
  183. package/hmr/shared/runtime/boot-timeline.d.ts +17 -0
  184. package/hmr/shared/runtime/boot-timeline.js +51 -0
  185. package/hmr/shared/runtime/boot-timeline.js.map +1 -0
  186. package/hmr/shared/runtime/browser-runtime-contract.d.ts +64 -0
  187. package/hmr/shared/runtime/browser-runtime-contract.js +54 -0
  188. package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -0
  189. package/hmr/shared/runtime/dev-overlay.d.ts +85 -0
  190. package/hmr/shared/runtime/dev-overlay.js +1236 -0
  191. package/hmr/shared/runtime/dev-overlay.js.map +1 -0
  192. package/hmr/shared/runtime/http-only-boot.d.ts +1 -0
  193. package/hmr/shared/runtime/http-only-boot.js +53 -6
  194. package/hmr/shared/runtime/http-only-boot.js.map +1 -1
  195. package/hmr/shared/runtime/module-provenance.d.ts +1 -0
  196. package/hmr/shared/runtime/module-provenance.js +63 -0
  197. package/hmr/shared/runtime/module-provenance.js.map +1 -0
  198. package/hmr/shared/runtime/platform-polyfills.d.ts +26 -0
  199. package/hmr/shared/runtime/platform-polyfills.js +122 -0
  200. package/hmr/shared/runtime/platform-polyfills.js.map +1 -0
  201. package/hmr/shared/runtime/root-placeholder.d.ts +1 -0
  202. package/hmr/shared/runtime/root-placeholder.js +552 -82
  203. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  204. package/hmr/shared/runtime/session-bootstrap.d.ts +1 -0
  205. package/hmr/shared/runtime/session-bootstrap.js +195 -0
  206. package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
  207. package/hmr/shared/runtime/vendor-bootstrap.js +52 -15
  208. package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
  209. package/hmr/shared/vendor/manifest.d.ts +37 -0
  210. package/hmr/shared/vendor/manifest.js +677 -57
  211. package/hmr/shared/vendor/manifest.js.map +1 -1
  212. package/hmr/shared/vendor/registry.js +104 -7
  213. package/hmr/shared/vendor/registry.js.map +1 -1
  214. package/index.d.ts +1 -0
  215. package/index.js +5 -0
  216. package/index.js.map +1 -1
  217. package/package.json +14 -2
  218. package/runtime/core-aliases-early.js +94 -67
  219. package/runtime/core-aliases-early.js.map +1 -1
  220. package/shims/solid-jsx-runtime.d.ts +7 -0
  221. package/shims/solid-jsx-runtime.js +17 -0
  222. package/shims/solid-jsx-runtime.js.map +1 -0
@@ -5,9 +5,41 @@
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
10
  const VERBOSE = typeof __NS_ENV_VERBOSE__ !== 'undefined' && __NS_ENV_VERBOSE__;
11
+ function resolveTargetFlavor() {
12
+ try {
13
+ if (typeof __NS_TARGET_FLAVOR__ !== 'undefined' && __NS_TARGET_FLAVOR__) {
14
+ return __NS_TARGET_FLAVOR__;
15
+ }
16
+ }
17
+ catch { }
18
+ try {
19
+ const g = globalThis;
20
+ if (typeof g.__NS_TARGET_FLAVOR__ === 'string' && g.__NS_TARGET_FLAVOR__) {
21
+ return g.__NS_TARGET_FLAVOR__;
22
+ }
23
+ if (typeof g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__ === 'string' && g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__) {
24
+ return g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__;
25
+ }
26
+ if (typeof g.__reboot_ng_modules__ === 'function') {
27
+ return 'angular';
28
+ }
29
+ if (g.__VUE_HMR_RUNTIME__ || g.__NS_HMR_VUE_SFC_REGISTRY__) {
30
+ return 'vue';
31
+ }
32
+ }
33
+ catch { }
34
+ return undefined;
35
+ }
36
+ const TARGET_FLAVOR = resolveTargetFlavor();
37
+ try {
38
+ if (TARGET_FLAVOR && !globalThis.__NS_TARGET_FLAVOR__) {
39
+ globalThis.__NS_TARGET_FLAVOR__ = TARGET_FLAVOR;
40
+ }
41
+ }
42
+ catch { }
11
43
  const APP_ROOT_VIRTUAL = typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__ ? __NS_APP_ROOT_VIRTUAL__ : '/src';
12
44
  const APP_VIRTUAL_WITH_SLASH = APP_ROOT_VIRTUAL.endsWith('/') ? APP_ROOT_VIRTUAL : `${APP_ROOT_VIRTUAL}/`;
13
45
  const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
@@ -29,8 +61,9 @@ function ensureCoreAliasesOnGlobalThis() {
29
61
  }
30
62
  catch { }
31
63
  try {
32
- if (A && !g.Application)
64
+ if (A && (!g.Application || typeof g.Application.run !== 'function' || typeof g.Application.on !== 'function' || typeof g.Application.resetRootView !== 'function')) {
33
65
  g.Application = A;
66
+ }
34
67
  }
35
68
  catch { }
36
69
  try {
@@ -43,12 +76,95 @@ function ensureCoreAliasesOnGlobalThis() {
43
76
  }
44
77
  // Apply once on module evaluation
45
78
  ensureCoreAliasesOnGlobalThis();
79
+ function getHmrOverlayApi() {
80
+ try {
81
+ return globalThis.__NS_HMR_DEV_OVERLAY__ || null;
82
+ }
83
+ catch { }
84
+ return null;
85
+ }
86
+ function setConnectionOverlayStage(stage, detail) {
87
+ try {
88
+ const api = getHmrOverlayApi();
89
+ if (api && typeof api.setConnectionStage === 'function') {
90
+ api.setConnectionStage(stage, { detail });
91
+ }
92
+ }
93
+ catch { }
94
+ }
95
+ function hideConnectionOverlay() {
96
+ try {
97
+ const api = getHmrOverlayApi();
98
+ if (api && typeof api.hide === 'function') {
99
+ api.hide('healthy');
100
+ }
101
+ }
102
+ catch { }
103
+ }
104
+ // Eagerly drive the HMR-applying overlay's 'received' frame as soon
105
+ // as the server emits `ns:hmr-pending`, BEFORE the framework-specific
106
+ // (`ns:angular-update` / `ns:css-updates`) payload arrives. The
107
+ // flavor-specific handler later walks through 'evicting' →
108
+ // 'reimporting' → 'rebooting' → 'complete'. Calling 'received' twice
109
+ // in the same cycle is safe: the overlay preserves
110
+ // `updateCycleStartedAt` when a 'received' frame replaces an existing
111
+ // 'received' frame so the minimum-visible window is still timed
112
+ // against the FIRST frame.
113
+ //
114
+ // Soft-fails when the overlay isn't installed (production builds,
115
+ // vitest, etc.) or when the user opted out via
116
+ // `__NS_HMR_PROGRESS_OVERLAY_ENABLED__ === false`.
117
+ import { applyHmrPendingFrame } from './hmr-pending-overlay.js';
118
+ function setHmrPendingOverlay(filePath) {
119
+ applyHmrPendingFrame(filePath, { getOverlay: getHmrOverlayApi });
120
+ }
121
+ let connectionOverlayTimer = null;
122
+ let connectionOverlayVisible = false;
123
+ let hasOpenedHmrSocket = false;
124
+ let awaitingHealthyHmrMessage = false;
125
+ let pendingConnectionOverlayStage = 'connecting';
126
+ let pendingConnectionOverlayDetail = '';
127
+ function clearConnectionOverlayTimer() {
128
+ if (connectionOverlayTimer) {
129
+ clearTimeout(connectionOverlayTimer);
130
+ connectionOverlayTimer = null;
131
+ }
132
+ }
133
+ function showConnectionOverlayNow(stage, detail) {
134
+ pendingConnectionOverlayStage = stage;
135
+ pendingConnectionOverlayDetail = detail || '';
136
+ connectionOverlayVisible = true;
137
+ setConnectionOverlayStage(stage, detail);
138
+ }
139
+ function scheduleConnectionOverlay(stage, detail, delayMs = 1200) {
140
+ pendingConnectionOverlayStage = stage;
141
+ pendingConnectionOverlayDetail = detail || '';
142
+ clearConnectionOverlayTimer();
143
+ connectionOverlayTimer = setTimeout(() => {
144
+ showConnectionOverlayNow(pendingConnectionOverlayStage, pendingConnectionOverlayDetail);
145
+ }, delayMs);
146
+ }
147
+ function updateConnectionOverlay(stage, detail) {
148
+ pendingConnectionOverlayStage = stage;
149
+ pendingConnectionOverlayDetail = detail || '';
150
+ if (connectionOverlayVisible) {
151
+ showConnectionOverlayNow(stage, detail);
152
+ }
153
+ }
154
+ function markHmrConnectionHealthy() {
155
+ awaitingHealthyHmrMessage = false;
156
+ clearConnectionOverlayTimer();
157
+ if (connectionOverlayVisible) {
158
+ connectionOverlayVisible = false;
159
+ hideConnectionOverlay();
160
+ }
161
+ }
46
162
  /**
47
163
  * Flavor hooks
48
164
  */
49
- import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate } from '../frameworks/vue/client/index.js';
165
+ import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate, sfcArtifactMap } from '../frameworks/vue/client/index.js';
50
166
  import { handleAngularHotUpdateMessage, installAngularHmrClientHooks } from '../frameworks/angular/client/index.js';
51
- switch (__NS_TARGET_FLAVOR__) {
167
+ switch (TARGET_FLAVOR) {
52
168
  case 'vue':
53
169
  installNsVueDevShims();
54
170
  break;
@@ -60,6 +176,10 @@ switch (__NS_TARGET_FLAVOR__) {
60
176
  let initialMounted = !!globalThis.__NS_HMR_BOOT_COMPLETE__;
61
177
  // Prevent duplicate initial-mount scheduling across rapid full-graph broadcasts and re-evaluations
62
178
  let initialMounting = !!globalThis.__NS_HMR_INITIAL_MOUNT_IN_PROGRESS__;
179
+ // Track whether the first full-graph has been received. Before the full-graph,
180
+ // delta messages are just the server discovering modules during initial boot —
181
+ // NOT actual code changes. Re-imports must be gated behind this flag.
182
+ let hasReceivedFullGraph = false;
63
183
  // TypeScript flavor: track registry modules and inferred main id
64
184
  let tsModuleSet = null;
65
185
  let tsMainId = null;
@@ -94,10 +214,14 @@ function applyFullGraph(payload) {
94
214
  console.log('[hmr][graph] full graph applied version', getGraphVersion(), 'modules=', graph.size);
95
215
  // Guarded initial mount rescue: if app hasn't replaced the placeholder shortly after graph arrives,
96
216
  // perform a one-time mount. This waits briefly to let the app's main entry start() run first to avoid double mounts.
217
+ // TypeScript flavor: skip the rescue entirely. The HTTP boot will load main.ts which
218
+ // calls Application.run() (patched to resetRootView) — the rescue is redundant and
219
+ // causes a double-mount race (rescue fires at 450ms, then main.ts fires ~1s later,
220
+ // causing a visual flash and leaving the app in an inconsistent state).
97
221
  try {
98
222
  const g = globalThis;
99
223
  const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
100
- if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__) {
224
+ if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && TARGET_FLAVOR !== 'typescript') {
101
225
  // simple snapshot helpers
102
226
  const getTopmost = () => {
103
227
  try {
@@ -170,7 +294,7 @@ function applyFullGraph(payload) {
170
294
  if (VERBOSE)
171
295
  console.log('[hmr][init] placeholder persists after delay; evaluating rescue policy');
172
296
  // Flavor-specific rescue handling
173
- if (__NS_TARGET_FLAVOR__ === 'typescript') {
297
+ if (TARGET_FLAVOR === 'typescript') {
174
298
  // For TS apps, perform a one-time resetRootView to the conventional
175
299
  // app root module. This mimics what Application.run would do and
176
300
  // replaces the placeholder with the real UI without trying to
@@ -211,7 +335,7 @@ function applyFullGraph(payload) {
211
335
  return;
212
336
  }
213
337
  let candidate = null;
214
- switch (__NS_TARGET_FLAVOR__) {
338
+ switch (TARGET_FLAVOR) {
215
339
  case 'vue': {
216
340
  const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
217
341
  if (appEntry && Array.isArray(appEntry.deps)) {
@@ -227,6 +351,19 @@ function applyFullGraph(payload) {
227
351
  }
228
352
  }
229
353
  }
354
+ // Fallback: when the module graph is empty (Vite 7+ may not populate it
355
+ // before the first full-graph broadcast), check the SFC artifact registry
356
+ // which is populated from the ns:vue-sfc-registry message.
357
+ if (!candidate && sfcArtifactMap.size > 0) {
358
+ for (const id of sfcArtifactMap.keys()) {
359
+ if (/\.vue$/i.test(id)) {
360
+ candidate = id;
361
+ if (VERBOSE)
362
+ console.log('[hmr][init] rescue candidate from SFC registry:', id);
363
+ break;
364
+ }
365
+ }
366
+ }
230
367
  break;
231
368
  }
232
369
  }
@@ -240,7 +377,7 @@ function applyFullGraph(payload) {
240
377
  (async () => {
241
378
  try {
242
379
  let comp = null;
243
- switch (__NS_TARGET_FLAVOR__) {
380
+ switch (TARGET_FLAVOR) {
244
381
  case 'vue':
245
382
  comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
246
383
  break;
@@ -285,7 +422,7 @@ function applyFullGraph(payload) {
285
422
  // to avoid double-mount races that can cause duplicate navigation logs.
286
423
  if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
287
424
  let candidate = null;
288
- switch (__NS_TARGET_FLAVOR__) {
425
+ switch (TARGET_FLAVOR) {
289
426
  case 'vue': {
290
427
  const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
291
428
  if (appEntry && Array.isArray(appEntry.deps)) {
@@ -301,6 +438,17 @@ function applyFullGraph(payload) {
301
438
  }
302
439
  }
303
440
  }
441
+ // Fallback: SFC registry (same as rescue mount above)
442
+ if (!candidate && sfcArtifactMap.size > 0) {
443
+ for (const id of sfcArtifactMap.keys()) {
444
+ if (/\.vue$/i.test(id)) {
445
+ candidate = id;
446
+ if (VERBOSE)
447
+ console.log('[hmr][init] initial mount candidate from SFC registry:', id);
448
+ break;
449
+ }
450
+ }
451
+ }
304
452
  break;
305
453
  }
306
454
  case 'typescript': {
@@ -318,7 +466,7 @@ function applyFullGraph(payload) {
318
466
  (async () => {
319
467
  try {
320
468
  if (VERBOSE)
321
- console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', __NS_TARGET_FLAVOR__);
469
+ console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
322
470
  // Android-only: avoid racing entry-runtime reset and Activity bring-up
323
471
  try {
324
472
  const g = globalThis;
@@ -352,7 +500,7 @@ function applyFullGraph(payload) {
352
500
  }
353
501
  catch { }
354
502
  let comp = null;
355
- switch (__NS_TARGET_FLAVOR__) {
503
+ switch (TARGET_FLAVOR) {
356
504
  case 'vue':
357
505
  comp = await loadSfcComponent(candidate, 'initial_mount');
358
506
  break;
@@ -401,7 +549,7 @@ function applyFullGraph(payload) {
401
549
  })();
402
550
  }
403
551
  else if (VERBOSE) {
404
- console.warn('[hmr][init] no component found in graph to mount initially for flavor', __NS_TARGET_FLAVOR__);
552
+ console.warn('[hmr][init] no component found in graph to mount initially for flavor', TARGET_FLAVOR);
405
553
  }
406
554
  }
407
555
  }
@@ -427,7 +575,7 @@ function applyDelta(payload) {
427
575
  setGraphVersion(payload.newVersion);
428
576
  }
429
577
  const changed = payload.changed || [];
430
- switch (__NS_TARGET_FLAVOR__) {
578
+ switch (TARGET_FLAVOR) {
431
579
  case 'vue':
432
580
  recordVuePayloadChanges(changed, getGraphVersion());
433
581
  break;
@@ -445,6 +593,16 @@ function applyDelta(payload) {
445
593
  console.log('[hmr][graph] delta applied newVersion', getGraphVersion(), 'changed=', (payload.changed || []).length, 'removed=', (payload.removed || []).length, 'baseVersion=', payload.baseVersion);
446
594
  // Queue evaluation of changed modules (placeholder pipeline)
447
595
  if (payload.changed?.length) {
596
+ // Gate: Before the first full-graph is received, delta messages are the server
597
+ // discovering modules during initial boot — NOT actual code changes. The entry-runtime
598
+ // already loaded all modules; re-importing them would cause duplicate Application.run(),
599
+ // router initialization, and modal conflicts. Only queue re-imports after the first
600
+ // full-graph confirms the graph is synced and subsequent deltas are real changes.
601
+ if (!hasReceivedFullGraph) {
602
+ if (VERBOSE)
603
+ console.log('[hmr][delta] skipping re-import queue (initial graph build, no full-graph yet)');
604
+ return;
605
+ }
448
606
  // HARD SUPPRESS: the very first delta (baseVersion 0) commonly includes the app main entry which is already evaluated during bootstrap.
449
607
  // Importing it again with a cache-bust often produces a spurious module-not-found (timestamp param treated as distinct file).
450
608
  const isInitial = payload.baseVersion === 0;
@@ -507,7 +665,7 @@ function applyDelta(payload) {
507
665
  // Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
508
666
  function __nsNavigateUsingApp(comp, opts = {}) {
509
667
  const g = globalThis;
510
- switch (__NS_TARGET_FLAVOR__) {
668
+ switch (TARGET_FLAVOR) {
511
669
  case 'vue':
512
670
  ensureVueGlobals();
513
671
  break;
@@ -535,7 +693,7 @@ function __nsNavigateUsingApp(comp, opts = {}) {
535
693
  const existingApp = getCurrentApp();
536
694
  const baseProvides = (existingApp && existingApp._context && existingApp._context.provides) || {};
537
695
  const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)));
538
- switch (__NS_TARGET_FLAVOR__) {
696
+ switch (TARGET_FLAVOR) {
539
697
  case 'vue':
540
698
  ensurePiniaOnApp(app);
541
699
  break;
@@ -669,6 +827,25 @@ async function processQueue() {
669
827
  return;
670
828
  if (VERBOSE)
671
829
  console.log('[hmr][queue] processing changed ids', drained);
830
+ // Explicit eviction step.
831
+ //
832
+ // On modern runtimes the URL canonicalizer collapses any
833
+ // `__ns_hmr__/<tag>/` segment back to a stable cache key, so
834
+ // without explicit eviction the upcoming `import(url)` would
835
+ // resolve via V8's `g_moduleRegistry` and return the cached
836
+ // stale module — making the queue drain a silent no-op for
837
+ // every save after the first.
838
+ //
839
+ // We hand the canonical eviction URLs to the runtime first;
840
+ // `invalidateModulesByUrls` is a no-op on older runtimes and
841
+ // `requestModuleFromServer` automatically falls back to the
842
+ // legacy `/ns/m/__ns_hmr__/v<N>/` URL versioning path in that
843
+ // case. node_modules and virtual specs are filtered out by
844
+ // `buildEvictionUrls` so vendor modules stay hot.
845
+ const evictUrls = buildEvictionUrls(drained);
846
+ const evicted = invalidateModulesByUrls(evictUrls);
847
+ if (VERBOSE)
848
+ console.log(`[hmr][queue] eviction count=${evictUrls.length} ok=${evicted}`);
672
849
  // Evaluate changed modules best-effort; failures shouldn't completely break HMR.
673
850
  for (const id of drained) {
674
851
  try {
@@ -686,10 +863,176 @@ async function processQueue() {
686
863
  }
687
864
  }
688
865
  // After evaluating the batch, perform flavor-specific UI refresh.
689
- switch (__NS_TARGET_FLAVOR__) {
866
+ switch (TARGET_FLAVOR) {
690
867
  case 'vue':
691
868
  // Vue SFCs are handled via the registry update path; nothing to do here.
692
869
  break;
870
+ case 'solid': {
871
+ // Solid .tsx components are self-accepting via solid-refresh's inline
872
+ // patchRegistry — re-importing them is sufficient. For non-component
873
+ // .ts utility modules, we must propagate up the import graph to find
874
+ // the .tsx/.jsx component boundaries and re-import those so their
875
+ // solid-refresh proxies pick up the new dependency values.
876
+ try {
877
+ // Build reverse index: dep id → list of importer ids
878
+ const reverseIndex = new Map();
879
+ for (const [id, mod] of graph) {
880
+ for (const dep of mod.deps) {
881
+ let arr = reverseIndex.get(dep);
882
+ if (!arr) {
883
+ arr = [];
884
+ reverseIndex.set(dep, arr);
885
+ }
886
+ arr.push(id);
887
+ }
888
+ }
889
+ // BFS from each non-tsx changed module up to tsx/jsx boundaries
890
+ const boundaries = new Set();
891
+ for (const id of drained) {
892
+ if (/\.(tsx|jsx)$/i.test(id))
893
+ continue; // already self-accepting
894
+ const visited = new Set();
895
+ const queue = [id];
896
+ while (queue.length) {
897
+ const cur = queue.shift();
898
+ if (visited.has(cur))
899
+ continue;
900
+ visited.add(cur);
901
+ const importers = reverseIndex.get(cur);
902
+ if (!importers)
903
+ continue;
904
+ for (const imp of importers) {
905
+ if (/\.(tsx|jsx)$/i.test(imp)) {
906
+ boundaries.add(imp);
907
+ }
908
+ else {
909
+ queue.push(imp);
910
+ }
911
+ }
912
+ }
913
+ }
914
+ // Re-import each boundary so solid-refresh patchRegistry fires.
915
+ // For route files (TanStack Router), capture the new Route export
916
+ // and patch the router's existing route with the fresh loader.
917
+ let routesPatchCount = 0;
918
+ let discoveredRouter = null;
919
+ // Discover router: try __ns_router global (set by createNativeScriptRouter),
920
+ // then scan globalThis for any router-shaped object with routesById.
921
+ const findRouter = () => {
922
+ if (discoveredRouter)
923
+ return discoveredRouter;
924
+ const g = globalThis;
925
+ if (g.__ns_router?.routesById)
926
+ return (discoveredRouter = g.__ns_router);
927
+ // Fallback: scan common global keys for router
928
+ for (const key of ['__ns_router', 'router', '__router']) {
929
+ if (g[key]?.routesById && g[key]?.invalidate)
930
+ return (discoveredRouter = g[key]);
931
+ }
932
+ return null;
933
+ };
934
+ // Convert boundary file path to TanStack Router fullPath.
935
+ // e.g. /src/routes/posts.$postId.tsx → /posts/$postId
936
+ const boundaryToFullPath = (bid) => {
937
+ const m = bid.match(/\/src\/routes\/(.+)\.(tsx|jsx|ts|js)$/i);
938
+ if (!m)
939
+ return null;
940
+ let p = m[1];
941
+ // Replace dots between segments with slashes (posts.$postId → posts/$postId)
942
+ p = p.replace(/\./g, '/');
943
+ // Handle index files
944
+ if (p === 'index')
945
+ return '/';
946
+ if (p.endsWith('/index'))
947
+ p = p.slice(0, -6);
948
+ // Strip leading - (TanStack pathless layout convention)
949
+ p = p.replace(/(^|\/)-([\w])/g, '$1$2');
950
+ return '/' + p;
951
+ };
952
+ // Find existing route by fullPath (since new Route has no id yet)
953
+ const findRouteByFullPath = (router, fp) => {
954
+ if (!router?.routesById)
955
+ return null;
956
+ for (const rid of Object.keys(router.routesById)) {
957
+ const r = router.routesById[rid];
958
+ if (r?.fullPath === fp)
959
+ return r;
960
+ }
961
+ return null;
962
+ };
963
+ // Evict the boundary set so re-importing each .tsx
964
+ // component actually picks up the new transitive
965
+ // dependency code; without this V8 returns the
966
+ // cached boundary module unchanged.
967
+ const boundaryIds = Array.from(boundaries);
968
+ const solidEvictUrls = buildEvictionUrls(boundaryIds);
969
+ const solidEvicted = invalidateModulesByUrls(solidEvictUrls);
970
+ if (VERBOSE)
971
+ console.log(`[hmr][solid] eviction count=${solidEvictUrls.length} ok=${solidEvicted}`);
972
+ for (const id of boundaries) {
973
+ if (seen.has(id))
974
+ continue;
975
+ try {
976
+ const spec = normalizeSpec(id);
977
+ const url = await requestModuleFromServer(spec);
978
+ if (!url)
979
+ continue;
980
+ if (VERBOSE)
981
+ console.log('[hmr][solid] propagated to boundary', { id, url });
982
+ const mod = await import(/* @vite-ignore */ url);
983
+ // Patch TanStack Router route loaders
984
+ try {
985
+ const newRoute = mod?.Route;
986
+ if (newRoute?.options?.loader) {
987
+ const router = findRouter();
988
+ const fullPath = boundaryToFullPath(id);
989
+ if (VERBOSE)
990
+ console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, routesByIdKeys: router?.routesById ? Object.keys(router.routesById) : 'none' });
991
+ const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
992
+ if (existingRoute?.options) {
993
+ existingRoute.options.loader = newRoute.options.loader;
994
+ if (newRoute.options.component)
995
+ existingRoute.options.component = newRoute.options.component;
996
+ routesPatchCount++;
997
+ if (VERBOSE)
998
+ console.log('[hmr][solid] patched route loader', existingRoute.id, 'fullPath=', fullPath);
999
+ }
1000
+ else if (VERBOSE) {
1001
+ console.log('[hmr][solid] no matching route for fullPath', fullPath);
1002
+ }
1003
+ }
1004
+ }
1005
+ catch (e) {
1006
+ if (VERBOSE)
1007
+ console.warn('[hmr][solid] route patch error', id, e);
1008
+ }
1009
+ }
1010
+ catch (e) {
1011
+ if (VERBOSE)
1012
+ console.warn('[hmr][solid] boundary re-import failed', id, e);
1013
+ }
1014
+ }
1015
+ // Route loaders were patched with fresh closures. The data is
1016
+ // correct in router.state.matches[].loaderData (confirmed via
1017
+ // diagnostics), but the currently visible page doesn't re-render
1018
+ // because the NativeScriptRouterProvider's Solid store flush only
1019
+ // fires through history.subscribe. TODO: find the right mechanism
1020
+ // to trigger a Solid reactive update for the active match stores.
1021
+ if (routesPatchCount > 0 && VERBOSE) {
1022
+ console.log('[hmr][solid] patched', routesPatchCount, 'route loaders (data correct in match state, pending UI refresh mechanism)');
1023
+ }
1024
+ if (VERBOSE) {
1025
+ if (boundaries.size)
1026
+ console.log('[hmr][solid] propagated non-component change to', boundaries.size, 'boundaries', Array.from(boundaries));
1027
+ console.log('[hmr][queue] Solid: modules re-imported, solid-refresh handles reactive update', drained);
1028
+ }
1029
+ }
1030
+ catch (e) {
1031
+ if (VERBOSE)
1032
+ console.warn('[hmr][solid] propagation failed', e);
1033
+ }
1034
+ break;
1035
+ }
693
1036
  case 'typescript': {
694
1037
  // For TS apps, always reset back to the conventional app root.
695
1038
  // This preserves the shell (Frame, ActionBar, etc.) that the app's
@@ -702,9 +1045,124 @@ async function processQueue() {
702
1045
  console.warn('[hmr][queue] TS flavor: Application.resetRootView unavailable; skipping UI refresh');
703
1046
  break;
704
1047
  }
1048
+ // Re-fetch changed XML/CSS files and update the bundled module registry
1049
+ // so Builder.createViewFromEntry picks up fresh content.
1050
+ const rawAssetIds = drained.filter((id) => /\.(xml|css|scss|sass|less)$/i.test(id));
1051
+ if (rawAssetIds.length && typeof g.registerModule === 'function') {
1052
+ const origin = getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
1053
+ if (origin) {
1054
+ for (const id of rawAssetIds) {
1055
+ try {
1056
+ const spec = normalizeSpec(id);
1057
+ // Fetch the raw file content directly from Vite's dev server.
1058
+ // Use the project-relative path which Vite serves as static files.
1059
+ const fetchUrl = origin + (spec.startsWith('/') ? spec : '/' + spec);
1060
+ if (VERBOSE)
1061
+ console.log('[hmr][queue] fetching raw asset', { id, fetchUrl });
1062
+ const resp = await fetch(fetchUrl);
1063
+ if (resp.ok) {
1064
+ const rawContent = await resp.text();
1065
+ // Register under all nickname variants the module registry uses.
1066
+ // The bundler context registers XML as e.g., './main-page.xml' and 'main-page.xml'
1067
+ const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1068
+ let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1069
+ if (relPath.startsWith(appVirtual))
1070
+ relPath = relPath.slice(appVirtual.length);
1071
+ const nicknames = ['./' + relPath, relPath];
1072
+ // Also add without extension for CSS
1073
+ const extIdx = relPath.lastIndexOf('.');
1074
+ if (extIdx > 0) {
1075
+ const baseName = relPath.slice(0, extIdx);
1076
+ if (!relPath.endsWith('.xml'))
1077
+ nicknames.push(baseName, './' + baseName);
1078
+ }
1079
+ for (const name of nicknames) {
1080
+ if (VERBOSE)
1081
+ console.log('[hmr][queue] re-registering module', name);
1082
+ g.registerModule(name, () => rawContent);
1083
+ }
1084
+ }
1085
+ else if (VERBOSE) {
1086
+ console.warn('[hmr][queue] raw asset fetch failed', id, resp.status);
1087
+ }
1088
+ }
1089
+ catch (e) {
1090
+ if (VERBOSE)
1091
+ console.warn('[hmr][queue] raw asset refresh failed for', id, e);
1092
+ }
1093
+ }
1094
+ }
1095
+ }
1096
+ // Determine if we can navigate in-place to a changed page
1097
+ // instead of resetting all the way back to app-root.
1098
+ // This keeps the user on the page they're editing for faster iteration.
1099
+ const changedXmlPages = drained
1100
+ .filter((id) => /\.xml$/i.test(id))
1101
+ .map((id) => {
1102
+ const spec = normalizeSpec(id);
1103
+ const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1104
+ let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1105
+ if (relPath.startsWith(appVirtual))
1106
+ relPath = relPath.slice(appVirtual.length);
1107
+ // Strip .xml extension to get the moduleName (e.g., 'pages/status-bar')
1108
+ return relPath.replace(/\.xml$/i, '');
1109
+ })
1110
+ .filter((m) => m && m !== 'app-root');
1111
+ // Resolve the topmost Frame from the bundled realm.
1112
+ // Frame.topmost() relies on an internal frameStack array, so we must
1113
+ // call it on the bundled-realm class. Multiple strategies to find it:
1114
+ const FrameClass = getCore('Frame') || g.Frame;
1115
+ let topFrame = null;
1116
+ // 1) Try the vendor-realm static topmost()
1117
+ try {
1118
+ topFrame = FrameClass?.topmost?.();
1119
+ }
1120
+ catch { }
1121
+ // 2) Try getting the root view from Application — if it's a Frame, use it
1122
+ if (!topFrame) {
1123
+ try {
1124
+ const rootView = App.getRootView?.() || App._rootView;
1125
+ if (rootView) {
1126
+ // rootView could be a Frame itself, or contain a Frame
1127
+ const isFrame = rootView.constructor?.name === 'Frame' || rootView.navigate;
1128
+ if (isFrame) {
1129
+ topFrame = rootView;
1130
+ }
1131
+ else if (rootView.getChildAt) {
1132
+ // Walk direct children looking for a Frame
1133
+ for (let i = 0; i < (rootView.getChildrenCount?.() || 0); i++) {
1134
+ const child = rootView.getChildAt(i);
1135
+ if (child?.constructor?.name === 'Frame' || child?.navigate) {
1136
+ topFrame = child;
1137
+ break;
1138
+ }
1139
+ }
1140
+ }
1141
+ }
1142
+ }
1143
+ catch { }
1144
+ }
705
1145
  if (VERBOSE)
706
- console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
707
- App.resetRootView({ moduleName: 'app-root' });
1146
+ console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame);
1147
+ if (changedXmlPages.length > 0 && topFrame) {
1148
+ // Navigate the current frame to the changed page directly.
1149
+ // Use the last changed XML page (most specific).
1150
+ const moduleName = changedXmlPages[changedXmlPages.length - 1];
1151
+ if (VERBOSE)
1152
+ console.log('[hmr][queue] TS: navigating in-place to', moduleName);
1153
+ try {
1154
+ topFrame.navigate({ moduleName, clearHistory: false, animated: false });
1155
+ }
1156
+ catch (navErr) {
1157
+ console.warn('[hmr][queue] TS flavor: in-place navigate failed, falling back to resetRootView', navErr);
1158
+ App.resetRootView({ moduleName: 'app-root' });
1159
+ }
1160
+ }
1161
+ else {
1162
+ if (VERBOSE)
1163
+ console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
1164
+ App.resetRootView({ moduleName: 'app-root' });
1165
+ }
708
1166
  }
709
1167
  catch (e) {
710
1168
  console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
@@ -752,6 +1210,11 @@ function connectHmr() {
752
1210
  console.log('[hmr-client] Already connecting to HMR WebSocket, skipping');
753
1211
  return;
754
1212
  }
1213
+ try {
1214
+ globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
1215
+ }
1216
+ catch { }
1217
+ const overlayStage = hasOpenedHmrSocket ? 'reconnecting' : 'connecting';
755
1218
  const baseUrl = getHMRWsUrl() || 'ws://localhost:5173/ns-hmr';
756
1219
  const buildCandidates = (url) => {
757
1220
  let candidates = [];
@@ -795,7 +1258,7 @@ function connectHmr() {
795
1258
  if (seen.has(key))
796
1259
  continue;
797
1260
  seen.add(key);
798
- const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}`;
1261
+ const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}${u.search || ''}`;
799
1262
  candidates.push(cand);
800
1263
  }
801
1264
  }
@@ -811,10 +1274,19 @@ function connectHmr() {
811
1274
  let idx = 0;
812
1275
  const tryNext = () => {
813
1276
  if (idx >= candidates.length) {
1277
+ showConnectionOverlayNow('offline', 'Waiting for the Vite websocket to come back.');
814
1278
  console.warn('[hmr-client] All WS candidates failed:', candidates.join(', '));
1279
+ setTimeout(connectHmr, 1500);
815
1280
  return;
816
1281
  }
817
1282
  const url = candidates[idx++];
1283
+ const connectionDetail = `${overlayStage === 'reconnecting' ? 'Retrying' : 'Opening'} ${url}`;
1284
+ if (connectionOverlayVisible) {
1285
+ updateConnectionOverlay(overlayStage, connectionDetail);
1286
+ }
1287
+ else {
1288
+ scheduleConnectionOverlay(overlayStage, connectionDetail);
1289
+ }
818
1290
  try {
819
1291
  if (__NS_ENV_VERBOSE__)
820
1292
  console.log('[hmr-client] Connecting to HMR WebSocket:', url);
@@ -836,7 +1308,25 @@ function connectHmr() {
836
1308
  sock.onopen = () => {
837
1309
  opened = true;
838
1310
  clearTimeout(timeout);
1311
+ clearConnectionOverlayTimer();
1312
+ hasOpenedHmrSocket = true;
1313
+ awaitingHealthyHmrMessage = true;
1314
+ try {
1315
+ globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = true;
1316
+ }
1317
+ catch { }
1318
+ if (connectionOverlayVisible) {
1319
+ showConnectionOverlayNow('synchronizing', 'Connected. Synchronizing the HMR graph.');
1320
+ }
839
1321
  VERBOSE && console.log('[hmr-client] Connected to HMR WebSocket');
1322
+ // Print the active module reload mode once on first
1323
+ // successful connect so the user can correlate HMR latency
1324
+ // with runtime capability without grepping for protocol
1325
+ // details. The banner is verbose-gated.
1326
+ try {
1327
+ emitHmrModeBannerOnce();
1328
+ }
1329
+ catch { }
840
1330
  };
841
1331
  sock.onmessage = handleHmrMessage;
842
1332
  sock.onerror = (error) => {
@@ -845,6 +1335,10 @@ function connectHmr() {
845
1335
  };
846
1336
  sock.onclose = (ev) => {
847
1337
  clearTimeout(timeout);
1338
+ try {
1339
+ globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
1340
+ }
1341
+ catch { }
848
1342
  if (!opened) {
849
1343
  // immediate failure during connect → try another candidate
850
1344
  if (VERBOSE)
@@ -854,6 +1348,7 @@ function connectHmr() {
854
1348
  else {
855
1349
  if (VERBOSE)
856
1350
  console.log('[hmr-client] WebSocket closed (code', ev?.code, '), will reconnect…');
1351
+ scheduleConnectionOverlay('reconnecting', 'The websocket closed. Waiting to reconnect.', 700);
857
1352
  // try to reconnect with full candidate list again
858
1353
  setTimeout(connectHmr, 1000);
859
1354
  }
@@ -874,6 +1369,12 @@ async function handleHmrMessage(ev) {
874
1369
  catch {
875
1370
  return;
876
1371
  }
1372
+ if (awaitingHealthyHmrMessage && msg) {
1373
+ markHmrConnectionHealthy();
1374
+ }
1375
+ if (VERBOSE && msg?.type) {
1376
+ console.log('[hmr-client] received message', msg.type);
1377
+ }
877
1378
  // Notify optional app-level hook after an HMR batch is applied.
878
1379
  function notifyAppHmrUpdate(kind, changedIds) {
879
1380
  try {
@@ -885,6 +1386,17 @@ async function handleHmrMessage(ev) {
885
1386
  catch { }
886
1387
  }
887
1388
  if (msg) {
1389
+ // `ns:hmr-pending` is a fire-and-forget UX hint emitted by the
1390
+ // server at the START of handleHotUpdate. We drive the
1391
+ // HMR-applying overlay's 'received' frame here (synchronously),
1392
+ // well before the authoritative payload (`ns:angular-update` /
1393
+ // `ns:css-updates`) lands. Skip running any other handlers —
1394
+ // the pending message has no module payload and intentionally
1395
+ // does not bump the graph version.
1396
+ if (msg.type === 'ns:hmr-pending' && typeof msg.path === 'string') {
1397
+ setHmrPendingOverlay(msg.path);
1398
+ return;
1399
+ }
888
1400
  if (msg.type === 'ns:hmr-full-graph') {
889
1401
  // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
890
1402
  try {
@@ -896,6 +1408,34 @@ async function handleHmrMessage(ev) {
896
1408
  const prevGraph = new Map(graph);
897
1409
  setGraphVersion(Number(msg.version || getGraphVersion() || 0));
898
1410
  applyFullGraph(msg);
1411
+ hasReceivedFullGraph = true;
1412
+ // Gate: On first boot, the entry-runtime handles all initial module loading
1413
+ // (with the import map already configured). Don't re-import here — the graph
1414
+ // is stored above for future HMR delta comparisons, but modules are already
1415
+ // loaded correctly via the entry-runtime boot sequence.
1416
+ //
1417
+ // Two cases to catch:
1418
+ // 1. Boot still in progress (__NS_HMR_BOOT_COMPLETE__ is false)
1419
+ // 2. Boot already finished but this is the FIRST full-graph (prevGraph was
1420
+ // empty). The WebSocket often connects after entry-runtime finishes, so
1421
+ // boot is "complete" but we still shouldn't re-import — all modules were
1422
+ // just loaded fresh. Only re-import on subsequent full-graphs (reconnect
1423
+ // scenarios) where prevGraph already has entries.
1424
+ if (!globalThis.__NS_HMR_BOOT_COMPLETE__) {
1425
+ if (VERBOSE)
1426
+ console.info('[hmr][full-graph] skipping initial re-import (boot in progress)');
1427
+ const fullIds = Array.isArray(msg.modules) ? msg.modules.map((m) => m?.id).filter(Boolean) : [];
1428
+ notifyAppHmrUpdate('full-graph', fullIds);
1429
+ return;
1430
+ }
1431
+ if (prevGraph.size === 0) {
1432
+ if (VERBOSE)
1433
+ console.info('[hmr][full-graph] skipping re-import on first graph after boot (modules already fresh)');
1434
+ const fullIds = Array.isArray(msg.modules) ? msg.modules.map((m) => m?.id).filter(Boolean) : [];
1435
+ notifyAppHmrUpdate('full-graph', fullIds);
1436
+ return;
1437
+ }
1438
+ // Reconnect / resync case — re-import changed modules as normal.
899
1439
  // In some cases (e.g. server chooses full-graph resync / page reload), we won't
900
1440
  // receive a delta queue to re-import changed TS modules. Without re-import,
901
1441
  // HTTP ESM caching means module bodies (and side effects) won't re-run.
@@ -926,6 +1466,14 @@ async function handleHmrMessage(ev) {
926
1466
  });
927
1467
  if (toReimport.length && VERBOSE)
928
1468
  console.log('[hmr][full-graph] inferred changed modules; re-importing', toReimport);
1469
+ // Evict the inferred changed set before re-importing.
1470
+ // See `processQueue` for the architectural rationale; the
1471
+ // full-graph code path is the resync fallback (server chose
1472
+ // not to send a delta) and shares the same V8 cache pitfall.
1473
+ const fgEvictUrls = buildEvictionUrls(toReimport);
1474
+ const fgEvicted = invalidateModulesByUrls(fgEvictUrls);
1475
+ if (VERBOSE)
1476
+ console.log(`[hmr][full-graph] eviction count=${fgEvictUrls.length} ok=${fgEvicted}`);
929
1477
  for (const id of toReimport) {
930
1478
  try {
931
1479
  const spec = normalizeSpec(id);
@@ -998,8 +1546,13 @@ async function handleHmrMessage(ev) {
998
1546
  notifyAppHmrUpdate('delta', deltaIds);
999
1547
  return;
1000
1548
  }
1001
- else if (handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
1002
- return;
1549
+ else {
1550
+ if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
1551
+ setGraphVersion(Number(msg.version || getGraphVersion() || 0));
1552
+ }
1553
+ if (await handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
1554
+ return;
1555
+ }
1003
1556
  }
1004
1557
  }
1005
1558
  // On-demand module fetch response (Option A)
@@ -1140,7 +1693,7 @@ async function performResetRoot(newComponent) {
1140
1693
  if (cachedRoot)
1141
1694
  return cachedRoot;
1142
1695
  try {
1143
- switch (__NS_TARGET_FLAVOR__) {
1696
+ switch (TARGET_FLAVOR) {
1144
1697
  case 'vue':
1145
1698
  cachedRoot = getRootForVue(newComponent, state);
1146
1699
  break;
@@ -1455,9 +2008,29 @@ export function initHmrClient(opts) {
1455
2008
  }
1456
2009
  g.__NS_HMR_CLIENT_ACTIVE__ = true;
1457
2010
  ensureCoreAliasesOnGlobalThis();
1458
- connectHmr();
2011
+ // Defer WebSocket connection until boot completes to avoid native V8 crashes
2012
+ // caused by concurrent WebSocket message handling + HTTP fetch during early startup.
2013
+ // The WebSocket is only needed for HMR updates, not the initial boot sequence.
2014
+ if (g.__NS_HMR_BOOT_COMPLETE__) {
2015
+ connectHmr();
2016
+ }
2017
+ else {
2018
+ const waitForBoot = () => {
2019
+ if (globalThis.__NS_HMR_BOOT_COMPLETE__) {
2020
+ if (VERBOSE)
2021
+ console.log('[hmr-client] boot complete, connecting HMR WebSocket');
2022
+ connectHmr();
2023
+ }
2024
+ else {
2025
+ setTimeout(waitForBoot, 100);
2026
+ }
2027
+ };
2028
+ if (VERBOSE)
2029
+ console.log('[hmr-client] deferring WebSocket connection until boot completes');
2030
+ setTimeout(waitForBoot, 100);
2031
+ }
1459
2032
  // Best-effort: install back wrapper even before first remount; original root may be captured later
1460
- switch (__NS_TARGET_FLAVOR__) {
2033
+ switch (TARGET_FLAVOR) {
1461
2034
  case 'vue':
1462
2035
  ensureBackWrapperInstalled(performResetRoot, getCore);
1463
2036
  break;