@nativescript/vite 8.0.0-alpha.13 → 8.0.0-alpha.15

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 (54) hide show
  1. package/helpers/main-entry.d.ts +2 -1
  2. package/helpers/main-entry.js +70 -8
  3. package/helpers/main-entry.js.map +1 -1
  4. package/helpers/ns-core-url.js +23 -0
  5. package/helpers/ns-core-url.js.map +1 -1
  6. package/hmr/client/css-handler.d.ts +1 -0
  7. package/hmr/client/css-handler.js +33 -5
  8. package/hmr/client/css-handler.js.map +1 -1
  9. package/hmr/client/css-update-overlay.d.ts +18 -0
  10. package/hmr/client/css-update-overlay.js +27 -0
  11. package/hmr/client/css-update-overlay.js.map +1 -0
  12. package/hmr/client/index.js +20 -0
  13. package/hmr/client/index.js.map +1 -1
  14. package/hmr/entry-runtime.d.ts +1 -0
  15. package/hmr/entry-runtime.js +95 -11
  16. package/hmr/entry-runtime.js.map +1 -1
  17. package/hmr/helpers/ast-normalizer.js +45 -5
  18. package/hmr/helpers/ast-normalizer.js.map +1 -1
  19. package/hmr/server/core-sanitize.d.ts +23 -0
  20. package/hmr/server/core-sanitize.js +60 -0
  21. package/hmr/server/core-sanitize.js.map +1 -1
  22. package/hmr/server/vite-plugin.js +24 -4
  23. package/hmr/server/vite-plugin.js.map +1 -1
  24. package/hmr/server/websocket-core-bridge.d.ts +10 -0
  25. package/hmr/server/websocket-core-bridge.js +35 -5
  26. package/hmr/server/websocket-core-bridge.js.map +1 -1
  27. package/hmr/server/websocket-css-hot-update.d.ts +33 -0
  28. package/hmr/server/websocket-css-hot-update.js +65 -0
  29. package/hmr/server/websocket-css-hot-update.js.map +1 -0
  30. package/hmr/server/websocket-ns-m-finalize.js +2 -4
  31. package/hmr/server/websocket-ns-m-finalize.js.map +1 -1
  32. package/hmr/server/websocket-served-module-helpers.js +19 -6
  33. package/hmr/server/websocket-served-module-helpers.js.map +1 -1
  34. package/hmr/server/websocket.d.ts +1 -0
  35. package/hmr/server/websocket.js +120 -67
  36. package/hmr/server/websocket.js.map +1 -1
  37. package/hmr/shared/runtime/boot-placeholder-ui.d.ts +69 -0
  38. package/hmr/shared/runtime/boot-placeholder-ui.js +101 -0
  39. package/hmr/shared/runtime/boot-placeholder-ui.js.map +1 -0
  40. package/hmr/shared/runtime/boot-progress.d.ts +40 -0
  41. package/hmr/shared/runtime/boot-progress.js +128 -0
  42. package/hmr/shared/runtime/boot-progress.js.map +1 -0
  43. package/hmr/shared/runtime/boot-timeline.d.ts +1 -0
  44. package/hmr/shared/runtime/boot-timeline.js +1 -0
  45. package/hmr/shared/runtime/boot-timeline.js.map +1 -1
  46. package/hmr/shared/runtime/dev-overlay.js +96 -5
  47. package/hmr/shared/runtime/dev-overlay.js.map +1 -1
  48. package/hmr/shared/runtime/root-placeholder.js +317 -47
  49. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  50. package/hmr/shared/runtime/session-bootstrap.js +132 -18
  51. package/hmr/shared/runtime/session-bootstrap.js.map +1 -1
  52. package/hmr/shared/vendor/manifest.js +18 -2
  53. package/hmr/shared/vendor/manifest.js.map +1 -1
  54. package/package.json +1 -1
