@nativescript/vite 8.0.0-alpha.1 → 8.0.0-alpha.11

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 (220) 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 -29
  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 +27 -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 +375 -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 -36
  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/project.d.ts +35 -0
  68. package/helpers/project.js +120 -2
  69. package/helpers/project.js.map +1 -1
  70. package/helpers/ts-config-paths.js +50 -2
  71. package/helpers/ts-config-paths.js.map +1 -1
  72. package/helpers/workers.d.ts +20 -19
  73. package/helpers/workers.js +620 -3
  74. package/helpers/workers.js.map +1 -1
  75. package/hmr/client/css-handler.js +60 -19
  76. package/hmr/client/css-handler.js.map +1 -1
  77. package/hmr/client/hmr-pending-overlay.d.ts +27 -0
  78. package/hmr/client/hmr-pending-overlay.js +50 -0
  79. package/hmr/client/hmr-pending-overlay.js.map +1 -0
  80. package/hmr/client/index.js +767 -24
  81. package/hmr/client/index.js.map +1 -1
  82. package/hmr/client/utils.d.ts +5 -0
  83. package/hmr/client/utils.js +283 -12
  84. package/hmr/client/utils.js.map +1 -1
  85. package/hmr/entry-runtime.d.ts +10 -0
  86. package/hmr/entry-runtime.js +330 -42
  87. package/hmr/entry-runtime.js.map +1 -1
  88. package/hmr/frameworks/angular/client/index.d.ts +3 -1
  89. package/hmr/frameworks/angular/client/index.js +821 -25
  90. package/hmr/frameworks/angular/client/index.js.map +1 -1
  91. package/hmr/frameworks/angular/server/linker.js +37 -6
  92. package/hmr/frameworks/angular/server/linker.js.map +1 -1
  93. package/hmr/frameworks/angular/server/strategy.js +30 -6
  94. package/hmr/frameworks/angular/server/strategy.js.map +1 -1
  95. package/hmr/frameworks/typescript/server/strategy.js +8 -2
  96. package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
  97. package/hmr/frameworks/vue/client/index.js +18 -42
  98. package/hmr/frameworks/vue/client/index.js.map +1 -1
  99. package/hmr/helpers/ast-normalizer.js +22 -10
  100. package/hmr/helpers/ast-normalizer.js.map +1 -1
  101. package/hmr/helpers/cjs-named-exports.d.ts +23 -0
  102. package/hmr/helpers/cjs-named-exports.js +152 -0
  103. package/hmr/helpers/cjs-named-exports.js.map +1 -0
  104. package/hmr/server/constants.d.ts +1 -0
  105. package/hmr/server/constants.js +14 -3
  106. package/hmr/server/constants.js.map +1 -1
  107. package/hmr/server/core-sanitize.d.ts +49 -2
  108. package/hmr/server/core-sanitize.js +267 -24
  109. package/hmr/server/core-sanitize.js.map +1 -1
  110. package/hmr/server/import-map.d.ts +65 -0
  111. package/hmr/server/import-map.js +222 -0
  112. package/hmr/server/import-map.js.map +1 -0
  113. package/hmr/server/index.d.ts +2 -1
  114. package/hmr/server/index.js.map +1 -1
  115. package/hmr/server/ns-core-cjs-shape.d.ts +204 -0
  116. package/hmr/server/ns-core-cjs-shape.js +271 -0
  117. package/hmr/server/ns-core-cjs-shape.js.map +1 -0
  118. package/hmr/server/perf-instrumentation.d.ts +114 -0
  119. package/hmr/server/perf-instrumentation.js +195 -0
  120. package/hmr/server/perf-instrumentation.js.map +1 -0
  121. package/hmr/server/runtime-graph-filter.d.ts +5 -0
  122. package/hmr/server/runtime-graph-filter.js +21 -0
  123. package/hmr/server/runtime-graph-filter.js.map +1 -0
  124. package/hmr/server/shared-transform-request.d.ts +12 -0
  125. package/hmr/server/shared-transform-request.js +144 -0
  126. package/hmr/server/shared-transform-request.js.map +1 -0
  127. package/hmr/server/vite-plugin.d.ts +21 -1
  128. package/hmr/server/vite-plugin.js +461 -22
  129. package/hmr/server/vite-plugin.js.map +1 -1
  130. package/hmr/server/websocket-angular-entry.d.ts +2 -0
  131. package/hmr/server/websocket-angular-entry.js +68 -0
  132. package/hmr/server/websocket-angular-entry.js.map +1 -0
  133. package/hmr/server/websocket-angular-hot-update.d.ts +78 -0
  134. package/hmr/server/websocket-angular-hot-update.js +413 -0
  135. package/hmr/server/websocket-angular-hot-update.js.map +1 -0
  136. package/hmr/server/websocket-core-bridge.d.ts +21 -0
  137. package/hmr/server/websocket-core-bridge.js +357 -0
  138. package/hmr/server/websocket-core-bridge.js.map +1 -0
  139. package/hmr/server/websocket-graph-upsert.d.ts +21 -0
  140. package/hmr/server/websocket-graph-upsert.js +33 -0
  141. package/hmr/server/websocket-graph-upsert.js.map +1 -0
  142. package/hmr/server/websocket-hmr-pending.d.ts +43 -0
  143. package/hmr/server/websocket-hmr-pending.js +55 -0
  144. package/hmr/server/websocket-hmr-pending.js.map +1 -0
  145. package/hmr/server/websocket-module-bindings.d.ts +6 -0
  146. package/hmr/server/websocket-module-bindings.js +471 -0
  147. package/hmr/server/websocket-module-bindings.js.map +1 -0
  148. package/hmr/server/websocket-module-specifiers.d.ts +101 -0
  149. package/hmr/server/websocket-module-specifiers.js +820 -0
  150. package/hmr/server/websocket-module-specifiers.js.map +1 -0
  151. package/hmr/server/websocket-ns-m-finalize.d.ts +22 -0
  152. package/hmr/server/websocket-ns-m-finalize.js +88 -0
  153. package/hmr/server/websocket-ns-m-finalize.js.map +1 -0
  154. package/hmr/server/websocket-ns-m-paths.d.ts +3 -0
  155. package/hmr/server/websocket-ns-m-paths.js +92 -0
  156. package/hmr/server/websocket-ns-m-paths.js.map +1 -0
  157. package/hmr/server/websocket-ns-m-request.d.ts +45 -0
  158. package/hmr/server/websocket-ns-m-request.js +196 -0
  159. package/hmr/server/websocket-ns-m-request.js.map +1 -0
  160. package/hmr/server/websocket-runtime-compat.d.ts +19 -0
  161. package/hmr/server/websocket-runtime-compat.js +287 -0
  162. package/hmr/server/websocket-runtime-compat.js.map +1 -0
  163. package/hmr/server/websocket-served-module-helpers.d.ts +36 -0
  164. package/hmr/server/websocket-served-module-helpers.js +631 -0
  165. package/hmr/server/websocket-served-module-helpers.js.map +1 -0
  166. package/hmr/server/websocket-txn.d.ts +6 -0
  167. package/hmr/server/websocket-txn.js +45 -0
  168. package/hmr/server/websocket-txn.js.map +1 -0
  169. package/hmr/server/websocket-vendor-unifier.d.ts +10 -0
  170. package/hmr/server/websocket-vendor-unifier.js +51 -0
  171. package/hmr/server/websocket-vendor-unifier.js.map +1 -0
  172. package/hmr/server/websocket-vue-sfc.d.ts +27 -0
  173. package/hmr/server/websocket-vue-sfc.js +1069 -0
  174. package/hmr/server/websocket-vue-sfc.js.map +1 -0
  175. package/hmr/server/websocket.d.ts +26 -3
  176. package/hmr/server/websocket.js +2492 -798
  177. package/hmr/server/websocket.js.map +1 -1
  178. package/hmr/shared/package-classifier.d.ts +9 -0
  179. package/hmr/shared/package-classifier.js +58 -0
  180. package/hmr/shared/package-classifier.js.map +1 -0
  181. package/hmr/shared/runtime/boot-timeline.d.ts +17 -0
  182. package/hmr/shared/runtime/boot-timeline.js +51 -0
  183. package/hmr/shared/runtime/boot-timeline.js.map +1 -0
  184. package/hmr/shared/runtime/browser-runtime-contract.d.ts +64 -0
  185. package/hmr/shared/runtime/browser-runtime-contract.js +54 -0
  186. package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -0
  187. package/hmr/shared/runtime/dev-overlay.d.ts +85 -0
  188. package/hmr/shared/runtime/dev-overlay.js +1236 -0
  189. package/hmr/shared/runtime/dev-overlay.js.map +1 -0
  190. package/hmr/shared/runtime/http-only-boot.d.ts +1 -0
  191. package/hmr/shared/runtime/http-only-boot.js +53 -6
  192. package/hmr/shared/runtime/http-only-boot.js.map +1 -1
  193. package/hmr/shared/runtime/module-provenance.d.ts +1 -0
  194. package/hmr/shared/runtime/module-provenance.js +63 -0
  195. package/hmr/shared/runtime/module-provenance.js.map +1 -0
  196. package/hmr/shared/runtime/platform-polyfills.d.ts +26 -0
  197. package/hmr/shared/runtime/platform-polyfills.js +122 -0
  198. package/hmr/shared/runtime/platform-polyfills.js.map +1 -0
  199. package/hmr/shared/runtime/root-placeholder.d.ts +1 -0
  200. package/hmr/shared/runtime/root-placeholder.js +552 -82
  201. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  202. package/hmr/shared/runtime/session-bootstrap.d.ts +1 -0
  203. package/hmr/shared/runtime/session-bootstrap.js +195 -0
  204. package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
  205. package/hmr/shared/runtime/vendor-bootstrap.js +52 -15
  206. package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
  207. package/hmr/shared/vendor/manifest.d.ts +37 -0
  208. package/hmr/shared/vendor/manifest.js +677 -57
  209. package/hmr/shared/vendor/manifest.js.map +1 -1
  210. package/hmr/shared/vendor/registry.js +104 -7
  211. package/hmr/shared/vendor/registry.js.map +1 -1
  212. package/index.d.ts +1 -0
  213. package/index.js +5 -0
  214. package/index.js.map +1 -1
  215. package/package.json +14 -2
  216. package/runtime/core-aliases-early.js +94 -67
  217. package/runtime/core-aliases-early.js.map +1 -1
  218. package/shims/solid-jsx-runtime.d.ts +7 -0
  219. package/shims/solid-jsx-runtime.js +17 -0
  220. 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,151 @@ 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
