@nativescript/vite 8.0.0-alpha.34 → 8.0.0-alpha.36

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 (69) hide show
  1. package/configuration/base.js +9 -1
  2. package/configuration/base.js.map +1 -1
  3. package/configuration/javascript.js +5 -3
  4. package/configuration/javascript.js.map +1 -1
  5. package/configuration/typescript.js +3 -2
  6. package/configuration/typescript.js.map +1 -1
  7. package/helpers/global-defines.d.ts +23 -0
  8. package/helpers/global-defines.js +36 -1
  9. package/helpers/global-defines.js.map +1 -1
  10. package/helpers/logging.js +2 -1
  11. package/helpers/logging.js.map +1 -1
  12. package/helpers/main-entry.d.ts +1 -0
  13. package/helpers/main-entry.js +18 -21
  14. package/helpers/main-entry.js.map +1 -1
  15. package/hmr/client/css-handler.js +4 -3
  16. package/hmr/client/css-handler.js.map +1 -1
  17. package/hmr/client/hmr-pending-overlay.d.ts +0 -14
  18. package/hmr/client/hmr-pending-overlay.js +13 -3
  19. package/hmr/client/hmr-pending-overlay.js.map +1 -1
  20. package/hmr/client/index.js +269 -39
  21. package/hmr/client/index.js.map +1 -1
  22. package/hmr/client/utils.js +7 -3
  23. package/hmr/client/utils.js.map +1 -1
  24. package/hmr/entry-runtime.js +5 -5
  25. package/hmr/entry-runtime.js.map +1 -1
  26. package/hmr/frameworks/angular/client/index.js +34 -10
  27. package/hmr/frameworks/angular/client/index.js.map +1 -1
  28. package/hmr/frameworks/vue/client/index.js +14 -10
  29. package/hmr/frameworks/vue/client/index.js.map +1 -1
  30. package/hmr/frameworks/vue/client/vue-sfc-update-overlay.d.ts +0 -33
  31. package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js +12 -3
  32. package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js.map +1 -1
  33. package/hmr/helpers/ast-normalizer.js +2 -1
  34. package/hmr/helpers/ast-normalizer.js.map +1 -1
  35. package/hmr/server/websocket-core-bridge.js +2 -1
  36. package/hmr/server/websocket-core-bridge.js.map +1 -1
  37. package/hmr/server/websocket.js +2 -1
  38. package/hmr/server/websocket.js.map +1 -1
  39. package/hmr/shared/runtime/boot-progress.js +3 -2
  40. package/hmr/shared/runtime/boot-progress.js.map +1 -1
  41. package/hmr/shared/runtime/boot-timeline.js +2 -12
  42. package/hmr/shared/runtime/boot-timeline.js.map +1 -1
  43. package/hmr/shared/runtime/dev-overlay.js +2 -1
  44. package/hmr/shared/runtime/dev-overlay.js.map +1 -1
  45. package/hmr/shared/runtime/global-scope.d.ts +18 -0
  46. package/hmr/shared/runtime/global-scope.js +21 -0
  47. package/hmr/shared/runtime/global-scope.js.map +1 -0
  48. package/hmr/shared/runtime/hooks.js +2 -1
  49. package/hmr/shared/runtime/hooks.js.map +1 -1
  50. package/hmr/shared/runtime/http-only-boot.js +7 -6
  51. package/hmr/shared/runtime/http-only-boot.js.map +1 -1
  52. package/hmr/shared/runtime/module-provenance.js +3 -2
  53. package/hmr/shared/runtime/module-provenance.js.map +1 -1
  54. package/hmr/shared/runtime/root-placeholder-view.js +2 -1
  55. package/hmr/shared/runtime/root-placeholder-view.js.map +1 -1
  56. package/hmr/shared/runtime/root-placeholder.js +5 -4
  57. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  58. package/hmr/shared/runtime/session-bootstrap.js +10 -9
  59. package/hmr/shared/runtime/session-bootstrap.js.map +1 -1
  60. package/hmr/shared/runtime/vendor-bootstrap.js +2 -1
  61. package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
  62. package/hmr/shared/vendor/manifest-loader.js +2 -1
  63. package/hmr/shared/vendor/manifest-loader.js.map +1 -1
  64. package/hmr/vendor-bootstrap.d.ts +1 -3
  65. package/hmr/vendor-bootstrap.js +4 -6
  66. package/hmr/vendor-bootstrap.js.map +1 -1
  67. package/package.json +1 -1
  68. package/runtime/core-aliases-early.js +2 -3
  69. package/runtime/core-aliases-early.js.map +1 -1