@@ -0,0 +1,69 @@
1
+ import type { HmrOverlaySnapshot } from './dev-overlay.js';
2
+ /**
3
+ * Calibrated palette for the boot placeholder card. The card is
4
+ * intentionally light so the small accent (brand badge + progress
5
+ * fill) reads as the only "live" element on the screen.
6
+ *
7
+ * Tones map to the snapshot tone enum from `dev-overlay.ts`:
8
+ * - `info` (default) — calm slate + NS blue
9
+ * - `error` — dusty rose-on-white surface
10
+ */
11
+ export type BootPlaceholderTone = 'info' | 'error';
12
+ export interface BootPlaceholderPalette {
13
+ pageBackground: string;
14
+ cardBackground: string;
15
+ cardShadow: string;
16
+ titleText: string;
17
+ phaseText: string;
18
+ detailText: string;
19
+ progressTrack: string;
20
+ progressFill: string;
21
+ activityIndicator: string;
22
+ }
23
+ /**
24
+ * Resolve the palette for a given tone. Unknown tones fall back to
25
+ * the calm `info` palette
26
+ */
27
+ export declare function getBootPlaceholderPalette(tone?: string | null): BootPlaceholderPalette;
28
+ /**
29
+ * Animation contract for the boot placeholder.
30
+ *
31
+ * `entranceDurationMs` covers the one-shot fade + scale that fires
32
+ * when the placeholder card first attaches to the visual tree.
33
+ * `entranceFromScale` is the starting scale before easeOut to 1.
34
+ *
35
+ * `progressDurationMs` is the per-update fade for the progress fill's
36
+ * `scaleX`. Pegged to 220 ms (just under the heartbeat's 250 ms tick)
37
+ * so consecutive ticks chain into continuous motion rather than
38
+ * waiting on the previous animation to settle.
39
+ *
40
+ * `brandPulseDurationMs` is one half-period of the brand badge's
41
+ * opacity pulse. A full cycle is `brandPulseDurationMs * 2`. Keeping
42
+ * it long (~1.2 s) makes the pulse feel ambient rather than nervous.
43
+ */
44
+ export declare const BOOT_PLACEHOLDER_MOTION: {
45
+ readonly entranceDurationMs: 380;
46
+ readonly entranceFromScale: 0.94;
47
+ readonly progressDurationMs: 220;
48
+ readonly brandPulseDurationMs: 1200;
49
+ readonly brandPulseMinOpacity: 0.55;
50
+ };
51
+ /**
52
+ * Convert a 0–100 progress percentage into an `scaleX` factor that
53
+ * the fill view can animate against. Clamps to [`minScale`, 1] so the
54
+ * fill never collapses to an invisible hairline
55
+ *
56
+ * The default `minScale` of 0.01 keeps the bar visible during the
57
+ * earliest moments of boot when progress is 0
58
+ */
59
+ export declare function computeBootProgressFillScale(progress: number | null | undefined, minScale?: number): number;
60
+ /**
61
+ * The "phase + percent" line we show as the primary status text
62
+ * inside the card
63
+ */
64
+ export declare function formatBootPrimaryLine(snapshot: Pick<HmrOverlaySnapshot, 'phase' | 'progress'> | null | undefined): string;
65
+ /**
66
+ * The secondary detail line (e.g. "Loading the application module
67
+ * graph (6259ms)").
68
+ */
69
+ export declare function formatBootDetailLine(snapshot: Pick<HmrOverlaySnapshot, 'detail'> | null | undefined): string;
@@ -0,0 +1,101 @@
1
+ // Pure helpers for the cold-boot placeholder's visual treatment.
2
+ //
3
+ // The visible UI is constructed in `root-placeholder.ts` (an NS view
4
+ // tree attached to `Application` via `launchEvent`), and driven by the
5
+ // dev-overlay via `updateBootStatusLabel`. This file owns the bits
6
+ // that don't need a runtime view tree to make sense — colours,
7
+ // animation timings, the formula that turns a 0–100 progress into the
8
+ // fill bar's `scaleX`, and the split between the "phase + percent"
9
+ // primary line and the secondary detail line.
10
+ //
11
+ const INFO_PALETTE = {
12
+ pageBackground: '#F4F7FB',
13
+ cardBackground: '#FFFFFF',
14
+ cardShadow: '#0F172A',
15
+ titleText: '#0F172A',
16
+ phaseText: '#475569',
17
+ detailText: '#94A3B8',
18
+ progressTrack: '#E2E8F0',
19
+ progressFill: '#3B6FE5',
20
+ activityIndicator: '#3B6FE5',
21
+ };
22
+ const ERROR_PALETTE = {
23
+ pageBackground: '#FFF5F5',
24
+ cardBackground: '#FFFFFF',
25
+ cardShadow: '#7F1D1D',
26
+ titleText: '#7F1D1D',
27
+ phaseText: '#B91C1C',
28
+ detailText: '#DC2626',
29
+ progressTrack: '#FCA5A5',
30
+ progressFill: '#B41810',
31
+ activityIndicator: '#B41810',
32
+ };
33
+ /**
34
+ * Resolve the palette for a given tone. Unknown tones fall back to
35
+ * the calm `info` palette
36
+ */
37
+ export function getBootPlaceholderPalette(tone) {
38
+ if (tone === 'error')
39
+ return ERROR_PALETTE;
40
+ return INFO_PALETTE;
41
+ }
42
+ /**
43
+ * Animation contract for the boot placeholder.
44
+ *
45
+ * `entranceDurationMs` covers the one-shot fade + scale that fires
46
+ * when the placeholder card first attaches to the visual tree.
47
+ * `entranceFromScale` is the starting scale before easeOut to 1.
48
+ *
49
+ * `progressDurationMs` is the per-update fade for the progress fill's
50
+ * `scaleX`. Pegged to 220 ms (just under the heartbeat's 250 ms tick)
51
+ * so consecutive ticks chain into continuous motion rather than
52
+ * waiting on the previous animation to settle.
53
+ *
54
+ * `brandPulseDurationMs` is one half-period of the brand badge's
55
+ * opacity pulse. A full cycle is `brandPulseDurationMs * 2`. Keeping
56
+ * it long (~1.2 s) makes the pulse feel ambient rather than nervous.
57
+ */
58
+ export const BOOT_PLACEHOLDER_MOTION = {
59
+ entranceDurationMs: 380,
60
+ entranceFromScale: 0.94,
61
+ progressDurationMs: 220,
62
+ brandPulseDurationMs: 1200,
63
+ brandPulseMinOpacity: 0.55,
64
+ };
65
+ /**
66
+ * Convert a 0–100 progress percentage into an `scaleX` factor that
67
+ * the fill view can animate against. Clamps to [`minScale`, 1] so the
68
+ * fill never collapses to an invisible hairline
69
+ *
70
+ * The default `minScale` of 0.01 keeps the bar visible during the
71
+ * earliest moments of boot when progress is 0
72
+ */
73
+ export function computeBootProgressFillScale(progress, minScale = 0.01) {
74
+ const numeric = typeof progress === 'number' && Number.isFinite(progress) ? progress : 0;
75
+ const normalized = numeric / 100;
76
+ if (!Number.isFinite(normalized))
77
+ return minScale;
78
+ return Math.max(minScale, Math.min(1, normalized));
79
+ }
80
+ /**
81
+ * The "phase + percent" line we show as the primary status text
82
+ * inside the card
83
+ */
84
+ export function formatBootPrimaryLine(snapshot) {
85
+ if (!snapshot)
86
+ return '';
87
+ const progress = snapshot.progress;
88
+ const progressText = typeof progress === 'number' && Number.isFinite(progress) ? ` (${Math.round(progress)}%)` : '';
89
+ return `${snapshot.phase || ''}${progressText}`.trim();
90
+ }
91
+ /**
92
+ * The secondary detail line (e.g. "Loading the application module
93
+ * graph (6259ms)").
94
+ */
95
+ export function formatBootDetailLine(snapshot) {
96
+ if (!snapshot)
97
+ return '';
98
+ const detail = snapshot.detail;
99
+ return typeof detail === 'string' ? detail : '';
100
+ }
101
+ //# sourceMappingURL=boot-placeholder-ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot-placeholder-ui.js","sourceRoot":"","sources":["../../../../../../packages/vite/hmr/shared/runtime/boot-placeholder-ui.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,EAAE;AACF,qEAAqE;AACrE,uEAAuE;AACvE,mEAAmE;AACnE,+DAA+D;AAC/D,sEAAsE;AACtE,mEAAmE;AACnE,8CAA8C;AAC9C,EAAE;AA2BF,MAAM,YAAY,GAA2B;IAC5C,cAAc,EAAE,SAAS;IACzB,cAAc,EAAE,SAAS;IACzB,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,UAAU,EAAE,SAAS;IACrB,aAAa,EAAE,SAAS;IACxB,YAAY,EAAE,SAAS;IACvB,iBAAiB,EAAE,SAAS;CAC5B,CAAC;AAEF,MAAM,aAAa,GAA2B;IAC7C,cAAc,EAAE,SAAS;IACzB,cAAc,EAAE,SAAS;IACzB,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,UAAU,EAAE,SAAS;IACrB,aAAa,EAAE,SAAS;IACxB,YAAY,EAAE,SAAS;IACvB,iBAAiB,EAAE,SAAS;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAoB;IAC7D,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,aAAa,CAAC;IAC3C,OAAO,YAAY,CAAC;AACrB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACtC,kBAAkB,EAAE,GAAG;IACvB,iBAAiB,EAAE,IAAI;IACvB,kBAAkB,EAAE,GAAG;IACvB,oBAAoB,EAAE,IAAI;IAC1B,oBAAoB,EAAE,IAAI;CACjB,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAAC,QAAmC,EAAE,QAAQ,GAAG,IAAI;IAChG,MAAM,OAAO,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACzF,MAAM,UAAU,GAAG,OAAO,GAAG,GAAG,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,QAAQ,CAAC;IAClD,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAA2E;IAChH,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;IACnC,MAAM,YAAY,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACpH,OAAO,GAAG,QAAQ,CAAC,KAAK,IAAI,EAAE,GAAG,YAAY,EAAE,CAAC,IAAI,EAAE,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAA+D;IACnG,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAC/B,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;AACjD,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `'importing-main'` is the long HTTP-module-load phase (5–10s for a
3
+ * real Angular app). The bar uses this range so the user sees ~62
4
+ * percentage points of motion during the phase that actually takes
5
+ * time, sandwiched between the cheap bootstrap stages
6
+ * ('configuring-import-map' = 26) and the post-import wait
7
+ * ('waiting-for-app' = 94 → 'app-root-committed' = 100).
8
+ */
9
+ export declare const BOOT_IMPORT_PROGRESS_MIN = 30;
10
+ export declare const BOOT_IMPORT_PROGRESS_MAX = 92;
11
+ /** Ceiling guards against rounding into `'waiting-for-app'` (94). */
12
+ export declare const BOOT_IMPORT_PROGRESS_CEILING = 94;
13
+ export declare function computeBootImportProgress(input: {
14
+ count?: number;
15
+ elapsedMs?: number;
16
+ }): number;
17
+ /**
18
+ * Render the second-line detail for the placeholder. Surfaces the
19
+ * count + last-loaded module path once the snippet has fired, or a
20
+ * generic "Loading the application module graph (Nms)" line during
21
+ * the pre-snippet window.
22
+ */
23
+ export declare function formatBootImportDetail(input: {
24
+ count?: number;
25
+ lastModule?: string;
26
+ elapsedMs?: number;
27
+ }): string;
28
+ /**
29
+ * Ratchet a candidate boot-import progress value against the highest
30
+ * value any consumer has emitted so far (stored on
31
+ * `globalThis.__NS_HMR_BOOT_LAST_PROGRESS__`). Reset by
32
+ * `clearBootProgressState` between sessions.
33
+ */
34
+ export declare function applyMonotonicBootProgress(candidate: number): number;
35
+ /**
36
+ * Reset every boot-progress global so a re-bootstrapped session (e.g.
37
+ * after `__reboot_ng_modules__`) starts a fresh ratchet rather than
38
+ * inheriting the previous cycle's terminal values.
39
+ */
40
+ export declare function clearBootProgressState(): void;
@@ -0,0 +1,128 @@
1
+ // Pure helpers for the cold-boot "Importing the app entry" progress display.
2
+ //
3
+ // Pipeline:
4
+ // * Server-side `buildBootProgressSnippet` is injected at the top of
5
+ // every `__ns_boot__/b1`-tagged module and bumps the count /
6
+ // last-module globals (FULLY SYNCHRONOUS — see the snippet doc for
7
+ // why top-level await must stay out of boot-tagged modules).
8
+ // * `startBrowserRuntimeSession` stamps the time origin
9
+ // (`__NS_HMR_BOOT_IMPORT_STARTED_AT__`) right before
10
+ // `__nsStartDevSession`.
11
+ // * `startBootImportHeartbeat` reads both signals every 250 ms and
12
+ // re-asserts `'importing-main'` so the bar climbs even across long
13
+ // vendor stretches that don't tick the count axis. The iOS runtime's
14
+ // `MaybePumpJSThreadDuringBoot` keeps the JS-thread CFRunLoop ticking
15
+ // between synchronous fetches so the heartbeat's `setInterval` timer
16
+ // can fire during the cold-boot module walk.
17
+ //
18
+ // Monotonic ratchet (`applyMonotonicBootProgress`,
19
+ // `__NS_HMR_BOOT_LAST_PROGRESS__`): each tick can never undercut the
20
+ // previous one, preventing visible stutter when the count axis briefly
21
+ // wins over the time axis.
22
+ /**
23
+ * `'importing-main'` is the long HTTP-module-load phase (5–10s for a
24
+ * real Angular app). The bar uses this range so the user sees ~62
25
+ * percentage points of motion during the phase that actually takes
26
+ * time, sandwiched between the cheap bootstrap stages
27
+ * ('configuring-import-map' = 26) and the post-import wait
28
+ * ('waiting-for-app' = 94 → 'app-root-committed' = 100).
29
+ */
30
+ export const BOOT_IMPORT_PROGRESS_MIN = 30;
31
+ export const BOOT_IMPORT_PROGRESS_MAX = 92;
32
+ /** Ceiling guards against rounding into `'waiting-for-app'` (94). */
33
+ export const BOOT_IMPORT_PROGRESS_CEILING = 94;
34
+ const BOOT_IMPORT_PROGRESS_RANGE = BOOT_IMPORT_PROGRESS_MAX - BOOT_IMPORT_PROGRESS_MIN;
35
+ /**
36
+ * Compute the percentage shown next to "Importing the app entry".
37
+ *
38
+ * Sum of two clamped contributions:
39
+ * * `progressFromCount` — `__NS_HMR_BOOT_MODULE_COUNT__` bumped by the
40
+ * boot snippet (1 unit per 2 modules, cap 40).
41
+ * * `progressFromTime` — elapsed wall-clock since
42
+ * `__NS_HMR_BOOT_IMPORT_STARTED_AT__` (1 unit per 250 ms, cap
43
+ * matches the full 62-point range so time alone can drive the bar
44
+ * 30 → 92 if the count axis is starved).
45
+ *
46
+ * The count axis goes silent in long node_modules stretches and on
47
+ * Vite's rewrite chains where the served import URL skips the
48
+ * `__ns_boot__/b1` prefix; the time axis covers those gaps. Both
49
+ * contributions are summed, clamped to the 62-point range, then
50
+ * ceilinged so they cannot cross into 'waiting-for-app' (94+).
51
+ *
52
+ * Canonical values are pinned by `boot-progress.spec.ts`. The
53
+ * server-side snippet does NOT mirror this math — it only writes the
54
+ * counters; the heartbeat in `session-bootstrap.ts` is the sole caller.
55
+ */
56
+ const BOOT_IMPORT_COUNT_CONTRIBUTION_MAX = 40;
57
+ const BOOT_IMPORT_TIME_CONTRIBUTION_MAX = 62;
58
+ const BOOT_IMPORT_COUNT_DIVISOR = 2;
59
+ const BOOT_IMPORT_TIME_TICK_MS = 250;
60
+ export function computeBootImportProgress(input) {
61
+ // Coerce NaN / ±Infinity / negatives to 0 — `Math.max(0, NaN)` is NaN.
62
+ const rawCount = Number(input?.count ?? 0);
63
+ const rawElapsed = Number(input?.elapsedMs ?? 0);
64
+ const count = Number.isFinite(rawCount) ? Math.max(0, rawCount) : 0;
65
+ const elapsedMs = Number.isFinite(rawElapsed) ? Math.max(0, rawElapsed) : 0;
66
+ const progressFromCount = Math.min(BOOT_IMPORT_COUNT_CONTRIBUTION_MAX, Math.floor(count / BOOT_IMPORT_COUNT_DIVISOR));
67
+ const progressFromTime = Math.min(BOOT_IMPORT_TIME_CONTRIBUTION_MAX, Math.floor(elapsedMs / BOOT_IMPORT_TIME_TICK_MS));
68
+ const combined = BOOT_IMPORT_PROGRESS_MIN + Math.min(BOOT_IMPORT_PROGRESS_RANGE, progressFromCount + progressFromTime);
69
+ return Math.min(BOOT_IMPORT_PROGRESS_CEILING, combined);
70
+ }
71
+ /**
72
+ * Render the second-line detail for the placeholder. Surfaces the
73
+ * count + last-loaded module path once the snippet has fired, or a
74
+ * generic "Loading the application module graph (Nms)" line during
75
+ * the pre-snippet window.
76
+ */
77
+ export function formatBootImportDetail(input) {
78
+ const count = Math.max(0, Number(input?.count ?? 0));
79
+ const lastModule = typeof input?.lastModule === 'string' ? input.lastModule : '';
80
+ const elapsedMs = Math.max(0, Number(input?.elapsedMs ?? 0));
81
+ if (count > 0) {
82
+ return lastModule ? `Evaluated ${count} modules\n${lastModule}` : `Evaluated ${count} modules`;
83
+ }
84
+ return `Loading the module graph (${elapsedMs}ms)`;
85
+ }
86
+ /**
87
+ * Ratchet a candidate boot-import progress value against the highest
88
+ * value any consumer has emitted so far (stored on
89
+ * `globalThis.__NS_HMR_BOOT_LAST_PROGRESS__`). Reset by
90
+ * `clearBootProgressState` between sessions.
91
+ */
92
+ export function applyMonotonicBootProgress(candidate) {
93
+ const g = globalThis;
94
+ const previousRaw = g.__NS_HMR_BOOT_LAST_PROGRESS__;
95
+ const previous = typeof previousRaw === 'number' && Number.isFinite(previousRaw) ? previousRaw : 0;
96
+ const next = Math.max(previous, Math.max(0, Number(candidate) || 0));
97
+ try {
98
+ g.__NS_HMR_BOOT_LAST_PROGRESS__ = next;
99
+ }
100
+ catch { }
101
+ return next;
102
+ }
103
+ /**
104
+ * Reset every boot-progress global so a re-bootstrapped session (e.g.
105
+ * after `__reboot_ng_modules__`) starts a fresh ratchet rather than
106
+ * inheriting the previous cycle's terminal values.
107
+ */
108
+ export function clearBootProgressState() {
109
+ const g = globalThis;
110
+ for (const key of [
111
+ '__NS_HMR_BOOT_MODULE_COUNT__',
112
+ '__NS_HMR_BOOT_LAST_MODULE__',
113
+ '__NS_HMR_BOOT_LAST_PROGRESS__',
114
+ '__NS_HMR_BOOT_LAST_PROGRESS_AT__',
115
+ '__NS_HMR_BOOT_IMPORT_STARTED_AT__',
116
+ // Defensive: an earlier snippet shape used this to throttle a
117
+ // top-level await yield. Removed — top-level await in a boot-tagged
118
+ // module trips the iOS 10s async-module deadline. Cleared in case a
119
+ // stale boot left it behind.
120
+ '__NS_HMR_BOOT_LAST_YIELD_AT__',
121
+ ]) {
122
+ try {
123
+ delete g[key];
124
+ }
125
+ catch { }
126
+ }
127
+ }
128
+ //# sourceMappingURL=boot-progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot-progress.js","sourceRoot":"","sources":["../../../../../../packages/vite/hmr/shared/runtime/boot-progress.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@ export type BootTrace = {
8
8
  t1?: number;