+ function setUpdateOverlayStage(stage, info) {
105
+ try {
106
+ const api = getHmrOverlayApi();
107
+ if (api && typeof api.setUpdateStage === 'function') {
108
+ api.setUpdateStage(stage, info);
109
+ }
110
+ }
111
+ catch { }
112
+ }
113
+ // Store the listener registry on globalThis (rather than in a module-private
114
+ // closure) because in NativeScript the HMR client module and the user app
115
+ // modules can resolve to different module instances depending on how the
116
+ // dev runtime loads them (HTTP client URL vs. the bundled vendor realm).
117
+ // A module-local Set would not be shared across instances; the global one
118
+ // is.
119
+ function getNsSolidHmrListenerSet() {
120
+ const g = globalThis;
121
+ let set = g.__ns_solid_hmr_listener_set;
122
+ if (!set) {
123
+ set = new Set();
124
+ g.__ns_solid_hmr_listener_set = set;
125
+ }
126
+ return set;
127
+ }
128
+ function nsSolidHmrSubscribe(fn) {
129
+ const listeners = getNsSolidHmrListenerSet();
130
+ listeners.add(fn);
131
+ if (VERBOSE)
132
+ console.log('[hmr][solid] subscribe — listeners=', listeners.size);
133
+ return () => listeners.delete(fn);
134
+ }
135
+ function nsSolidHmrEmit(ev) {
136
+ const listeners = getNsSolidHmrListenerSet();
137
+ if (VERBOSE)
138
+ console.log('[hmr][solid] emit listeners=', listeners.size, 'changedFiles=', ev.changedFiles);
139
+ for (const fn of Array.from(listeners)) {
140
+ try {
141
+ fn(ev);
142
+ }
143
+ catch (err) {
144
+ if (VERBOSE)
145
+ console.warn('[hmr][solid] listener threw', err);
146
+ }
147
+ }
148
+ }
149
+ try {
150
+ const g = globalThis;
151
+ g.__ns_solid_hmr_subscribe = nsSolidHmrSubscribe;
152
+ // Eagerly create the listener set so the global exists at module load time.
153
+ getNsSolidHmrListenerSet();
154
+ if (VERBOSE)
155
+ console.log('[hmr][solid] HMR client loaded. global set=', typeof g.__ns_solid_hmr_subscribe, 'listenerSet=', typeof g.__ns_solid_hmr_listener_set);
156
+ }
157
+ catch (err) {
158
+ console.warn('[hmr][solid] could not install global __ns_solid_hmr_subscribe', err);
159
+ }
160
+ // Eagerly drive the HMR-applying overlay's 'received' frame as soon
161
+ // as the server emits `ns:hmr-pending`, BEFORE the framework-specific
162
+ // (`ns:angular-update` / `ns:css-updates`) payload arrives. The
163
+ // flavor-specific handler later walks through 'evicting' →
164
+ // 'reimporting' → 'rebooting' → 'complete'. Calling 'received' twice
165
+ // in the same cycle is safe: the overlay preserves
166
+ // `updateCycleStartedAt` when a 'received' frame replaces an existing
167
+ // 'received' frame so the minimum-visible window is still timed
168
+ // against the FIRST frame.
169
+ //
170
+ // Soft-fails when the overlay isn't installed (production builds,
171
+ // vitest, etc.) or when the user opted out via
172
+ // `__NS_HMR_PROGRESS_OVERLAY_ENABLED__ === false`.
173
+ import { applyHmrPendingFrame } from './hmr-pending-overlay.js';
174
+ function setHmrPendingOverlay(filePath) {
175
+ applyHmrPendingFrame(filePath, { getOverlay: getHmrOverlayApi });
176
+ }
177
+ let connectionOverlayTimer = null;
178
+ let connectionOverlayVisible = false;
179
+ let hasOpenedHmrSocket = false;
180
+ let awaitingHealthyHmrMessage = false;
181
+ let pendingConnectionOverlayStage = 'connecting';
182
+ let pendingConnectionOverlayDetail = '';
183
+ function clearConnectionOverlayTimer() {
184
+ if (connectionOverlayTimer) {
185
+ clearTimeout(connectionOverlayTimer);
186
+ connectionOverlayTimer = null;
187
+ }
188
+ }
189
+ function showConnectionOverlayNow(stage, detail) {
190
+ pendingConnectionOverlayStage = stage;
191
+ pendingConnectionOverlayDetail = detail || '';
192
+ connectionOverlayVisible = true;
193
+ setConnectionOverlayStage(stage, detail);
194
+ }
195
+ function scheduleConnectionOverlay(stage, detail, delayMs = 1200) {
196
+ pendingConnectionOverlayStage = stage;
197
+ pendingConnectionOverlayDetail = detail || '';
198
+ clearConnectionOverlayTimer();
199
+ connectionOverlayTimer = setTimeout(() => {
200
+ showConnectionOverlayNow(pendingConnectionOverlayStage, pendingConnectionOverlayDetail);
201
+ }, delayMs);
202
+ }
203
+ function updateConnectionOverlay(stage, detail) {
204
+ pendingConnectionOverlayStage = stage;
205
+ pendingConnectionOverlayDetail = detail || '';
206
+ if (connectionOverlayVisible) {
207
+ showConnectionOverlayNow(stage, detail);
208
+ }
209
+ }
210
+ function markHmrConnectionHealthy() {
211
+ awaitingHealthyHmrMessage = false;
212
+ clearConnectionOverlayTimer();
213
+ if (connectionOverlayVisible) {
214
+ connectionOverlayVisible = false;
215
+ hideConnectionOverlay();
216
+ }
217
+ }
46
218
  /**
47
219
  * Flavor hooks
48
220
  */