@@ -12,6 +12,7 @@
12
12
  * (`__NS_HMR_PROGRESS_OVERLAY_ENABLED__`) and detail-string formatting
13
13
  * can be unit-tested without booting the full HMR client.
14
14
  */
15
+ import { getGlobalScope } from '../shared/runtime/global-scope.js';
15
16
  export function buildHmrPendingDetail(filePath) {
16
17
  if (!filePath || filePath === '<unknown>')
17
18
  return 'Preparing update';
@@ -21,12 +22,21 @@ export function applyHmrPendingFrame(filePath, deps) {
21
22
  const enabled = typeof deps.overlayEnabled === 'boolean'
22
23
  ? deps.overlayEnabled
23
24
  : (() => {
25
+ // Define substitution does not reach this raw-served file — fall
26
+ // back to the globalThis seed planted by the entry's defines-seed
27
+ // module before defaulting to enabled.
24
28
  try {
25
- return typeof __NS_HMR_PROGRESS_OVERLAY_ENABLED__ === 'boolean' ? __NS_HMR_PROGRESS_OVERLAY_ENABLED__ : true;
29
+ if (typeof __NS_HMR_PROGRESS_OVERLAY_ENABLED__ === 'boolean')
30
+ return __NS_HMR_PROGRESS_OVERLAY_ENABLED__;
26
31
  }
27
- catch {
28
- return true;
32
+ catch { }
33
+ try {
34
+ const seeded = getGlobalScope().__NS_HMR_PROGRESS_OVERLAY_ENABLED__;
35
+ if (typeof seeded === 'boolean')
36
+ return seeded;
29
37
  }
38
+ catch { }
39
+ return true;
30
40
  })();
31
41
  if (!enabled)
32
42
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"hmr-pending-overlay.js","sourceRoot":"","sources":["../../../../../packages/vite/hmr/client/hmr-pending-overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAkBH,MAAM,UAAU,qBAAqB,CAAC,QAA4B;IACjE,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,WAAW;QAAE,OAAO,kBAAkB,CAAC;IACrE,OAAO,YAAY,QAAQ,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAA4B,EAAE,IAA2B;IAC7F,MAAM,OAAO,GACZ,OAAO,IAAI,CAAC,cAAc,KAAK,SAAS;QACvC,CAAC,CAAC,IAAI,CAAC,cAAc;QACrB,CAAC,CAAC,CAAC,GAAG,EAAE;YACN,IAAI,CAAC;gBACJ,OAAO,OAAO,mCAAmC,KAAK,SAAS,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC9G,CAAC;YAAC,MAAM,CAAC;gBACR,OAAO,IAAI,CAAC;YACb,CAAC;QACF,CAAC,CAAC,EAAE,CAAC;IACR,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,IAAI,GAAsB,CAAC;IAC3B,IAAI,CAAC;QACJ,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IACnE,IAAI,CAAC;QACJ,GAAG,CAAC,cAAc,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC5E,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"hmr-pending-overlay.js","sourceRoot":"","sources":["../../../../../packages/vite/hmr/client/hmr-pending-overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAkBnE,MAAM,UAAU,qBAAqB,CAAC,QAA4B;IACjE,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,WAAW;QAAE,OAAO,kBAAkB,CAAC;IACrE,OAAO,YAAY,QAAQ,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAA4B,EAAE,IAA2B;IAC7F,MAAM,OAAO,GACZ,OAAO,IAAI,CAAC,cAAc,KAAK,SAAS;QACvC,CAAC,CAAC,IAAI,CAAC,cAAc;QACrB,CAAC,CAAC,CAAC,GAAG,EAAE;YACN,iEAAiE;YACjE,kEAAkE;YAClE,uCAAuC;YACvC,IAAI,CAAC;gBACJ,IAAI,OAAO,mCAAmC,KAAK,SAAS;oBAAE,OAAO,mCAAmC,CAAC;YAC1G,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACV,IAAI,CAAC;gBACJ,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC,mCAAmC,CAAC;gBACpE,IAAI,OAAO,MAAM,KAAK,SAAS;oBAAE,OAAO,MAAM,CAAC;YAChD,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACV,OAAO,IAAI,CAAC;QACb,CAAC,CAAC,EAAE,CAAC;IACR,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,IAAI,GAAsB,CAAC;IAC3B,IAAI,CAAC;QACJ,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IACnE,IAAI,CAAC;QACJ,GAAG,CAAC,cAAc,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC5E,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC"}
@@ -8,6 +8,7 @@
8
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
  import { buildCssApplyingDetail, buildCssAppliedDetail } from './css-update-overlay.js';
11
+ import { getGlobalScope } from '../shared/runtime/global-scope.js';
11
12
  const VERBOSE = typeof __NS_ENV_VERBOSE__ !== 'undefined' && __NS_ENV_VERBOSE__;
12
13
  function resolveTargetFlavor() {
13
14
  try {
@@ -17,7 +18,7 @@ function resolveTargetFlavor() {
17
18
  }
18
19
  catch { }
19
20
  try {
20
- const g = globalThis;
21
+ const g = getGlobalScope();
21
22
  if (typeof g.__NS_TARGET_FLAVOR__ === 'string' && g.__NS_TARGET_FLAVOR__) {
22
23
  return g.__NS_TARGET_FLAVOR__;
23
24
  }
@@ -41,7 +42,11 @@ try {
41
42
  }
42
43
  }
43
44
  catch { }
44
- const APP_ROOT_VIRTUAL = typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__ ? __NS_APP_ROOT_VIRTUAL__ : '/src';
45
+ // Define substitution does NOT reach this file (served raw from node_modules),
46
+ // so prefer the globalThis seed planted by the entry's defines-seed module —
47
+ // the '/src' literal is a last-resort default and is WRONG for 'app/'-rooted
48
+ // projects.
49
+ const APP_ROOT_VIRTUAL = (typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__) || (typeof getGlobalScope().__NS_APP_ROOT_VIRTUAL__ === 'string' && getGlobalScope().__NS_APP_ROOT_VIRTUAL__) || '/src';
45
50
  const APP_VIRTUAL_WITH_SLASH = APP_ROOT_VIRTUAL.endsWith('/') ? APP_ROOT_VIRTUAL : `${APP_ROOT_VIRTUAL}/`;
46
51
  const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
47
52
  // Policy: by default, let the app's own main entry mount initially; HMR client handles updates/remounts only.
@@ -118,7 +123,7 @@ function setUpdateOverlayStage(stage, info) {
118
123
  // A module-local Set would not be shared across instances; the global one
119
124
  // is.
120
125
  function getNsSolidHmrListenerSet() {
121
- const g = globalThis;
126
+ const g = getGlobalScope();
122
127
  let set = g.__ns_solid_hmr_listener_set;
123
128
  if (!set) {
124
129
  set = new Set();
@@ -148,7 +153,7 @@ function nsSolidHmrEmit(ev) {
148
153
  }
149
154
  }
150
155
  try {
151
- const g = globalThis;
156
+ const g = getGlobalScope();
152
157
  g.__ns_solid_hmr_subscribe = nsSolidHmrSubscribe;
153
158
  // Eagerly create the listener set so the global exists at module load time.
154
159
  getNsSolidHmrListenerSet();
@@ -246,7 +251,7 @@ let processingPromise = null;
246
251
  // Detect whether the early placeholder root is still active on screen
247
252
  function isPlaceholderActive() {
248
253
  try {
249
- const g = globalThis;
254
+ const g = getGlobalScope();
250
255
  if (g.__NS_DEV_PLACEHOLDER_ROOT_VIEW__)
251
256
  return true;
252
257
  if (g.__NS_DEV_PLACEHOLDER_ROOT_EARLY__)
@@ -276,7 +281,7 @@ function applyFullGraph(payload) {
276
281
  // causes a double-mount race (rescue fires at 450ms, then main.ts fires ~1s later,
277
282
  // causing a visual flash and leaving the app in an inconsistent state).
278
283
  try {
279
- const g = globalThis;
284
+ const g = getGlobalScope();
280
285
  const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
281
286
  if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && TARGET_FLAVOR !== 'typescript') {
282
287
  // simple snapshot helpers
@@ -462,7 +467,7 @@ function applyFullGraph(payload) {
462
467
  console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
463
468
  // Android-only: avoid racing entry-runtime reset and Activity bring-up
464
469
  try {
465
- const g = globalThis;
470
+ const g = getGlobalScope();
466
471
  const App = getCore('Application') || g.Application;
467
472
  const isAndroid = !!(App && App.android !== undefined);
468
473
  if (isAndroid) {
@@ -633,7 +638,7 @@ function applyDelta(payload) {
633
638
  }
634
639
  if (isAppMainEntryId(id)) {
635
640
  try {
636
- const exists = globalThis.require?.(id) || globalThis.__nsGetModuleExports?.(id);
641
+ const exists = getGlobalScope().require?.(id) || globalThis.__nsGetModuleExports?.(id);
637
642
  if (!exists && VERBOSE)
638
643
  console.log(`[hmr][delta] skipping unresolved ${APP_MAIN_ENTRY_SPEC} change`);
639
644
  if (!exists)
@@ -651,7 +656,7 @@ function applyDelta(payload) {
651
656
  }
652
657
  // Deterministic navigation using the current Vue app instance rather than vendor-held rootApp.
653
658
  function __nsNavigateUsingApp(comp, opts = {}) {
654
- const g = globalThis;
659
+ const g = getGlobalScope();
655
660
  CLIENT_STRATEGY?.beforeNavigateBuild?.();
656
661
  const AppFactory = g.createApp;
657
662
  const RootCtor = g.NSVRoot;
@@ -777,6 +782,184 @@ try {
777
782
  globalThis.__nsNavigateUsingApp = __nsNavigateUsingApp;
778
783
  }
779
784
  catch { }
785
+ const openModalRecords = [];
786
+ let modalTrackingInstalled = false;
787
+ /**
788
+ * Map a served/graph module id (e.g. `/app/modal-page.xml`) to its app-root
789
+ * relative path (`modal-page.xml`). Single mapping point — the raw-asset
790
+ * re-registration, page-navigation targets, and modal matching all derive
791
+ * from this; keep them in sync by construction.
792
+ */
793
+ function toAppRelativePath(id) {
794
+ try {
795
+ const spec = normalizeSpec(id);
796
+ const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
797
+ let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
798
+ if (relPath.startsWith(appVirtual))
799
+ relPath = relPath.slice(appVirtual.length);
800
+ return relPath || null;
801
+ }
802
+ catch {
803
+ return null;
804
+ }
805
+ }
806
+ /** App-root relative module name (no extension) for page-shaped files, else null. */
807
+ function toAppModuleName(id) {
808
+ const relPath = toAppRelativePath(id);
809
+ if (!relPath || !/\.(xml|ts|js)$/i.test(relPath))
810
+ return null;
811
+ return relPath.replace(/\.(xml|ts|js)$/i, '');
812
+ }
813
+ function ensureModalTracking() {
814
+ if (modalTrackingInstalled)
815
+ return;
816
+ try {
817
+ const View = getCore('View') || getGlobalScope().View;
818
+ const proto = View?.prototype;
819
+ if (!proto || typeof proto.showModal !== 'function')
820
+ return;
821
+ const orig = proto.showModal;
822
+ if (orig.__nsHmrModalTracked) {
823
+ modalTrackingInstalled = true;
824
+ return;
825
+ }
826
+ const wrapped = function (...args) {
827
+ const result = orig.apply(this, args);
828
+ try {
829
+ if (typeof args[0] === 'string' && result) {
830
+ // Mirror core's getModalOptions arg shapes: (moduleName, options)
831
+ // or the deprecated positional form.
832
+ const options = args.length === 2 && args[1] && typeof args[1] === 'object' ? args[1] : { context: args[1], closeCallback: args[2], fullscreen: args[3], animated: args[4], stretched: args[5] };
833
+ const moduleName = String(args[0])
834
+ .replace(/^\.\//, '')
835
+ .replace(/\.(xml|ts|js)$/i, '');
836
+ openModalRecords.push({ moduleName, options, parent: this, modal: result });
837
+ if (VERBOSE)
838
+ console.log('[hmr][modal] tracked open modal', moduleName);
839
+ }
840
+ }
841
+ catch { }
842
+ return result;
843
+ };
844
+ wrapped.__nsHmrModalTracked = true;
845
+ proto.showModal = wrapped;
846
+ modalTrackingInstalled = true;
847
+ }
848
+ catch (e) {
849
+ if (VERBOSE)
850
+ console.warn('[hmr][modal] tracking install failed', e);
851
+ }
852
+ }
853
+ /**
854
+ * Enumerate the modals that are currently presented AND were opened by module
855
+ * name, with everything needed to re-present them.
856
+ *
857
+ * Source of truth is core's live modal stack (`_getRootModalViews()`):
858
+ * - `modal._moduleName` — set by the Builder on every createViewFromEntry
859
+ * view (longstanding, used by livesync), so available on any core.
860
+ * - `modal._modalOptions` — the original ShowModalOptions, stored by core's
861
+ * `_showNativeModalView` (newer cores). For older cores the showModal
862
+ * wrap's records (see ensureModalTracking) fill the gap.
863
+ * - `modal._modalParent` — the presenting view.
864
+ * Stale wrap records are pruned against the live stack while we're here.
865
+ */
866
+ function getOpenStringModuleModals() {
867
+ const out = [];
868
+ try {
869
+ const App = getCore('Application');
870
+ const root = App?.getRootView?.() || App?._rootView;
871
+ const stack = root?._getRootModalViews?.() || [];
872
+ // Prune wrap records whose modal is gone (keeps the fallback list small).
873
+ for (let i = openModalRecords.length - 1; i >= 0; i--) {
874
+ if (!stack.includes(openModalRecords[i].modal)) {
875
+ openModalRecords.splice(i, 1);
876
+ }
877
+ }
878
+ for (const modal of stack) {
879
+ const record = openModalRecords.find((r) => r.modal === modal);
880
+ const rawModuleName = typeof modal?._moduleName === 'string' && modal._moduleName ? modal._moduleName : record?.moduleName;
881
+ const parent = modal?._modalParent || record?.parent;
882
+ const options = modal?._modalOptions || record?.options;
883
+ if (!rawModuleName || !parent)
884
+ continue;
885
+ const moduleName = String(rawModuleName)
886
+ .replace(/^\.\//, '')
887
+ .replace(/\.(xml|ts|js)$/i, '');
888
+ out.push({ moduleName, options, parent, modal });
889
+ }
890
+ }
891
+ catch (e) {
892
+ if (VERBOSE)
893
+ console.warn('[hmr][modal] open-modal enumeration failed', e);
894
+ }
895
+ return out;
896
+ }
897
+ /**
898
+ * Close and re-present an open modal so it rebuilds from the freshly
899
+ * re-registered XML/code-behind. Core clears the modal stack synchronously on
900
+ * close but the NATIVE dismissal completes asynchronously; iOS refuses a
901
+ * present while a dismissal is in flight. Newer cores fire `closedModally` on
902
+ * the modal at exactly that completion point — preferred signal. Older cores
903
+ * fall back to polling `isLoaded` (flipped by `_tearDownUI` in the same
904
+ * completion callback).
905
+ */
906
+ async function reshowOpenModal(record) {
907
+ const { parent, modal, moduleName, options } = record;
908
+ await new Promise((resolve) => {
909
+ let settled = false;
910
+ const finish = () => {
911
+ if (!settled) {
912
+ settled = true;
913
+ resolve();
914
+ }
915
+ };
916
+ let eventArmed = false;
917
+ try {
918
+ if (typeof modal.once === 'function') {
919
+ modal.once('closedModally', finish);
920
+ eventArmed = true;
921
+ }
922
+ }
923
+ catch { }
924
+ // Poll fallback (also the safety net if the event never fires —
925
+ // e.g. an interactive-dismiss cancellation).
926
+ const deadline = Date.now() + 2000;
927
+ const poll = () => {
928
+ if (settled)
929
+ return;
930
+ let stillLoaded = false;
931
+ try {
932
+ stillLoaded = !!modal.isLoaded;
933
+ }
934
+ catch { }
935
+ if ((!eventArmed && !stillLoaded) || Date.now() > deadline) {
936
+ finish();
937
+ return;
938
+ }
939
+ setTimeout(poll, 50);
940
+ };
941
+ setTimeout(poll, 50);
942
+ try {
943
+ modal.closeModal();
944
+ }
945
+ catch (e) {
946
+ if (VERBOSE)
947
+ console.warn('[hmr][modal] close failed for', moduleName, e);
948
+ finish();
949
+ }
950
+ });
951
+ // One settle beat so the platform finishes releasing the presentation
952
+ // before the new present begins.
953
+ await new Promise((resolve) => setTimeout(resolve, 100));
954
+ try {
955
+ parent.showModal(moduleName, { ...(options || {}), animated: false });
956
+ if (VERBOSE)
957
+ console.log('[hmr][modal] re-presented', moduleName);
958
+ }
959
+ catch (e) {
960
+ console.warn('[hmr][modal] re-present failed for', moduleName, e);
961
+ }
962
+ }
780
963
  async function processQueue() {
781
964
  if (!globalThis.__NS_HMR_BOOT_COMPLETE__) {
782
965
  if (VERBOSE)
@@ -857,6 +1040,27 @@ async function processQueue() {
857
1040
  if (VERBOSE)
858
1041
  console.log('[hmr][queue] re-import', { id, spec, url });
859
1042
  const mod = await import(/* @vite-ignore */ url);
1043
+ // TS/XML flavor: refresh the bundler module registry with the fresh
1044
+ // exports so Builder.createViewFromEntry / loadModule('<page>')
1045
+ // resolves the NEW code-behind (tap handlers, page events) instead
1046
+ // of the stale module captured in the boot bundle. Without this,
1047
+ // XML re-renders pick up new markup but keep old behavior.
1048
+ if (TARGET_FLAVOR === 'typescript' && mod && /\.(ts|js)$/i.test(id)) {
1049
+ try {
1050
+ const g = getGlobalScope();
1051
+ const moduleName = toAppModuleName(id);
1052
+ if (moduleName && typeof g.registerModule === 'function') {
1053
+ g.registerModule(moduleName, () => mod);
1054
+ g.registerModule('./' + moduleName, () => mod);
1055
+ if (VERBOSE)
1056
+ console.log('[hmr][queue] re-registered code-behind', moduleName);
1057
+ }
1058
+ }
1059
+ catch (e) {
1060
+ if (VERBOSE)
1061
+ console.warn('[hmr][queue] code-behind re-register failed for', id, e);
1062
+ }
1063
+ }
860
1064
  }
861
1065
  catch (e) {
862
1066
  if (VERBOSE)
@@ -976,7 +1180,7 @@ async function processQueue() {
976
1180
  const findRouter = () => {
977
1181
  if (discoveredRouter)
978
1182
  return discoveredRouter;
979
- const g = globalThis;
1183
+ const g = getGlobalScope();
980
1184
  if (g.__ns_router?.routesById)
981
1185
  return (discoveredRouter = g.__ns_router);
982
1186
  // Fallback: scan common global keys for router
@@ -1137,7 +1341,7 @@ async function processQueue() {
1137
1341
  // This preserves the shell (Frame, ActionBar, etc.) that the app's
1138
1342
  // own bootstrapping wires up via `Application.run`.
1139
1343
  try {
1140
- const g = globalThis;
1344
+ const g = getGlobalScope();
1141
1345
  const App = getCore('Application') || g.Application;
1142
1346
  if (!App || typeof App.resetRootView !== 'function') {
1143
1347
  if (VERBOSE)
@@ -1163,10 +1367,9 @@ async function processQueue() {
1163
1367
  const rawContent = await resp.text();
1164
1368
  // Register under all nickname variants the module registry uses.
1165
1369
  // The bundler context registers XML as e.g., './main-page.xml' and 'main-page.xml'
1166
- const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1167
- let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1168
- if (relPath.startsWith(appVirtual))
1169
- relPath = relPath.slice(appVirtual.length);
1370
+ const relPath = toAppRelativePath(id);
1371
+ if (!relPath)
1372
+ continue;
1170
1373
  const nicknames = ['./' + relPath, relPath];
1171
1374
  // Also add without extension for CSS
1172
1375
  const extIdx = relPath.lastIndexOf('.');
@@ -1192,21 +1395,24 @@ async function processQueue() {
1192
1395
  }
1193
1396
  }
1194
1397
  }
1398
+ // Modal-aware refresh: pages currently PRESENTED AS MODALS must be
1399
+ // closed + re-presented in place — navigating the top frame to a
1400
+ // modal's page would push it as a frame page, and resetRootView
1401
+ // would dismiss the modal entirely. State comes from core's live
1402
+ // modal stack (_moduleName/_modalOptions/_modalParent); the
1403
+ // showModal wrap only backfills options on older cores.
1404
+ ensureModalTracking();
1405
+ const openModals = getOpenStringModuleModals();
1406
+ const changedModuleNames = new Set(drained.map(toAppModuleName).filter(Boolean));
1407
+ const modalsToReshow = openModals.filter((record) => changedModuleNames.has(record.moduleName));
1408
+ const reshowModuleNames = new Set(modalsToReshow.map((record) => record.moduleName));
1195
1409
  // Determine if we can navigate in-place to a changed page
1196
1410
  // instead of resetting all the way back to app-root.
1197
1411
  // This keeps the user on the page they're editing for faster iteration.
1198
1412
  const changedXmlPages = drained
1199
1413
  .filter((id) => /\.xml$/i.test(id))
1200
- .map((id) => {
1201
- const spec = normalizeSpec(id);
1202
- const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1203
- let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1204
- if (relPath.startsWith(appVirtual))
1205
- relPath = relPath.slice(appVirtual.length);
1206
- // Strip .xml extension to get the moduleName (e.g., 'pages/status-bar')
1207
- return relPath.replace(/\.xml$/i, '');
1208
- })
1209
- .filter((m) => m && m !== 'app-root');
1414
+ .map((id) => toAppModuleName(id))
1415
+ .filter((m) => m && m !== 'app-root' && !reshowModuleNames.has(m));
1210
1416
  // Resolve the topmost Frame from the bundled realm.
1211
1417
  // Frame.topmost() relies on an internal frameStack array, so we must
1212
1418
  // call it on the bundled-realm class. Multiple strategies to find it:
@@ -1242,7 +1448,7 @@ async function processQueue() {
1242
1448
  catch { }
1243
1449
  }
1244
1450
  if (VERBOSE)
1245
- console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame);
1451
+ console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame, 'modalsToReshow=', modalsToReshow.length);
1246
1452
  if (changedXmlPages.length > 0 && topFrame) {
1247
1453
  // Navigate the current frame to the changed page directly.
1248
1454
  // Use the last changed XML page (most specific).
@@ -1257,11 +1463,21 @@ async function processQueue() {
1257
1463
  App.resetRootView({ moduleName: 'app-root' });
1258
1464
  }
1259
1465
  }
1260
- else {
1466
+ else if (modalsToReshow.length === 0) {
1467
+ // No frame page to refresh and no open modal owns the change —
1468
+ // fall back to a full root reset. (Skipped when an open modal is
1469
+ // being re-presented below: resetRootView would dismiss it.)
1261
1470
  if (VERBOSE)
1262
1471
  console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
1263
1472
  App.resetRootView({ moduleName: 'app-root' });
1264
1473
  }
1474
+ // Re-present any open modals whose XML/code-behind changed. The
1475
+ // modules were already re-registered above (raw XML assets + fresh
1476
+ // code-behind exports), so the re-presented modal rebuilds from
1477
+ // the new content while the page beneath it stays put.
1478
+ for (const record of modalsToReshow) {
1479
+ await reshowOpenModal(record);
1480
+ }
1265
1481
  }
1266
1482
  catch (e) {
1267
1483
  console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
@@ -1331,7 +1547,7 @@ function connectHmr() {
1331
1547
  // Build ordered host candidates with preference to the active HTTP origin
1332
1548
  const orderedHosts = [];
1333
1549
  try {
1334
- const g = globalThis;
1550
+ const g = getGlobalScope();
1335
1551
  const httpOrigin = g && typeof g.__NS_HTTP_ORIGIN__ === 'string' ? g.__NS_HTTP_ORIGIN__ : undefined;
1336
1552
  if (httpOrigin) {
1337
1553
  try {
@@ -1511,7 +1727,7 @@ async function handleHmrMessage(ev) {
1511
1727
  if (msg.type === 'ns:hmr-full-graph') {
1512
1728
  // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
1513
1729
  try {
1514
- const g = globalThis;
1730
+ const g = getGlobalScope();
1515
1731
  g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
1516
1732
  }
1517
1733
  catch { }
@@ -1636,7 +1852,7 @@ async function handleHmrMessage(ev) {
1636
1852
  if (msg.type === 'ns:hmr-delta') {
1637
1853
  // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
1638
1854
  try {
1639
- const g = globalThis;
1855
+ const g = getGlobalScope();
1640
1856
  g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
1641
1857
  }
1642
1858
  catch { }
@@ -1854,8 +2070,8 @@ function normalizeComponent(input, nameHint) {
1854
2070
  // If provided a render function, wrap with defineComponent
1855
2071
  if (typeof input === 'function') {
1856
2072
  CLIENT_STRATEGY?.beforeNavigateBuild?.();
1857
- const comp = globalThis.defineComponent
1858
- ? globalThis.defineComponent({
2073
+ const comp = getGlobalScope().defineComponent
2074
+ ? getGlobalScope().defineComponent({
1859
2075
  name: nameHint || input.name || 'AnonymousSFC',
1860
2076
  render: input,
1861
2077
  })
@@ -1865,8 +2081,8 @@ function normalizeComponent(input, nameHint) {
1865
2081
  // If object has a render function property
1866
2082
  if (input?.render && typeof input.render === 'function') {
1867
2083
  CLIENT_STRATEGY?.beforeNavigateBuild?.();
1868
- const comp = globalThis.defineComponent
1869
- ? globalThis.defineComponent({
2084
+ const comp = getGlobalScope().defineComponent
2085
+ ? getGlobalScope().defineComponent({
1870
2086
  name: nameHint || input.name || 'AnonymousSFC',
1871
2087
  render: input.render,
1872
2088
  })
@@ -1965,7 +2181,7 @@ async function performResetRoot(newComponent) {
1965
2181
  return factory;
1966
2182
  }
1967
2183
  // Android readiness before any root changes
1968
- const App = getCore('Application') || globalThis.Application;
2184
+ const App = getCore('Application') || getGlobalScope().Application;
1969
2185
  const isAndroid = !!(App && App.android !== undefined);
1970
2186
  if (isAndroid) {
1971
2187
  const isReady = () => {
@@ -2067,7 +2283,7 @@ async function performResetRoot(newComponent) {
2067
2283
  }
2068
2284
  catch { }
2069
2285
  try {
2070
- const AppAny = getCore('Application') || globalThis.Application;
2286
+ const AppAny = getCore('Application') || getGlobalScope().Application;
2071
2287
  isIOS = !!(AppAny && AppAny.ios !== undefined);
2072
2288
  }
2073
2289
  catch { }
@@ -2077,7 +2293,7 @@ async function performResetRoot(newComponent) {
2077
2293
  // - Otherwise (subsequent HMR updates with an authoritative Frame already in place), re-use the
2078
2294
  // current app Frame and navigate to the new Page. This avoids a brief flash that can occur
2079
2295
  // when swapping the entire root view on Android. The placeholder is never involved here.
2080
- const gAnyForPolicy = globalThis;
2296
+ const gAnyForPolicy = getGlobalScope();
2081
2297
  const placeholderFrame = (() => {
2082
2298
  try {
2083
2299
  return gAnyForPolicy.__NS_DEV_PLACEHOLDER_ROOT_VIEW__ || null;
@@ -2118,7 +2334,7 @@ async function performResetRoot(newComponent) {
2118
2334
  console.log('[hmr-client] full root replacement via resetRootView (placeholder will be discarded)', { isFrameRoot, isIOS, hadPlaceholder });
2119
2335
  // Fallback or preferred path: resetRootView with a creator that builds a fresh Frame and navigates to the new Page
2120
2336
  try {
2121
- const App2 = getCore('Application') || globalThis.Application;
2337
+ const App2 = getCore('Application') || getGlobalScope().Application;
2122
2338
  if (!App2 || typeof App2.resetRootView !== 'function') {
2123
2339
  console.warn('[hmr-client] Application.resetRootView unavailable');
2124
2340
  return false;
@@ -2137,7 +2353,7 @@ async function performResetRoot(newComponent) {
2137
2353
  if (VERBOSE)
2138
2354
  console.warn('[hmr-client] iOS Application.window is boolean false; attempting to clear cached window');
2139
2355
  try {
2140
- const g = globalThis;
2356
+ const g = getGlobalScope();
2141
2357
  const reg = g.__nsVendorRegistry;
2142
2358
  const req = reg?.get ? g.__nsVendorRequire || g.__nsRequire || g.require : g.__nsRequire || g.require;
2143
2359
  let helpers = null;
@@ -2241,6 +2457,20 @@ export function initHmrClient(opts) {
2241
2457
  }
2242
2458
  g.__NS_HMR_CLIENT_ACTIVE__ = true;
2243
2459
  ensureCoreAliasesOnGlobalThis();
2460
+ // XML flavor: record string-module modals from the moment the client is up
2461
+ // so an already-open modal can be re-presented when its files change.
2462
+ // Installed at init (not first-update time) because the wrap can only
2463
+ // observe showModal calls made AFTER it lands. Retried briefly because
2464
+ // getCore('View') may not resolve until the vendor realm finishes booting.
2465
+ if (TARGET_FLAVOR === 'typescript') {
2466
+ const tryInstallModalTracking = (attempts) => {
2467
+ ensureModalTracking();
2468
+ if (!modalTrackingInstalled && attempts > 0) {
2469
+ setTimeout(() => tryInstallModalTracking(attempts - 1), 250);
2470
+ }
2471
+ };
2472
+ tryInstallModalTracking(40);
2473
+ }
2244
2474
  // Defer WebSocket connection until boot completes to avoid native V8 crashes
2245
2475
  // caused by concurrent WebSocket message handling + HTTP fetch during early startup.
2246
2476
  // The WebSocket is only needed for HMR updates, not the initial boot sequence.