9
9
  session?: BootTraceSegment;
10
10
  importMap?: BootTraceSegment;
11
+ kickstart?: BootTraceSegment;
11
12
  native?: BootTraceSegment;
12
13
  error?: {
13
14
  message: string;
@@ -33,6 +33,7 @@ export function formatBootTimeline(trace) {
33
33
  }
34
34
  push('session', trace.session);
35
35
  push('importMap', trace.importMap);
36
+ push('kickstart', trace.kickstart);
36
37
  push('native', trace.native);
37
38
  const suffix = trace.error?.message ? `: ${trace.error.message}` : '';
38
39
  return `[ns-boot] ${status} ${parts.join(' ')}${suffix}`.replace(/\s+$/, '');
@@ -1 +1 @@
1
- {"version":3,"file":"boot-timeline.js","sourceRoot":"","sources":["../../../../../../packages/vite/hmr/shared/runtime/boot-timeline.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,uEAAuE;AACvE,wEAAwE;AACxE,sEAAsE;AACtE,sEAAsE;AACtE,gEAAgE;AAChE,EAAE;AACF,sEAAsE;AACtE,qEAAqE;AACrE,4DAA4D;AAiB5D,oEAAoE;AACpE,2CAA2C;AAC3C,EAAE;AACF,UAAU;AACV,wEAAwE;AACxE,6DAA6D;AAC7D,EAAE;AACF,uEAAuE;AACvE,uEAAuE;AACvE,wEAAwE;AACxE,MAAM,UAAU,kBAAkB,CAAC,KAAgB;IAClD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,GAAiC,EAAE,EAAE;QACjE,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAClE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7G,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAE7B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEtE,OAAO,aAAa,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,sEAAsE;AACtE,qEAAqE;AACrE,sEAAsE;AACtE,wEAAwE;AACxE,SAAS;AACT,MAAM,UAAU,gBAAgB,CAAC,KAAgB;IAChD,IAAI,CAAC;QACH,UAAkB,CAAC,iBAAiB,GAAG,KAAK,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AACX,CAAC"}
1
+ {"version":3,"file":"boot-timeline.js","sourceRoot":"","sources":["../../../../../../packages/vite/hmr/shared/runtime/boot-timeline.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,uEAAuE;AACvE,wEAAwE;AACxE,sEAAsE;AACtE,sEAAsE;AACtE,gEAAgE;AAChE,EAAE;AACF,sEAAsE;AACtE,qEAAqE;AACrE,4DAA4D;AAsB5D,oEAAoE;AACpE,2CAA2C;AAC3C,EAAE;AACF,UAAU;AACV,wEAAwE;AACxE,6DAA6D;AAC7D,EAAE;AACF,uEAAuE;AACvE,uEAAuE;AACvE,wEAAwE;AACxE,MAAM,UAAU,kBAAkB,CAAC,KAAgB;IAClD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,GAAiC,EAAE,EAAE;QACjE,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAClE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7G,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAE7B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEtE,OAAO,aAAa,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,sEAAsE;AACtE,qEAAqE;AACrE,sEAAsE;AACtE,wEAAwE;AACxE,SAAS;AACT,MAAM,UAAU,gBAAgB,CAAC,KAAgB;IAChD,IAAI,CAAC;QACH,UAAkB,CAAC,iBAAiB,GAAG,KAAK,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AACX,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { BOOT_PLACEHOLDER_MOTION, computeBootProgressFillScale, formatBootDetailLine, formatBootPrimaryLine } from './boot-placeholder-ui.js';
1
2
  const DEFAULT_OVERLAY_POSITION = 'top';
2
3
  const BOOT_TITLE = 'NativeScript Vite preparing dev session...';
3
4
  const DEFAULT_SNAPSHOT = {
@@ -178,7 +179,11 @@ export function createBootOverlaySnapshot(stage, info) {
178
179
  badge: 'BOOT',
179
180
  title: BOOT_TITLE,
180
181
  phase: 'Importing the app entry',
181
- progress: 82,
182
+ // 30 (not 82) so the bar visibly climbs the ~62 points the
183
+ // heartbeat + snippet drive during the long HTTP-module-load
184
+ // phase. The monotonic ratchet in `setBootStage` prevents
185
+ // earlier-but-higher stages from being clobbered.
186
+ progress: 30,
182
187
  busy: true,
183
188
  blocking: true,
184
189
  tone: 'info',
@@ -565,10 +570,28 @@ function findBootStatusLabel() {
565
570
  catch { }
566
571
  return null;
567
572
  }
573
+ function findBootDetailLabel() {
574
+ const g = getOverlayGlobal();
575
+ return g.__NS_DEV_BOOT_DETAIL_LABEL__ || null;
576
+ }
577
+ function findBootProgressFill() {
578
+ const g = getOverlayGlobal();
579
+ return g.__NS_DEV_BOOT_PROGRESS_FILL__ || null;
580
+ }
568
581
  function updateBootStatusLabel(snapshot) {
569
- const newText = formatStatusText(snapshot) || 'Preparing the HTTP HMR bootstrap (4%)';
570
582
  const statusLabel = findBootStatusLabel();
583
+ const detailLabel = findBootDetailLabel();
584
+ const progressFill = findBootProgressFill();
571
585
  const activityIndicator = findBootActivityIndicator();
586
+ // New (card) layout: phase line + detail line live in separate
587
+ // labels so the typography can differ. Legacy (single-label)
588
+ // layout: keep the original combined "phase (X%)\ndetail" text so
589
+ // nothing visually regresses for runtimes still attached to the
590
+ // older placeholder shape.
591
+ const hasSplitLabels = !!detailLabel;
592
+ const phaseLine = formatBootPrimaryLine(snapshot);
593
+ const detailLine = formatBootDetailLine(snapshot);
594
+ const combinedText = formatStatusText(snapshot) || 'Preparing the HTTP HMR bootstrap (4%)';
572
595
  if (!statusLabel) {
573
596
  if (activityIndicator) {
574
597
  try {
@@ -577,11 +600,16 @@ function updateBootStatusLabel(snapshot) {
577
600
  }
578
601
  catch { }
579
602
  }
603
+ applyBootProgressFill(progressFill, snapshot);
580
604
  return;
581
605
  }
582
606
  try {
583
- statusLabel.text = newText;
584
- statusLabel.color = asColor(snapshot.tone === 'error' ? '#b41810e6' : '#563e3fb1');
607
+ statusLabel.text = hasSplitLabels ? phaseLine || 'Preparing the HTTP HMR bootstrap' : combinedText;
608
+ // Card layout uses the calibrated phase-text colour from the
609
+ // palette; legacy single-label layout keeps the original muted
610
+ // brown so we don't visually regress mid-session.
611
+ const phaseColorHex = snapshot.tone === 'error' ? '#B91C1C' : hasSplitLabels ? '#475569' : '#563e3fb1';
612
+ statusLabel.color = asColor(phaseColorHex);
585
613
  if (typeof statusLabel.requestLayout === 'function') {
586
614
  statusLabel.requestLayout();
587
615
  }
@@ -591,6 +619,15 @@ function updateBootStatusLabel(snapshot) {
591
619
  }
592
620
  }
593
621
  catch { }
622
+ if (detailLabel) {
623
+ try {
624
+ detailLabel.text = detailLine;
625
+ detailLabel.color = asColor(snapshot.tone === 'error' ? '#DC2626' : '#94A3B8');
626
+ detailLabel.visibility = detailLine ? 'visible' : 'collapse';
627
+ }
628
+ catch { }
629
+ }
630
+ applyBootProgressFill(progressFill, snapshot);
594
631
  if (activityIndicator) {
595
632
  try {
596
633
  activityIndicator.busy = !!snapshot.busy;
@@ -599,6 +636,50 @@ function updateBootStatusLabel(snapshot) {
599
636
  catch { }
600
637
  }
601
638
  }
639
+ // Drive the progress fill scaleX from the snapshot. Uses NS's view
640
+ // animate API for a smooth 220 ms easeOut between heartbeat ticks; a
641
+ // monotonic ratchet on `globalThis.__NS_DEV_BOOT_PROGRESS_LAST_SCALE__`
642
+ // guards against the fill snapping backwards if a less-progressed
643
+ // snapshot ever lands between ticks (mirrors the JS-side
644
+ // `applyMonotonicBootProgress` contract).
645
+ function applyBootProgressFill(progressFill, snapshot) {
646
+ if (!progressFill)
647
+ return;
648
+ const g = getOverlayGlobal();
649
+ const isError = snapshot.tone === 'error';
650
+ progressFill.backgroundColor = asColor(isError ? '#B41810' : '#3B6FE5');
651
+ const targetScale = computeBootProgressFillScale(snapshot.progress ?? null);
652
+ const previousRaw = Number(g.__NS_DEV_BOOT_PROGRESS_LAST_SCALE__);
653
+ const previous = Number.isFinite(previousRaw) ? previousRaw : 0;
654
+ const next = Math.max(previous, targetScale);
655
+ g.__NS_DEV_BOOT_PROGRESS_LAST_SCALE__ = next;
656
+ try {
657
+ // NS view.animate scales around `originX`/`originY`; the
658
+ // placeholder builder pins `originX = 0` so the fill grows
659
+ // rightward. animate() may be unavailable in some headless
660
+ // test environments — fall through to a direct property set.
661
+ if (typeof progressFill.animate === 'function') {
662
+ progressFill
663
+ .animate({
664
+ scale: { x: next, y: 1 },
665
+ duration: BOOT_PLACEHOLDER_MOTION.progressDurationMs,
666
+ curve: 'easeOut',
667
+ })
668
+ .catch(() => {
669
+ try {
670
+ progressFill.scaleX = next;
671
+ }
672
+ catch { }
673
+ });
674
+ }
675
+ else {
676
+ progressFill.scaleX = next;
677
+ }
678
+ }
679
+ catch {
680
+ progressFill.scaleX = next;
681
+ }
682
+ }
602
683
  function resolveActivePage() {
603
684
  try {
604
685
  const Frame = resolveCoreExport('Frame');
@@ -1548,7 +1629,17 @@ function createOverlayApi() {
1548
1629
  const state = getRuntimeState();
1549
1630
  clearUpdateAutoHideTimer(state);
1550
1631
  state.updateCycleStartedAt = 0;
1551
- return applyRuntimeSnapshot(createBootOverlaySnapshot(stage, info));
1632
+ const next = createBootOverlaySnapshot(stage, info);
1633
+ // Monotonic boot-progress ratchet: boot stages can fire out of
1634
+ // order across boot paths (native `__nsStartDevSession` vs the
1635
+ // http-bootloader fallback) and individual bases were tuned
1636
+ // independently, so clamp boot→boot transitions to never go
1637
+ // backwards. Non-boot snapshots (error/ready) bypass — they
1638
+ // genuinely want to reset the visual.
1639
+ if (next.mode === 'boot' && state.snapshot.mode === 'boot' && typeof next.progress === 'number' && typeof state.snapshot.progress === 'number' && next.progress < state.snapshot.progress) {
1640
+ next.progress = state.snapshot.progress;
1641
+ }
1642
+ return applyRuntimeSnapshot(next);
1552
1643
  },
1553
1644
  setConnectionStage(stage, info) {
1554
1645
  const state = getRuntimeState();