49
- import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate } from '../frameworks/vue/client/index.js';
221
+ import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate, sfcArtifactMap } from '../frameworks/vue/client/index.js';
50
222
  import { handleAngularHotUpdateMessage, installAngularHmrClientHooks } from '../frameworks/angular/client/index.js';
51
- switch (__NS_TARGET_FLAVOR__) {
223
+ switch (TARGET_FLAVOR) {
52
224
  case 'vue':
53
225
  installNsVueDevShims();
54
226
  break;
@@ -60,6 +232,10 @@ switch (__NS_TARGET_FLAVOR__) {
60
232
  let initialMounted = !!globalThis.__NS_HMR_BOOT_COMPLETE__;
61
233
  // Prevent duplicate initial-mount scheduling across rapid full-graph broadcasts and re-evaluations
62
234
  let initialMounting = !!globalThis.__NS_HMR_INITIAL_MOUNT_IN_PROGRESS__;
235
+ // Track whether the first full-graph has been received. Before the full-graph,
236
+ // delta messages are just the server discovering modules during initial boot —
237
+ // NOT actual code changes. Re-imports must be gated behind this flag.
238
+ let hasReceivedFullGraph = false;
63
239
  // TypeScript flavor: track registry modules and inferred main id
64
240
  let tsModuleSet = null;
65
241
  let tsMainId = null;
@@ -94,10 +270,14 @@ function applyFullGraph(payload) {
94
270
  console.log('[hmr][graph] full graph applied version', getGraphVersion(), 'modules=', graph.size);
95
271
  // Guarded initial mount rescue: if app hasn't replaced the placeholder shortly after graph arrives,
96
272
  // perform a one-time mount. This waits briefly to let the app's main entry start() run first to avoid double mounts.
273
+ // TypeScript flavor: skip the rescue entirely. The HTTP boot will load main.ts which
274
+ // calls Application.run() (patched to resetRootView) — the rescue is redundant and
275
+ // causes a double-mount race (rescue fires at 450ms, then main.ts fires ~1s later,
276
+ // causing a visual flash and leaving the app in an inconsistent state).
97
277
  try {
98
278
  const g = globalThis;
99
279
  const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
100
- if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__) {
280
+ if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && TARGET_FLAVOR !== 'typescript') {
101
281
  // simple snapshot helpers
102
282
  const getTopmost = () => {
103
283
  try {
@@ -170,7 +350,7 @@ function applyFullGraph(payload) {
170
350
  if (VERBOSE)
171
351
  console.log('[hmr][init] placeholder persists after delay; evaluating rescue policy');
172
352
  // Flavor-specific rescue handling
173
- if (__NS_TARGET_FLAVOR__ === 'typescript') {
353
+ if (TARGET_FLAVOR === 'typescript') {
174
354
  // For TS apps, perform a one-time resetRootView to the conventional
175
355
  // app root module. This mimics what Application.run would do and
176
356
  // replaces the placeholder with the real UI without trying to
@@ -211,7 +391,7 @@ function applyFullGraph(payload) {
211
391
  return;
212
392
  }
213
393
  let candidate = null;
214
- switch (__NS_TARGET_FLAVOR__) {
394
+ switch (TARGET_FLAVOR) {
215
395
  case 'vue': {
216
396
  const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
217
397
  if (appEntry && Array.isArray(appEntry.deps)) {
@@ -227,6 +407,19 @@ function applyFullGraph(payload) {
227
407
  }
228
408
  }
229
409
  }
410
+ // Fallback: when the module graph is empty (Vite 7+ may not populate it
411
+ // before the first full-graph broadcast), check the SFC artifact registry
412
+ // which is populated from the ns:vue-sfc-registry message.
413
+ if (!candidate && sfcArtifactMap.size > 0) {
414
+ for (const id of sfcArtifactMap.keys()) {
415
+ if (/\.vue$/i.test(id)) {
416
+ candidate = id;
417
+ if (VERBOSE)
418
+ console.log('[hmr][init] rescue candidate from SFC registry:', id);
419
+ break;
420
+ }
421
+ }
422
+ }
230
423
  break;
231
424
  }
232
425
  }
@@ -240,7 +433,7 @@ function applyFullGraph(payload) {
240
433
  (async () => {
241
434
  try {
242
435
  let comp = null;
243
- switch (__NS_TARGET_FLAVOR__) {
436
+ switch (TARGET_FLAVOR) {
244
437
  case 'vue':
245
438
  comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
246
439
  break;
@@ -285,7 +478,7 @@ function applyFullGraph(payload) {
285
478
  // to avoid double-mount races that can cause duplicate navigation logs.
286
479
  if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
287
480
  let candidate = null;
288
- switch (__NS_TARGET_FLAVOR__) {
481
+ switch (TARGET_FLAVOR) {
289
482
  case 'vue': {
290
483
  const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
291
484
  if (appEntry && Array.isArray(appEntry.deps)) {
@@ -301,6 +494,17 @@ function applyFullGraph(payload) {
301
494
  }
302
495
  }
303
496
  }
497
+ // Fallback: SFC registry (same as rescue mount above)
498
+ if (!candidate && sfcArtifactMap.size > 0) {
499
+ for (const id of sfcArtifactMap.keys()) {
500
+ if (/\.vue$/i.test(id)) {
501
+ candidate = id;
502
+ if (VERBOSE)
503
+ console.log('[hmr][init] initial mount candidate from SFC registry:', id);
504
+ break;
505
+ }
506
+ }
507
+ }
304
508
  break;
305
509
  }
306
510
  case 'typescript': {
@@ -318,7 +522,7 @@ function applyFullGraph(payload) {
318
522
  (async () => {
319
523
  try {
320
524
  if (VERBOSE)
321
- console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', __NS_TARGET_FLAVOR__);
525
+ console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
322
526
  // Android-only: avoid racing entry-runtime reset and Activity bring-up
323
527
  try {
324
528
  const g = globalThis;
@@ -352,7 +556,7 @@ function applyFullGraph(payload) {
352
556
  }
353
557
  catch { }
354
558
  let comp = null;
355
- switch (__NS_TARGET_FLAVOR__) {
559
+ switch (TARGET_FLAVOR) {
356
560
  case 'vue':
357
561
  comp = await loadSfcComponent(candidate, 'initial_mount');
358
562
  break;
@@ -401,7 +605,7 @@ function applyFullGraph(payload) {
401
605
  })();
402
606
  }
403
607
  else if (VERBOSE) {
404
- console.warn('[hmr][init] no component found in graph to mount initially for flavor', __NS_TARGET_FLAVOR__);
608
+ console.warn('[hmr][init] no component found in graph to mount initially for flavor', TARGET_FLAVOR);
405
609
  }
406
610
  }
407
611
  }
@@ -427,7 +631,7 @@ function applyDelta(payload) {
427
631
  setGraphVersion(payload.newVersion);
428
632
  }
429
633
  const changed = payload.changed || [];
430
- switch (__NS_TARGET_FLAVOR__) {
634
+ switch (TARGET_FLAVOR) {
431
635
  case 'vue':
432
636
  recordVuePayloadChanges(changed, getGraphVersion());
433
637
  break;
@@ -445,6 +649,16 @@ function applyDelta(payload) {
445
649
  console.log('[hmr][graph] delta applied newVersion', getGraphVersion(), 'changed=', (payload.changed || []).length, 'removed=', (payload.removed || []).length, 'baseVersion=', payload.baseVersion);
446
650
  // Queue evaluation of changed modules (placeholder pipeline)
447
651
  if (payload.changed?.length) {
652
+ // Gate: Before the first full-graph is received, delta messages are the server
653
+ // discovering modules during initial boot — NOT actual code changes. The entry-runtime
654
+ // already loaded all modules; re-importing them would cause duplicate Application.run(),
655
+ // router initialization, and modal conflicts. Only queue re-imports after the first
656
+ // full-graph confirms the graph is synced and subsequent deltas are real changes.
657
+ if (!hasReceivedFullGraph) {
658
+ if (VERBOSE)
659
+ console.log('[hmr][delta] skipping re-import queue (initial graph build, no full-graph yet)');
660
+ return;
661
+ }
448
662
  // HARD SUPPRESS: the very first delta (baseVersion 0) commonly includes the app main entry which is already evaluated during bootstrap.
449
663
  // Importing it again with a cache-bust often produces a spurious module-not-found (timestamp param treated as distinct file).
450
664
  const isInitial = payload.baseVersion === 0;
@@ -507,7 +721,7 @@ function applyDelta(payload) {
507
721
  // Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
508
722
  function __nsNavigateUsingApp(comp, opts = {}) {
509
723
  const g = globalThis;
510
- switch (__NS_TARGET_FLAVOR__) {
724
+ switch (TARGET_FLAVOR) {
511
725
  case 'vue':
512
726
  ensureVueGlobals();
513
727
  break;
@@ -535,7 +749,7 @@ function __nsNavigateUsingApp(comp, opts = {}) {
535
749
  const existingApp = getCurrentApp();
536
750
  const baseProvides = (existingApp && existingApp._context && existingApp._context.provides) || {};
537
751
  const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)));
538
- switch (__NS_TARGET_FLAVOR__) {
752
+ switch (TARGET_FLAVOR) {
539
753
  case 'vue':
540
754
  ensurePiniaOnApp(app);
541
755
  break;
@@ -669,7 +883,42 @@ async function processQueue() {
669
883
  return;
670
884
  if (VERBOSE)
671
885
  console.log('[hmr][queue] processing changed ids', drained);
886
+ // Track wall-clock so the 'complete' frame can show a meaningful
887
+ // total. Only the Solid + TypeScript flavors drive the overlay
888
+ // from here; Angular has its own flow inside
889
+ // `frameworks/angular/client/index.ts`.
890
+ const tQueueStart = Date.now();
891
+ const driveSolidOverlay = TARGET_FLAVOR === 'solid';
892
+ // Explicit eviction step.
893
+ //
894
+ // On modern runtimes the URL canonicalizer collapses any
895
+ // `__ns_hmr__/<tag>/` segment back to a stable cache key, so
896
+ // without explicit eviction the upcoming `import(url)` would
897
+ // resolve via V8's `g_moduleRegistry` and return the cached
898
+ // stale module — making the queue drain a silent no-op for
899
+ // every save after the first.
900
+ //
901
+ // We hand the canonical eviction URLs to the runtime first;
902
+ // `invalidateModulesByUrls` is a no-op on older runtimes and
903
+ // `requestModuleFromServer` automatically falls back to the
904
+ // legacy `/ns/m/__ns_hmr__/v<N>/` URL versioning path in that
905
+ // case. node_modules and virtual specs are filtered out by
906
+ // `buildEvictionUrls` so vendor modules stay hot.
907
+ if (driveSolidOverlay) {
908
+ setUpdateOverlayStage('evicting', {
909
+ detail: drained.length === 1 ? `Invalidating ${drained[0]}` : `Invalidating ${drained.length} modules`,
910
+ });
911
+ }
912
+ const evictUrls = buildEvictionUrls(drained);
913
+ const evicted = invalidateModulesByUrls(evictUrls);
914
+ if (VERBOSE)
915
+ console.log(`[hmr][queue] eviction count=${evictUrls.length} ok=${evicted}`);
672
916
  // Evaluate changed modules best-effort; failures shouldn't completely break HMR.
917
+ if (driveSolidOverlay) {
918
+ setUpdateOverlayStage('reimporting', {
919
+ detail: drained.length === 1 ? `Re-importing ${drained[0]}` : `Re-importing ${drained.length} modules`,
920
+ });
921
+ }
673
922
  for (const id of drained) {
674
923
  try {
675
924
  const spec = normalizeSpec(id);
@@ -686,10 +935,274 @@ async function processQueue() {
686
935
  }
687
936
  }
688
937
  // After evaluating the batch, perform flavor-specific UI refresh.
689
- switch (__NS_TARGET_FLAVOR__) {
938
+ switch (TARGET_FLAVOR) {
690
939
  case 'vue':
691
940
  // Vue SFCs are handled via the registry update path; nothing to do here.
692
941
  break;
942
+ case 'solid': {
943
+ // Boundaries discovered in this HMR cycle (tsx files reachable
944
+ // via the reverse import graph from any changed file, plus route
945
+ // files reachable from any tsx start point). Declared at the top
946
+ // of the case block so the emit step below can include the
947
+ // complete set in the listener event — framework integrations
948
+ // use it to map route boundaries → fresh component references.
949
+ const boundaries = new Set();
950
+ // Solid .tsx components are self-accepting via solid-refresh's inline
951
+ // patchRegistry — re-importing them is sufficient. For non-component
952
+ // .ts utility modules, we must propagate up the import graph to find
953
+ // the .tsx/.jsx component boundaries and re-import those so their
954
+ // solid-refresh proxies pick up the new dependency values.
955
+ try {
956
+ // Build reverse index: dep id → list of importer ids
957
+ const reverseIndex = new Map();
958
+ for (const [id, mod] of graph) {
959
+ for (const dep of mod.deps) {
960
+ let arr = reverseIndex.get(dep);
961
+ if (!arr) {
962
+ arr = [];
963
+ reverseIndex.set(dep, arr);
964
+ }
965
+ arr.push(id);
966
+ }
967
+ }
968
+ // Pass 1: BFS from each non-tsx changed module up to tsx/jsx
969
+ // boundaries. These get re-imported below so solid-refresh's
970
+ // inline patchRegistry runs and (best-effort) swaps the proxy
971
+ // signals for any components defined in those tsx boundaries.
972
+ for (const id of drained) {
973
+ if (/\.(tsx|jsx)$/i.test(id))
974
+ continue; // already self-accepting
975
+ const visited = new Set();
976
+ const queue = [id];
977
+ while (queue.length) {
978
+ const cur = queue.shift();
979
+ if (visited.has(cur))
980
+ continue;
981
+ visited.add(cur);
982
+ const importers = reverseIndex.get(cur);
983
+ if (!importers)
984
+ continue;
985
+ for (const imp of importers) {
986
+ if (/\.(tsx|jsx)$/i.test(imp)) {
987
+ boundaries.add(imp);
988
+ }
989
+ else {
990
+ queue.push(imp);
991
+ }
992
+ }
993
+ }
994
+ }
995
+ // Pass 2: walk further from any tsx starting point (a tsx file
996
+ // in `drained` OR a tsx boundary discovered in pass 1) to find
997
+ // route files (`/src/routes/*.{tsx,jsx}`) that transitively
998
+ // import them. Re-importing a route file refreshes its
999
+ // `Route.options.component` to the freshly-imported reference
1000
+ // and the existing boundary loop below patches the live router
1001
+ // with that fresh reference.
1002
+ //
1003
+ // This is the key fix for "edit home.tsx → save → no visual
1004
+ // update": the old BFS skipped tsx files in `drained` (assuming
1005
+ // solid-refresh's in-place proxy patch was sufficient), but in
1006
+ // the universal-renderer + nested-context configuration that
1007
+ // patch does not always propagate to the visible page tree.
1008
+ // Adding the route file as a boundary lets us patch
1009
+ // `route.options.component` directly to a fresh module export,
1010
+ // which the framework subscriber then passes through to the
1011
+ // page remount — making the cycle robust to the proxy patch
1012
+ // silently failing.
1013
+ const tsxStarts = new Set();
1014
+ for (const id of drained) {
1015
+ if (/\.(tsx|jsx)$/i.test(id))
1016
+ tsxStarts.add(id);
1017
+ }
1018
+ for (const b of boundaries)
1019
+ tsxStarts.add(b);
1020
+ const ROUTE_FILE_RE = /\/src\/routes\/.+\.(tsx|jsx)$/i;
1021
+ for (const start of tsxStarts) {
1022
+ const visited = new Set();
1023
+ const queue = [start];
1024
+ while (queue.length) {
1025
+ const cur = queue.shift();
1026
+ if (visited.has(cur))
1027
+ continue;
1028
+ visited.add(cur);
1029
+ if (cur !== start && ROUTE_FILE_RE.test(cur)) {
1030
+ boundaries.add(cur);
1031
+ }
1032
+ const importers = reverseIndex.get(cur);
1033
+ if (!importers)
1034
+ continue;
1035
+ for (const imp of importers) {
1036
+ queue.push(imp);
1037
+ }
1038
+ }
1039
+ }
1040
+ // Re-import each boundary so solid-refresh patchRegistry fires.
1041
+ // For route files (TanStack Router), capture the new Route export
1042
+ // and patch the router's existing route with the fresh loader.
1043
+ let routesPatchCount = 0;
1044
+ let discoveredRouter = null;
1045
+ // Discover router: try __ns_router global (set by createNativeScriptRouter),
1046
+ // then scan globalThis for any router-shaped object with routesById.
1047
+ const findRouter = () => {
1048
+ if (discoveredRouter)
1049
+ return discoveredRouter;
1050
+ const g = globalThis;
1051
+ if (g.__ns_router?.routesById)
1052
+ return (discoveredRouter = g.__ns_router);
1053
+ // Fallback: scan common global keys for router
1054
+ for (const key of ['__ns_router', 'router', '__router']) {
1055
+ if (g[key]?.routesById && g[key]?.invalidate)
1056
+ return (discoveredRouter = g[key]);
1057
+ }
1058
+ return null;
1059
+ };
1060
+ // Convert boundary file path to TanStack Router fullPath.
1061
+ // e.g. /src/routes/posts.$postId.tsx → /posts/$postId
1062
+ const boundaryToFullPath = (bid) => {
1063
+ const m = bid.match(/\/src\/routes\/(.+)\.(tsx|jsx|ts|js)$/i);
1064
+ if (!m)
1065
+ return null;
1066
+ let p = m[1];
1067
+ // Replace dots between segments with slashes (posts.$postId → posts/$postId)
1068
+ p = p.replace(/\./g, '/');
1069
+ // Handle index files
1070
+ if (p === 'index')
1071
+ return '/';
1072
+ if (p.endsWith('/index'))
1073
+ p = p.slice(0, -6);
1074
+ // Strip leading - (TanStack pathless layout convention)
1075
+ p = p.replace(/(^|\/)-([\w])/g, '$1$2');
1076
+ return '/' + p;
1077
+ };
1078
+ // Find existing route by fullPath (since new Route has no id yet)
1079
+ const findRouteByFullPath = (router, fp) => {
1080
+ if (!router?.routesById)
1081
+ return null;
1082
+ for (const rid of Object.keys(router.routesById)) {
1083
+ const r = router.routesById[rid];
1084
+ if (r?.fullPath === fp)
1085
+ return r;
1086
+ }
1087
+ return null;
1088
+ };
1089
+ // Evict the boundary set so re-importing each .tsx
1090
+ // component actually picks up the new transitive
1091
+ // dependency code; without this V8 returns the
1092
+ // cached boundary module unchanged.
1093
+ const boundaryIds = Array.from(boundaries);
1094
+ const solidEvictUrls = buildEvictionUrls(boundaryIds);
1095
+ const solidEvicted = invalidateModulesByUrls(solidEvictUrls);
1096
+ if (VERBOSE)
1097
+ console.log(`[hmr][solid] eviction count=${solidEvictUrls.length} ok=${solidEvicted}`);
1098
+ for (const id of boundaries) {
1099
+ if (seen.has(id))
1100
+ continue;
1101
+ try {
1102
+ const spec = normalizeSpec(id);
1103
+ const url = await requestModuleFromServer(spec);
1104
+ if (!url)
1105
+ continue;
1106
+ if (VERBOSE)
1107
+ console.log('[hmr][solid] propagated to boundary', { id, url });
1108
+ const mod = await import(/* @vite-ignore */ url);
1109
+ // Patch TanStack Router route options for any module
1110
+ // that exports a `Route`. We patch BOTH the component
1111
+ // and the loader (when present); components-only routes
1112
+ // were previously skipped because the gate required a
1113
+ // loader, which left their `options.component` pointing
1114
+ // at the stale module's exports after HMR.
1115
+ try {
1116
+ const newRoute = mod?.Route;
1117
+ if (newRoute?.options) {
1118
+ const router = findRouter();
1119
+ const fullPath = boundaryToFullPath(id);
1120
+ if (VERBOSE)
1121
+ console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, hasLoader: !!newRoute.options.loader, hasComponent: !!newRoute.options.component });
1122
+ const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
1123
+ if (existingRoute?.options) {
1124
+ if (newRoute.options.loader)
1125
+ existingRoute.options.loader = newRoute.options.loader;
1126
+ if (newRoute.options.component)
1127
+ existingRoute.options.component = newRoute.options.component;
1128
+ routesPatchCount++;
1129
+ if (VERBOSE)
1130
+ console.log('[hmr][solid] patched route', existingRoute.id, 'fullPath=', fullPath);
1131
+ }
1132
+ else if (VERBOSE) {
1133
+ console.log('[hmr][solid] no matching route for fullPath', fullPath);
1134
+ }
1135
+ }
1136
+ }
1137
+ catch (e) {
1138
+ if (VERBOSE)
1139
+ console.warn('[hmr][solid] route patch error', id, e);
1140
+ }
1141
+ }
1142
+ catch (e) {
1143
+ if (VERBOSE)
1144
+ console.warn('[hmr][solid] boundary re-import failed', id, e);
1145
+ }
1146
+ }
1147
+ // Route loaders were patched with fresh closures. The data is
1148
+ // correct in router.state.matches[].loaderData (confirmed via
1149
+ // diagnostics), but the currently visible page doesn't re-render
1150
+ // because the NativeScriptRouterProvider's Solid store flush only
1151
+ // fires through history.subscribe. TODO: find the right mechanism
1152
+ // to trigger a Solid reactive update for the active match stores.
1153
+ if (routesPatchCount > 0 && VERBOSE) {
1154
+ console.log('[hmr][solid] patched', routesPatchCount, 'route loaders (data correct in match state, pending UI refresh mechanism)');
1155
+ }
1156
+ if (VERBOSE) {
1157
+ if (boundaries.size)
1158
+ console.log('[hmr][solid] propagated non-component change to', boundaries.size, 'boundaries', Array.from(boundaries));
1159
+ console.log('[hmr][queue] Solid: modules re-imported, solid-refresh handles reactive update', drained);
1160
+ }
1161
+ }
1162
+ catch (e) {
1163
+ if (VERBOSE)
1164
+ console.warn('[hmr][solid] propagation failed', e);
1165
+ }
1166
+ // Notify any framework integrations (e.g.
1167
+ // `@nativescript/tanstack-router`) that a Solid HMR
1168
+ // cycle has completed. They use this signal to perform
1169
+ // framework-specific UI refresh (e.g. remount the active
1170
+ // router page) when solid-refresh's own reactive
1171
+ // propagation does not reach the visible tree under
1172
+ // the current renderer/context configuration.
1173
+ //
1174
+ // Boundaries include both the directly-changed tsx files
1175
+ // AND every tsx ancestor reachable via the reverse import
1176
+ // graph (route files in particular). The framework
1177
+ // listener uses the route-file boundaries to look up the
1178
+ // freshly-patched `route.options.component` and pass it
1179
+ // through to the page remount.
1180
+ try {
1181
+ const tsxChangedInDrained = drained.filter((id) => /\.(tsx|jsx)$/i.test(id));
1182
+ const allBoundaries = Array.from(new Set([...tsxChangedInDrained, ...boundaries]));
1183
+ nsSolidHmrEmit({
1184
+ kind: 'solid',
1185
+ changedFiles: drained.slice(),
1186
+ boundaries: allBoundaries,
1187
+ });
1188
+ }
1189
+ catch (err) {
1190
+ if (VERBOSE)
1191
+ console.warn('[hmr][solid] emit failed', err);
1192
+ }
1193
+ // Tell the overlay the cycle is done. solid-refresh's
1194
+ // inline patchRegistry has already flushed the new
1195
+ // component bodies into the live tree (the `case
1196
+ // 'solid'` block above re-imports each .tsx
1197
+ // boundary), so by the time we get here the user is
1198
+ // already looking at the new render. The 'complete'
1199
+ // frame surfaces the wall-clock total and triggers
1200
+ // the overlay's auto-hide.
1201
+ setUpdateOverlayStage('complete', {
1202
+ detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
1203
+ });
1204
+ break;
1205
+ }
693
1206
  case 'typescript': {
694
1207
  // For TS apps, always reset back to the conventional app root.
695
1208
  // This preserves the shell (Frame, ActionBar, etc.) that the app's
@@ -702,9 +1215,124 @@ async function processQueue() {
702
1215
  console.warn('[hmr][queue] TS flavor: Application.resetRootView unavailable; skipping UI refresh');
703
1216
  break;
704
1217
  }
1218
+ // Re-fetch changed XML/CSS files and update the bundled module registry
1219
+ // so Builder.createViewFromEntry picks up fresh content.
1220
+ const rawAssetIds = drained.filter((id) => /\.(xml|css|scss|sass|less)$/i.test(id));
1221
+ if (rawAssetIds.length && typeof g.registerModule === 'function') {
1222
+ const origin = getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
1223
+ if (origin) {
1224
+ for (const id of rawAssetIds) {
1225
+ try {
1226
+ const spec = normalizeSpec(id);
1227
+ // Fetch the raw file content directly from Vite's dev server.
1228
+ // Use the project-relative path which Vite serves as static files.
1229
+ const fetchUrl = origin + (spec.startsWith('/') ? spec : '/' + spec);
1230
+ if (VERBOSE)
1231
+ console.log('[hmr][queue] fetching raw asset', { id, fetchUrl });
1232
+ const resp = await fetch(fetchUrl);
1233
+ if (resp.ok) {
1234
+ const rawContent = await resp.text();
1235
+ // Register under all nickname variants the module registry uses.
1236
+ // The bundler context registers XML as e.g., './main-page.xml' and 'main-page.xml'
1237
+ const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1238
+ let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1239
+ if (relPath.startsWith(appVirtual))
1240
+ relPath = relPath.slice(appVirtual.length);
1241
+ const nicknames = ['./' + relPath, relPath];
1242
+ // Also add without extension for CSS
1243
+ const extIdx = relPath.lastIndexOf('.');
1244
+ if (extIdx > 0) {
1245
+ const baseName = relPath.slice(0, extIdx);
1246
+ if (!relPath.endsWith('.xml'))
1247
+ nicknames.push(baseName, './' + baseName);
1248
+ }
1249
+ for (const name of nicknames) {
1250
+ if (VERBOSE)
1251
+ console.log('[hmr][queue] re-registering module', name);
1252
+ g.registerModule(name, () => rawContent);
1253
+ }
1254
+ }
1255
+ else if (VERBOSE) {
1256
+ console.warn('[hmr][queue] raw asset fetch failed', id, resp.status);
1257
+ }
1258
+ }
1259
+ catch (e) {
1260
+ if (VERBOSE)
1261
+ console.warn('[hmr][queue] raw asset refresh failed for', id, e);
1262
+ }
1263
+ }
1264
+ }
1265
+ }
1266
+ // Determine if we can navigate in-place to a changed page
1267
+ // instead of resetting all the way back to app-root.
1268
+ // This keeps the user on the page they're editing for faster iteration.
1269
+ const changedXmlPages = drained
1270
+ .filter((id) => /\.xml$/i.test(id))
1271
+ .map((id) => {
1272
+ const spec = normalizeSpec(id);
1273
+ const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1274
+ let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1275
+ if (relPath.startsWith(appVirtual))
1276
+ relPath = relPath.slice(appVirtual.length);
1277
+ // Strip .xml extension to get the moduleName (e.g., 'pages/status-bar')
1278
+ return relPath.replace(/\.xml$/i, '');
1279
+ })
1280
+ .filter((m) => m && m !== 'app-root');
1281
+ // Resolve the topmost Frame from the bundled realm.
1282
+ // Frame.topmost() relies on an internal frameStack array, so we must
1283
+ // call it on the bundled-realm class. Multiple strategies to find it:
1284
+ const FrameClass = getCore('Frame') || g.Frame;
1285
+ let topFrame = null;
1286
+ // 1) Try the vendor-realm static topmost()
1287
+ try {
1288
+ topFrame = FrameClass?.topmost?.();
1289
+ }
1290
+ catch { }
1291
+ // 2) Try getting the root view from Application — if it's a Frame, use it
1292
+ if (!topFrame) {
1293
+ try {
1294
+ const rootView = App.getRootView?.() || App._rootView;
1295
+ if (rootView) {
1296
+ // rootView could be a Frame itself, or contain a Frame
1297
+ const isFrame = rootView.constructor?.name === 'Frame' || rootView.navigate;
1298
+ if (isFrame) {
1299
+ topFrame = rootView;
1300
+ }
1301
+ else if (rootView.getChildAt) {
1302
+ // Walk direct children looking for a Frame
1303
+ for (let i = 0; i < (rootView.getChildrenCount?.() || 0); i++) {
1304
+ const child = rootView.getChildAt(i);
1305
+ if (child?.constructor?.name === 'Frame' || child?.navigate) {
1306
+ topFrame = child;
1307
+ break;
1308
+ }
1309
+ }
1310
+ }
1311
+ }
1312
+ }
1313
+ catch { }
1314
+ }
705
1315
  if (VERBOSE)
706
- console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
707
- App.resetRootView({ moduleName: 'app-root' });
1316
+ console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame);
1317
+ if (changedXmlPages.length > 0 && topFrame) {
1318
+ // Navigate the current frame to the changed page directly.
1319
+ // Use the last changed XML page (most specific).
1320
+ const moduleName = changedXmlPages[changedXmlPages.length - 1];
1321
+ if (VERBOSE)
1322
+ console.log('[hmr][queue] TS: navigating in-place to', moduleName);
1323
+ try {
1324
+ topFrame.navigate({ moduleName, clearHistory: false, animated: false });
1325
+ }
1326
+ catch (navErr) {
1327
+ console.warn('[hmr][queue] TS flavor: in-place navigate failed, falling back to resetRootView', navErr);
1328
+ App.resetRootView({ moduleName: 'app-root' });
1329
+ }
1330
+ }
1331
+ else {
1332
+ if (VERBOSE)
1333
+ console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
1334
+ App.resetRootView({ moduleName: 'app-root' });
1335
+ }
708
1336
  }
709
1337
  catch (e) {
710
1338
  console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
@@ -752,6 +1380,11 @@ function connectHmr() {
752
1380
  console.log('[hmr-client] Already connecting to HMR WebSocket, skipping');
753
1381
  return;
754
1382
  }
1383
+ try {
1384
+ globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
1385
+ }
1386
+ catch { }
1387
+ const overlayStage = hasOpenedHmrSocket ? 'reconnecting' : 'connecting';
755
1388
  const baseUrl = getHMRWsUrl() || 'ws://localhost:5173/ns-hmr';
756
1389
  const buildCandidates = (url) => {
757
1390
  let candidates = [];
@@ -795,7 +1428,7 @@ function connectHmr() {
795
1428
  if (seen.has(key))
796
1429
  continue;
797
1430
  seen.add(key);
798
- const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}`;
1431
+ const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}${u.search || ''}`;
799
1432
  candidates.push(cand);
800
1433
  }
801
1434
  }
@@ -811,10 +1444,19 @@ function connectHmr() {
811
1444
  let idx = 0;
812
1445
  const tryNext = () => {
813
1446
  if (idx >= candidates.length) {
1447
+ showConnectionOverlayNow('offline', 'Waiting for the Vite websocket to come back.');
814
1448
  console.warn('[hmr-client] All WS candidates failed:', candidates.join(', '));
1449
+ setTimeout(connectHmr, 1500);
815
1450
  return;
816
1451
  }
817
1452
  const url = candidates[idx++];
1453
+ const connectionDetail = `${overlayStage === 'reconnecting' ? 'Retrying' : 'Opening'} ${url}`;
1454
+ if (connectionOverlayVisible) {
1455
+ updateConnectionOverlay(overlayStage, connectionDetail);
1456
+ }
1457
+ else {
1458
+ scheduleConnectionOverlay(overlayStage, connectionDetail);
1459
+ }
818
1460
  try {
819
1461
  if (__NS_ENV_VERBOSE__)
820
1462
  console.log('[hmr-client] Connecting to HMR WebSocket:', url);
@@ -836,7 +1478,25 @@ function connectHmr() {
836
1478
  sock.onopen = () => {
837
1479
  opened = true;
838
1480
  clearTimeout(timeout);
1481
+ clearConnectionOverlayTimer();
1482
+ hasOpenedHmrSocket = true;
1483
+ awaitingHealthyHmrMessage = true;
1484
+ try {
1485
+ globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = true;
1486
+ }
1487
+ catch { }
1488
+ if (connectionOverlayVisible) {
1489
+ showConnectionOverlayNow('synchronizing', 'Connected. Synchronizing the HMR graph.');
1490
+ }
839
1491
  VERBOSE && console.log('[hmr-client] Connected to HMR WebSocket');
1492
+ // Print the active module reload mode once on first
1493
+ // successful connect so the user can correlate HMR latency
1494
+ // with runtime capability without grepping for protocol
1495
+ // details. The banner is verbose-gated.
1496
+ try {
1497
+ emitHmrModeBannerOnce();
1498
+ }
1499
+ catch { }
840
1500
  };
841
1501
  sock.onmessage = handleHmrMessage;
842
1502
  sock.onerror = (error) => {
@@ -845,6 +1505,10 @@ function connectHmr() {
845
1505
  };
846
1506
  sock.onclose = (ev) => {
847
1507
  clearTimeout(timeout);
1508
+ try {
1509
+ globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
1510
+ }
1511
+ catch { }
848
1512
  if (!opened) {
849
1513
  // immediate failure during connect → try another candidate
850
1514
  if (VERBOSE)
@@ -854,6 +1518,7 @@ function connectHmr() {
854
1518
  else {
855
1519
  if (VERBOSE)
856
1520
  console.log('[hmr-client] WebSocket closed (code', ev?.code, '), will reconnect…');
1521
+ scheduleConnectionOverlay('reconnecting', 'The websocket closed. Waiting to reconnect.', 700);
857
1522
  // try to reconnect with full candidate list again
858
1523
  setTimeout(connectHmr, 1000);
859
1524
  }
@@ -874,6 +1539,12 @@ async function handleHmrMessage(ev) {
874
1539
  catch {
875
1540
  return;
876
1541
  }
1542
+ if (awaitingHealthyHmrMessage && msg) {
1543
+ markHmrConnectionHealthy();
1544
+ }
1545
+ if (VERBOSE && msg?.type) {
1546
+ console.log('[hmr-client] received message', msg.type);
1547
+ }
877
1548
  // Notify optional app-level hook after an HMR batch is applied.
878
1549
  function notifyAppHmrUpdate(kind, changedIds) {
879
1550
  try {
@@ -885,6 +1556,17 @@ async function handleHmrMessage(ev) {
885
1556
  catch { }
886
1557
  }
887
1558
  if (msg) {
1559
+ // `ns:hmr-pending` is a fire-and-forget UX hint emitted by the
1560
+ // server at the START of handleHotUpdate. We drive the
1561
+ // HMR-applying overlay's 'received' frame here (synchronously),
1562
+ // well before the authoritative payload (`ns:angular-update` /
1563
+ // `ns:css-updates`) lands. Skip running any other handlers —
1564
+ // the pending message has no module payload and intentionally
1565
+ // does not bump the graph version.
1566
+ if (msg.type === 'ns:hmr-pending' && typeof msg.path === 'string') {
1567
+ setHmrPendingOverlay(msg.path);
1568
+ return;
1569
+ }
888
1570
  if (msg.type === 'ns:hmr-full-graph') {
889
1571
  // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
890
1572
  try {
@@ -896,6 +1578,34 @@ async function handleHmrMessage(ev) {
896
1578
  const prevGraph = new Map(graph);
897
1579
  setGraphVersion(Number(msg.version || getGraphVersion() || 0));
898
1580
  applyFullGraph(msg);
1581
+ hasReceivedFullGraph = true;
1582
+ // Gate: On first boot, the entry-runtime handles all initial module loading
1583
+ // (with the import map already configured). Don't re-import here — the graph
1584
+ // is stored above for future HMR delta comparisons, but modules are already
1585
+ // loaded correctly via the entry-runtime boot sequence.
1586
+ //
1587
+ // Two cases to catch:
1588
+ // 1. Boot still in progress (__NS_HMR_BOOT_COMPLETE__ is false)
1589
+ // 2. Boot already finished but this is the FIRST full-graph (prevGraph was
1590
+ // empty). The WebSocket often connects after entry-runtime finishes, so
1591
+ // boot is "complete" but we still shouldn't re-import — all modules were
1592
+ // just loaded fresh. Only re-import on subsequent full-graphs (reconnect
1593
+ // scenarios) where prevGraph already has entries.
1594
+ if (!globalThis.__NS_HMR_BOOT_COMPLETE__) {
1595
+ if (VERBOSE)
1596
+ console.info('[hmr][full-graph] skipping initial re-import (boot in progress)');
1597
+ const fullIds = Array.isArray(msg.modules) ? msg.modules.map((m) => m?.id).filter(Boolean) : [];
1598
+ notifyAppHmrUpdate('full-graph', fullIds);
1599
+ return;
1600
+ }
1601
+ if (prevGraph.size === 0) {
1602
+ if (VERBOSE)
1603
+ console.info('[hmr][full-graph] skipping re-import on first graph after boot (modules already fresh)');
1604
+ const fullIds = Array.isArray(msg.modules) ? msg.modules.map((m) => m?.id).filter(Boolean) : [];
1605
+ notifyAppHmrUpdate('full-graph', fullIds);
1606
+ return;
1607
+ }
1608
+ // Reconnect / resync case — re-import changed modules as normal.
899
1609
  // In some cases (e.g. server chooses full-graph resync / page reload), we won't
900
1610
  // receive a delta queue to re-import changed TS modules. Without re-import,
901
1611
  // HTTP ESM caching means module bodies (and side effects) won't re-run.
@@ -926,6 +1636,14 @@ async function handleHmrMessage(ev) {
926
1636
  });
927
1637
  if (toReimport.length && VERBOSE)
928
1638
  console.log('[hmr][full-graph] inferred changed modules; re-importing', toReimport);
1639
+ // Evict the inferred changed set before re-importing.
1640
+ // See `processQueue` for the architectural rationale; the
1641
+ // full-graph code path is the resync fallback (server chose
1642
+ // not to send a delta) and shares the same V8 cache pitfall.
1643
+ const fgEvictUrls = buildEvictionUrls(toReimport);
1644
+ const fgEvicted = invalidateModulesByUrls(fgEvictUrls);
1645
+ if (VERBOSE)
1646
+ console.log(`[hmr][full-graph] eviction count=${fgEvictUrls.length} ok=${fgEvicted}`);
929
1647
  for (const id of toReimport) {
930
1648
  try {
931
1649
  const spec = normalizeSpec(id);
@@ -998,8 +1716,13 @@ async function handleHmrMessage(ev) {
998
1716
  notifyAppHmrUpdate('delta', deltaIds);
999
1717
  return;
1000
1718
  }
1001
- else if (handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
1002
- return;
1719
+ else {
1720
+ if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
1721
+ setGraphVersion(Number(msg.version || getGraphVersion() || 0));
1722
+ }
1723
+ if (await handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
1724
+ return;
1725
+ }
1003
1726
  }
1004
1727
  }
1005
1728
  // On-demand module fetch response (Option A)
@@ -1140,7 +1863,7 @@ async function performResetRoot(newComponent) {
1140
1863
  if (cachedRoot)
1141
1864
  return cachedRoot;
1142
1865
  try {
1143
- switch (__NS_TARGET_FLAVOR__) {
1866
+ switch (TARGET_FLAVOR) {
1144
1867
  case 'vue':
1145
1868
  cachedRoot = getRootForVue(newComponent, state);
1146
1869
  break;
@@ -1455,9 +2178,29 @@ export function initHmrClient(opts) {
1455
2178
  }
1456
2179
  g.__NS_HMR_CLIENT_ACTIVE__ = true;
1457
2180
  ensureCoreAliasesOnGlobalThis();
1458
- connectHmr();
2181
+ // Defer WebSocket connection until boot completes to avoid native V8 crashes
2182
+ // caused by concurrent WebSocket message handling + HTTP fetch during early startup.
2183
+ // The WebSocket is only needed for HMR updates, not the initial boot sequence.
2184
+ if (g.__NS_HMR_BOOT_COMPLETE__) {
2185
+ connectHmr();
2186
+ }
2187
+ else {
2188
+ const waitForBoot = () => {
2189
+ if (globalThis.__NS_HMR_BOOT_COMPLETE__) {
2190
+ if (VERBOSE)
2191
+ console.log('[hmr-client] boot complete, connecting HMR WebSocket');
2192
+ connectHmr();
2193
+ }
2194
+ else {
2195
+ setTimeout(waitForBoot, 100);
2196
+ }
2197
+ };
2198
+ if (VERBOSE)
2199
+ console.log('[hmr-client] deferring WebSocket connection until boot completes');
2200
+ setTimeout(waitForBoot, 100);
2201
+ }
1459
2202
  // Best-effort: install back wrapper even before first remount; original root may be captured later
1460
- switch (__NS_TARGET_FLAVOR__) {
2203
+ switch (TARGET_FLAVOR) {
1461
2204
  case 'vue':
1462
2205
  ensureBackWrapperInstalled(performResetRoot, getCore);
1463
2206
  break;