@zenbujs/core 0.0.5 → 0.0.9

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 (72) hide show
  1. package/dist/{advice-config-QYB2qEd_.mjs → advice-config-DXSIo0sg.mjs} +40 -39
  2. package/dist/advice.d.mts +8 -8
  3. package/dist/advice.mjs +2 -2
  4. package/dist/{base-window-BbFRRhKP.mjs → base-window-BxBZ2md_.mjs} +51 -7
  5. package/dist/{transforms-CuTODvDx.d.mts → build-config-Dzg2frpk.d.mts} +98 -28
  6. package/dist/build-config-pWdmLnrk.mjs +53 -0
  7. package/dist/{build-electron-CNJ0dLND.mjs → build-electron-Dsbb1EMl.mjs} +308 -120
  8. package/dist/{build-source-C2puqEVr.mjs → build-source-d1J3shV8.mjs} +62 -27
  9. package/dist/cli/bin.mjs +7 -7
  10. package/dist/cli/build.d.mts +2 -2
  11. package/dist/cli/build.mjs +2 -3
  12. package/dist/cli/resolve-config.mjs +1 -1
  13. package/dist/{cli-C3R1LBMY.mjs → cli-kL6mPgBE.mjs} +2 -2
  14. package/dist/config.d.mts +3 -3
  15. package/dist/config.mjs +2 -3
  16. package/dist/{db-xjvahRFJ.mjs → db-Bc292RYo.mjs} +2 -2
  17. package/dist/db.d.mts +1 -1
  18. package/dist/dev-B2emj0HZ.mjs +301 -0
  19. package/dist/env-bootstrap.d.mts +1 -1
  20. package/dist/events.d.mts +19 -0
  21. package/dist/events.mjs +1 -0
  22. package/dist/host-version-BIrF8tX7.mjs +65 -0
  23. package/dist/index-CVF768Xs.d.mts +783 -0
  24. package/dist/index.d.mts +5 -6
  25. package/dist/index.mjs +2 -2
  26. package/dist/installing-preload.cjs +60 -0
  27. package/dist/launcher.mjs +2615 -122
  28. package/dist/{link-c0_aLWQ3.mjs → link-glX89NV5.mjs} +215 -89
  29. package/dist/{load-config-xMf2wxH8.mjs → load-config-C4Oe2qZO.mjs} +5 -1
  30. package/dist/loaders/zenbu.mjs +102 -0
  31. package/dist/node-loader.mjs +1 -1
  32. package/dist/{publish-source-Dill72NS.mjs → publish-source-Dq2c0iOw.mjs} +2 -2
  33. package/dist/react.d.mts +55 -6
  34. package/dist/react.mjs +116 -5
  35. package/dist/registry-CMp8FYgS.d.mts +47 -0
  36. package/dist/registry-generated.d.mts +26 -0
  37. package/dist/registry-generated.mjs +1 -0
  38. package/dist/registry.d.mts +2 -2
  39. package/dist/{reloader-DzEO8kJr.mjs → reloader-B22UiNA2.mjs} +2 -4
  40. package/dist/{renderer-host-Cau9JK0v.mjs → renderer-host-DD16MXhI.mjs} +152 -43
  41. package/dist/{rpc-JfGv-Wuw.mjs → rpc-C4_NQmpT.mjs} +5 -4
  42. package/dist/{runtime-pCeVzj--.d.mts → runtime-BQWntcOb.d.mts} +85 -48
  43. package/dist/runtime.d.mts +2 -2
  44. package/dist/runtime.mjs +139 -83
  45. package/dist/{schema-Dl85YjXW.d.mts → schema-CjrMVk36.d.mts} +3 -3
  46. package/dist/schema.d.mts +1 -1
  47. package/dist/schema.mjs +1 -1
  48. package/dist/{server-y3PPbh3l.mjs → server-CZLMF8Dj.mjs} +1 -3
  49. package/dist/services/default.d.mts +3 -3
  50. package/dist/services/default.mjs +14 -13
  51. package/dist/services/index.d.mts +2 -280
  52. package/dist/services/index.mjs +8 -7
  53. package/dist/setup-gate.d.mts +1 -1
  54. package/dist/setup-gate.mjs +123 -24
  55. package/dist/{transform-CmFYPmt8.mjs → transform-BzrwkEdf.mjs} +22 -916
  56. package/dist/updater-BtB_Ki1r.mjs +1011 -0
  57. package/dist/{vite-plugins-Do7liKi_.mjs → vite-plugins-tt6KAtyE.mjs} +26 -25
  58. package/dist/vite.d.mts +3 -3
  59. package/dist/vite.mjs +1 -1
  60. package/dist/{window-o2NGUsIb.mjs → window-YFKvAM0l.mjs} +30 -16
  61. package/package.json +17 -4
  62. package/dist/build-config-C3a-o3_B.mjs +0 -23
  63. package/dist/dev-Dazhu66l.mjs +0 -85
  64. package/dist/registry-eX6e2oql.d.mts +0 -61
  65. package/dist/transforms-htxfTwsY.mjs +0 -47
  66. /package/dist/{config-DXRCDUxG.mjs → config-BK78JDRI.mjs} +0 -0
  67. /package/dist/{env-bootstrap-DW2hVhSO.d.mts → env-bootstrap-rTs8KR3-.d.mts} +0 -0
  68. /package/dist/{index-M_lSNBrq.d.mts → index-DeDxePAa.d.mts} +0 -0
  69. /package/dist/{mirror-sync-PDzxhf1w.mjs → mirror-sync-pYU6f3-c.mjs} +0 -0
  70. /package/dist/{monorepo-3avKJwzJ.mjs → monorepo-Dct-kkbQ.mjs} +0 -0
  71. /package/dist/{node-_8xShqxr.mjs → node-BhfLKYCi.mjs} +0 -0
  72. /package/dist/{setup-gate-Dcy8gGPJ.d.mts → setup-gate-BQq0QgZH.d.mts} +0 -0
@@ -1,280 +1,2 @@
1
- import { i as Service, t as CleanupReason } from "../runtime-pCeVzj--.mjs";
2
- import { c as EffectFieldNode, d as KyjuError, l as FieldNode, r as createRouter, t as Db } from "../index-M_lSNBrq.mjs";
3
- import { o as ResolvedDbRoot, s as ResolvedEvents } from "../registry-eX6e2oql.mjs";
4
- import http from "node:http";
5
- import { WebSocket, WebSocketServer } from "ws";
6
- import { ViteDevServer } from "vite";
7
- import * as Effect from "effect/Effect";
8
- import { BaseWindow, WebPreferences } from "electron";
9
- import * as _$stream from "stream";
10
-
11
- //#region src/services/server.d.ts
12
- type UpgradeHandler = (req: http.IncomingMessage, socket: _$stream.Duplex, head: Buffer) => boolean;
13
- declare class ServerService extends Service {
14
- static key: string;
15
- static deps: {};
16
- server: http.Server | null;
17
- wss: WebSocketServer | null;
18
- port: number;
19
- authToken: string;
20
- private upgradeHandlers;
21
- addUpgradeHandler(handler: UpgradeHandler): () => void;
22
- evaluate(): Promise<void>;
23
- private isAuthorizedUpgrade;
24
- }
25
- //#endregion
26
- //#region src/services/reloader.d.ts
27
- interface ReloaderEntry {
28
- id: string;
29
- root: string;
30
- url: string;
31
- port: number;
32
- viteServer: ViteDevServer;
33
- }
34
- declare class ReloaderService extends Service {
35
- static key: string;
36
- static deps: {};
37
- private servers;
38
- create(id: string, root: string, configFile?: string | false): Promise<ReloaderEntry>;
39
- get(id: string): ReloaderEntry | undefined;
40
- remove(id: string): Promise<void>;
41
- evaluate(): void;
42
- }
43
- //#endregion
44
- //#region src/services/http.d.ts
45
- type ConnectedCallback = (id: string, ws: WebSocket) => void;
46
- type DisconnectedCallback = (id: string) => void;
47
- type RequestHandler = (req: http.IncomingMessage, res: http.ServerResponse) => void;
48
- declare class HttpService extends Service {
49
- static key: string;
50
- static deps: {
51
- server: typeof ServerService;
52
- reloader: typeof ReloaderService;
53
- };
54
- ctx: {
55
- server: ServerService;
56
- reloader: ReloaderService;
57
- };
58
- connectedCallbacks: ConnectedCallback[];
59
- disconnectedCallbacks: DisconnectedCallback[];
60
- activeConnections: Map<string, WebSocket>;
61
- private requestHandlers;
62
- get port(): number;
63
- get authToken(): string;
64
- addRequestHandler(prefix: string, handler: RequestHandler): () => void;
65
- onConnected(cb: ConnectedCallback): () => void;
66
- onDisconnected(cb: DisconnectedCallback): () => void;
67
- evaluate(): void;
68
- }
69
- //#endregion
70
- //#region src/services/db.d.ts
71
- type EffectSectionProxy<S> = { [K in keyof S]: EffectFieldNode<S[K]> };
72
- type SectionProxy<S> = { [K in keyof S]: FieldNode<S[K]> };
73
- type Root = ResolvedDbRoot;
74
- type Plugin$1 = Root extends {
75
- plugin: infer P;
76
- } ? P : never;
77
- type SectionedEffectClient = {
78
- readRoot(): Root;
79
- update(fn: (root: Root) => void | Root): Effect.Effect<void, KyjuError>;
80
- createBlob(data: Uint8Array, hot?: boolean): Effect.Effect<string, KyjuError>;
81
- deleteBlob(blobId: string): Effect.Effect<void, KyjuError>;
82
- getBlobData(blobId: string): Effect.Effect<Uint8Array | null, KyjuError>;
83
- plugin: { [K in keyof Plugin$1]: EffectSectionProxy<Plugin$1[K]> };
84
- };
85
- type SectionedClient = {
86
- readRoot(): Root;
87
- update(fn: (root: Root) => void | Root): Promise<void>;
88
- createBlob(data: Uint8Array, hot?: boolean): Promise<string>;
89
- deleteBlob(blobId: string): Promise<void>;
90
- getBlobData(blobId: string): Promise<Uint8Array | null>;
91
- plugin: { [K in keyof Plugin$1]: SectionProxy<Plugin$1[K]> };
92
- };
93
- declare class DbService extends Service {
94
- static key: string;
95
- static deps: {
96
- http: typeof HttpService;
97
- };
98
- ctx: {
99
- http: HttpService;
100
- };
101
- db: Db | null;
102
- dbRouter: ReturnType<typeof createRouter> | null;
103
- private sectionsHash;
104
- private _dbPath;
105
- /**
106
- * Resolved DB path. Throws if accessed before `evaluate()` has run — the
107
- * service contract guarantees deps are evaluated before dependents, so any
108
- * access from a dependent service or RPC handler is safe.
109
- */
110
- get dbPath(): string;
111
- get client(): SectionedClient;
112
- get effectClient(): SectionedEffectClient;
113
- /**
114
- * Drain kyju's lagged-persistence queue. Safe to call anytime; idempotent
115
- * when nothing is pending. Used by service teardown (effect cleanup) so
116
- * shutdown / hot-reload don't lose in-memory writes.
117
- */
118
- flush(): Promise<void>;
119
- /**
120
- * Flush + release the kyju cross-process lock at `<dbPath>/.lock`.
121
- * Called on service teardown so a subsequent process can open the DB
122
- * without seeing a stale lock. Idempotent.
123
- */
124
- close(): Promise<void>;
125
- evaluate(): Promise<void>;
126
- }
127
- //#endregion
128
- //#region src/services/view-registry.d.ts
129
- interface ViewEntry {
130
- scope: string;
131
- url: string;
132
- port: number;
133
- ownsServer: boolean;
134
- meta?: {
135
- kind?: string;
136
- sidebar?: boolean;
137
- bottomPanel?: boolean;
138
- label?: string;
139
- };
140
- }
141
- declare class ViewRegistryService extends Service {
142
- static key: string;
143
- static deps: {
144
- reloader: typeof ReloaderService;
145
- db: typeof DbService;
146
- };
147
- ctx: {
148
- reloader: ReloaderService;
149
- db: DbService;
150
- };
151
- private views;
152
- private manifestIcons;
153
- register(scope: string, root: string, configFile?: string | false, meta?: {
154
- kind?: string;
155
- sidebar?: boolean;
156
- bottomPanel?: boolean;
157
- label?: string;
158
- }): Promise<ViewEntry>;
159
- registerAlias(scope: string, reloaderId: string, pathPrefix: string, meta?: {
160
- kind?: string;
161
- sidebar?: boolean;
162
- bottomPanel?: boolean;
163
- label?: string;
164
- }): ViewEntry;
165
- unregister(scope: string): Promise<void>;
166
- get(scope: string): ViewEntry | undefined;
167
- evaluate(): void;
168
- private loadManifestIcons;
169
- private syncToDb;
170
- }
171
- //#endregion
172
- //#region src/services/renderer-host.d.ts
173
- declare class RendererHostService extends Service {
174
- static key: string;
175
- static deps: {
176
- reloader: typeof ReloaderService;
177
- viewRegistry: typeof ViewRegistryService;
178
- };
179
- ctx: {
180
- reloader: ReloaderService;
181
- viewRegistry: ViewRegistryService;
182
- };
183
- url: string;
184
- port: number;
185
- evaluate(): Promise<void>;
186
- }
187
- //#endregion
188
- //#region src/services/base-window.d.ts
189
- declare const MAIN_WINDOW_ID = "main";
190
- type WindowBounds = {
191
- x: number;
192
- y: number;
193
- width: number;
194
- height: number;
195
- };
196
- declare class BaseWindowService extends Service {
197
- static key: string;
198
- static deps: {
199
- db: typeof DbService;
200
- };
201
- ctx: {
202
- db: DbService;
203
- };
204
- windows: Map<string, BaseWindow>;
205
- private get bootWindows();
206
- private set bootWindows(value);
207
- private getZenWidth;
208
- getWindowId(win: BaseWindow): string | undefined;
209
- createWindow(opts?: Partial<WindowBounds> & {
210
- windowId?: string;
211
- show?: boolean;
212
- }): {
213
- win: BaseWindow;
214
- windowId: string;
215
- };
216
- evaluate(): void;
217
- }
218
- //#endregion
219
- //#region src/services/rpc.d.ts
220
- type EmitProxy<T> = { [K in keyof T]: T[K] extends Record<string, any> ? EmitProxy<T[K]> & ((data: T[K]) => void) : (data: T[K]) => void };
221
- declare class RpcService extends Service {
222
- static key: string;
223
- static deps: {
224
- http: typeof HttpService;
225
- };
226
- ctx: {
227
- http: HttpService;
228
- };
229
- private _emit;
230
- get emit(): EmitProxy<ResolvedEvents>;
231
- evaluate(): void;
232
- }
233
- //#endregion
234
- //#region src/services/window.d.ts
235
- declare const WindowService_base: (abstract new () => {
236
- ctx: {
237
- baseWindow: BaseWindowService;
238
- viewRegistry: ViewRegistryService;
239
- http: HttpService;
240
- rendererHost: RendererHostService;
241
- };
242
- __setupCleanups: Map<string, (reason: CleanupReason) => void | Promise<void>>;
243
- evaluate(): void | Promise<void>;
244
- setup(key: string, fn: () => void | ((reason: CleanupReason) => void | Promise<void>)): void;
245
- trace<T>(_name: string, fn: () => T | Promise<T>, _meta?: Record<string, unknown>): Promise<T>;
246
- traceSync<T>(_name: string, fn: () => T, _meta?: Record<string, unknown>): T;
247
- __cleanupAllSetups(reason?: CleanupReason): Promise<void>;
248
- }) & {
249
- deps: Record<string, (string | (abstract new (...args: any[]) => Service)) | {
250
- __optional: true;
251
- ref: string | (abstract new (...args: any[]) => Service);
252
- }>;
253
- key: string;
254
- };
255
- declare class WindowService extends WindowService_base {
256
- static key: string;
257
- private mounted;
258
- evaluate(): void;
259
- openView(args: {
260
- scope: string;
261
- windowId?: string;
262
- query?: Record<string, string | number | boolean | null | undefined>;
263
- view?: {
264
- backgroundColor?: string;
265
- webPreferences?: WebPreferences;
266
- };
267
- }): Promise<{
268
- windowId: string;
269
- }>;
270
- focusWindow(windowId: string): Promise<{
271
- ok: true;
272
- }>;
273
- pickFiles(): Promise<string[] | null>;
274
- pickDirectory(): Promise<string | null>;
275
- openExternal(url: string): Promise<void>;
276
- openPath(filePath: string): Promise<void>;
277
- copyToClipboard(text: string): void;
278
- }
279
- //#endregion
280
- export { BaseWindowService, DbService, HttpService, MAIN_WINDOW_ID, ReloaderService, RendererHostService, RpcService, ServerService, ViewRegistryService, WindowService };
1
+ import { a as MAIN_WINDOW_ID, c as DbService, d as ServerService, i as BaseWindowService, l as HttpService, n as WindowService, o as RendererHostService, r as RpcService, s as ViewRegistryService, t as UpdaterService, u as ReloaderService } from "../index-CVF768Xs.mjs";
2
+ export { BaseWindowService, DbService, HttpService, MAIN_WINDOW_ID, ReloaderService, RendererHostService, RpcService, ServerService, UpdaterService, ViewRegistryService, WindowService };
@@ -1,7 +1,8 @@
1
- import { t as ServerService } from "../server-y3PPbh3l.mjs";
2
- import { t as ReloaderService } from "../reloader-DzEO8kJr.mjs";
3
- import { a as DbService, r as ViewRegistryService, s as HttpService, t as RendererHostService } from "../renderer-host-Cau9JK0v.mjs";
4
- import { n as MAIN_WINDOW_ID, t as BaseWindowService } from "../base-window-BbFRRhKP.mjs";
5
- import { t as RpcService } from "../rpc-JfGv-Wuw.mjs";
6
- import { t as WindowService } from "../window-o2NGUsIb.mjs";
7
- export { BaseWindowService, DbService, HttpService, MAIN_WINDOW_ID, ReloaderService, RendererHostService, RpcService, ServerService, ViewRegistryService, WindowService };
1
+ import { t as ServerService } from "../server-CZLMF8Dj.mjs";
2
+ import { t as ReloaderService } from "../reloader-B22UiNA2.mjs";
3
+ import { a as DbService, r as ViewRegistryService, s as HttpService, t as RendererHostService } from "../renderer-host-DD16MXhI.mjs";
4
+ import { n as MAIN_WINDOW_ID, t as BaseWindowService } from "../base-window-BxBZ2md_.mjs";
5
+ import { t as RpcService } from "../rpc-C4_NQmpT.mjs";
6
+ import { t as WindowService } from "../window-YFKvAM0l.mjs";
7
+ import { t as UpdaterService } from "../updater-BtB_Ki1r.mjs";
8
+ export { BaseWindowService, DbService, HttpService, MAIN_WINDOW_ID, ReloaderService, RendererHostService, RpcService, ServerService, UpdaterService, ViewRegistryService, WindowService };
@@ -1,2 +1,2 @@
1
- import { t as setupGate } from "./setup-gate-Dcy8gGPJ.mjs";
1
+ import { t as setupGate } from "./setup-gate-BQq0QgZH.mjs";
2
2
  export { setupGate };
@@ -6,10 +6,30 @@ import path from "node:path";
6
6
  import { pathToFileURL } from "node:url";
7
7
  import { register as register$1 } from "tsx/esm/api";
8
8
  //#region src/setup-gate.ts
9
+ /**
10
+ * this file is in a really hacky state needs to be fixed
11
+ */
9
12
  function registerLoader(specifier, opts) {
10
13
  return register(specifier, void 0, opts);
11
14
  }
12
15
  const verbose = process.env.ZENBU_VERBOSE === "1";
16
+ /**
17
+ * Suppress `@babel/generator`'s "[BABEL] Note: The code generator has
18
+ * deoptimised the styling of <file> as it exceeds the max of 500KB" notice.
19
+ * It's hardcoded as `console.error` in @babel/generator's printer with no
20
+ * opt-out (https://github.com/babel/babel/issues/7569). Vite's
21
+ * `@vitejs/plugin-react` runs Babel for fast-refresh on prebundled deps
22
+ * caches, hitting this for `react-dom_client.js` and similar large chunks
23
+ * on every dev boot. We filter the one specific message at the
24
+ * `console.error` boundary so the rest of console.error stays useful.
25
+ *
26
+ * Patched once at module init, before any Vite dev server has started.
27
+ */
28
+ const _origConsoleError = console.error.bind(console);
29
+ console.error = (...args) => {
30
+ if (args.length > 0 && typeof args[0] === "string" && args[0].startsWith("[BABEL] Note: The code generator has deoptimised")) return;
31
+ _origConsoleError(...args);
32
+ };
13
33
  function isPluginModule(value) {
14
34
  return typeof value === "object" && value !== null && typeof value.default === "function";
15
35
  }
@@ -81,30 +101,51 @@ function readSplashBgColor(splashPath) {
81
101
  return "#F4F4F4";
82
102
  }
83
103
  /**
84
- * Spawn the splash window before any plugin service evaluates. The window
85
- * is created HIDDEN, the splash HTML is loaded into a `WebContentsView`,
86
- * we wait for `loadFile` to resolve (= did-finish-load), then `win.show()`
87
- * by the time the OS composites the window, the splash pixels are already
88
- * there. No white-frame flash.
104
+ * Spawn the splash window as early as possible OR adopt the launcher's
105
+ * pre-existing installing window. The window is created with `show: true`
106
+ * and its `backgroundColor` set up front so the OS composites a colored
107
+ * frame on the next vsync no waiting for `did-finish-load` before any
108
+ * pixels reach the screen. Splash content paints into the WebContentsView
109
+ * a frame or two later, replacing the flat color.
110
+ *
111
+ * If the launcher already opened an installing window
112
+ * (`globalThis.__zenbu_boot_windows__` is non-empty), we reuse that
113
+ * BaseWindow and just swap its child WebContentsView from installing.html
114
+ * to splash.html. The titlebar / window chrome stays continuous.
89
115
  *
90
- * `BaseWindowService.evaluate()` adopts this window from
116
+ * `BaseWindowService.evaluate()` adopts the resulting window from
91
117
  * `globalThis.__zenbu_boot_windows__` instead of creating a new one, so
92
118
  * `WindowService.openView` can swap the splash content view for the
93
119
  * Vite-served renderer in-place when the renderer is ready.
94
120
  */
95
- async function spawnSplashWindow() {
96
- const slot = globalThis;
97
- const splashPath = slot.__zenbu_main_resolved_config__?.payload?.splashPath;
121
+ async function spawnSplashWindow(splashPath) {
98
122
  if (!splashPath || !fs.existsSync(splashPath)) {
99
123
  if (verbose) console.log("[setup-gate] no splash.html resolved; skipping splash window");
100
124
  return;
101
125
  }
126
+ const slot = globalThis;
102
127
  const electron = await import("electron");
103
128
  const backgroundColor = readSplashBgColor(splashPath);
104
- const win = new electron.BaseWindow({
129
+ const existing = slot.__zenbu_boot_windows__?.find((b) => b.windowId === "main");
130
+ let win;
131
+ if (existing) {
132
+ win = existing.win;
133
+ try {
134
+ win.setBackgroundColor(backgroundColor);
135
+ } catch {}
136
+ for (const child of [...win.contentView.children]) {
137
+ try {
138
+ win.contentView.removeChildView(child);
139
+ } catch {}
140
+ const wc = child.webContents;
141
+ try {
142
+ wc?.close();
143
+ } catch {}
144
+ }
145
+ if (verbose) console.log("[setup-gate] adopting existing installing window for splash");
146
+ } else win = new electron.BaseWindow({
105
147
  width: 1100,
106
148
  height: 750,
107
- show: false,
108
149
  titleBarStyle: "hidden",
109
150
  trafficLightPosition: {
110
151
  x: 14,
@@ -112,7 +153,7 @@ async function spawnSplashWindow() {
112
153
  },
113
154
  backgroundColor
114
155
  });
115
- const splashView = new electron.WebContentsView({ webPreferences: { paintWhenInitiallyHidden: true } });
156
+ const splashView = new electron.WebContentsView();
116
157
  win.contentView.addChildView(splashView);
117
158
  const bounds = win.getContentBounds();
118
159
  splashView.setBounds({
@@ -121,19 +162,38 @@ async function spawnSplashWindow() {
121
162
  width: bounds.width,
122
163
  height: bounds.height
123
164
  });
165
+ /**
166
+ * human note: this is a bad solution we should have an invariant verifying program state
167
+ * but its fine for now
168
+ */
124
169
  await Promise.race([splashView.webContents.loadFile(splashPath).catch(() => {}), new Promise((resolve) => setTimeout(resolve, 1500))]);
125
- win.show();
126
- const boot = {
127
- windowId: "main",
128
- win
129
- };
130
- slot.__zenbu_boot_windows__ = [...slot.__zenbu_boot_windows__ ?? [], boot];
170
+ if (!existing) {
171
+ const boot = {
172
+ windowId: "main",
173
+ win
174
+ };
175
+ slot.__zenbu_boot_windows__ = [...slot.__zenbu_boot_windows__ ?? [], boot];
176
+ }
131
177
  if (verbose) console.log("[setup-gate] splash window shown with", splashPath, "bg=", backgroundColor);
132
178
  }
133
- async function registerLoaders(tsconfig, projectRoot) {
179
+ /**
180
+ * Phase 1 of loader setup: register tsx + read the user's `zenbu.config.ts`.
181
+ * This is split out from the rest of the loader registration so the splash
182
+ * window can be spawned the moment `splashPath` is known, instead of
183
+ * waiting for advice + dynohot to register too. tsx must run with the
184
+ * project's tsconfig because user code (config + plugins) may rely on
185
+ * non-default TS settings (paths, jsx, etc.).
186
+ */
187
+ async function loadConfigPhase(tsconfig, projectRoot) {
134
188
  register$1({ tsconfig });
135
- const { loadConfig } = await import("./load-config-xMf2wxH8.mjs").then((n) => n.n);
189
+ const { loadConfig } = await import("./load-config-C4Oe2qZO.mjs").then((n) => n.n);
136
190
  const { resolved, pluginSourceFiles } = await loadConfig(projectRoot);
191
+ try {
192
+ const { linkProject } = await import("./link-glX89NV5.mjs").then((n) => n.n);
193
+ await linkProject(projectRoot, { quiet: true });
194
+ } catch (err) {
195
+ console.warn(`[setup-gate] zen link failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
196
+ }
137
197
  const loaderData = {
138
198
  payload: {
139
199
  plugins: resolved.plugins.map((p) => ({
@@ -147,20 +207,37 @@ async function registerLoaders(tsconfig, projectRoot) {
147
207
  icons: p.icons
148
208
  })),
149
209
  appEntrypoint: resolved.uiEntrypointPath,
150
- splashPath: resolved.splashPath
210
+ splashPath: resolved.splashPath,
211
+ installingPath: resolved.installingPath
151
212
  },
152
213
  pluginSourceFiles
153
214
  };
154
215
  globalThis.__zenbu_main_resolved_config__ = loaderData;
216
+ return loaderData;
217
+ }
218
+ /**
219
+ * Phase 2 of loader setup: register the zenbu loader, advice, and dynohot.
220
+ * Runs AFTER the splash window is on screen so the user sees pixels while
221
+ * advice patches install and dynohot's worker spawns. Node 22+ runs loader
222
+ * hooks in a worker thread; the resolved config crosses the boundary via
223
+ * `register()`'s `data` argument.
224
+ */
225
+ async function registerLoadersPhase(projectRoot, loaderData) {
155
226
  registerLoader(import.meta.resolve("@zenbujs/core/loaders/zenbu"), { data: loaderData });
156
227
  process.env.ZENBU_ADVICE_ROOT = projectRoot;
157
- await import("./node-_8xShqxr.mjs");
228
+ await import("./node-BhfLKYCi.mjs");
158
229
  const dynohot = await import(pathToFileURL(createRequire(import.meta.url).resolve("@zenbujs/hmr/register")).href);
159
230
  if (typeof dynohot.register === "function") dynohot.register({ ignore: /[/\\](?:node_modules|dist)[/\\]/ });
160
231
  }
161
232
  async function setupGate() {
233
+ const t0 = Date.now();
234
+ const ms = () => `+${Date.now() - t0}ms`;
235
+ const step = (label) => {
236
+ console.log(`[zenbu] ${label} (${ms()})`);
237
+ };
162
238
  const app = loadElectronApp();
163
239
  await app.whenReady();
240
+ step("electron ready");
164
241
  bootstrapEnv();
165
242
  let shuttingDown = false;
166
243
  const shutdown = async (exitCode = 0) => {
@@ -182,6 +259,10 @@ async function setupGate() {
182
259
  event.preventDefault();
183
260
  shutdown(0);
184
261
  });
262
+ /**
263
+ * i think we probably want to expose this in an editable form to the user
264
+ */
265
+ app.on("window-all-closed", () => {});
185
266
  process.on("SIGINT", () => void shutdown(0));
186
267
  process.on("SIGTERM", () => void shutdown(0));
187
268
  const autoQuitReadyMs = envMs("ZENBU_AUTO_QUIT_AFTER_READY_MS");
@@ -203,15 +284,31 @@ async function setupGate() {
203
284
  console.log("[setup-gate] project:", resolvedProjectDir);
204
285
  console.log("[setup-gate] config:", configPath);
205
286
  }
206
- await registerLoaders(tsconfig, projectRoot);
287
+ let loaderData;
288
+ try {
289
+ loaderData = await loadConfigPhase(tsconfig, projectRoot);
290
+ } catch (err) {
291
+ if (shuttingDown) return;
292
+ throw err;
293
+ }
294
+ if (shuttingDown) return;
295
+ step(`config loaded (${loaderData.payload.plugins.length} plugins)`);
296
+ try {
297
+ await spawnSplashWindow(loaderData.payload.splashPath);
298
+ } catch (err) {
299
+ if (shuttingDown) return;
300
+ throw err;
301
+ }
207
302
  if (shuttingDown) return;
303
+ step("splash shown");
208
304
  try {
209
- await spawnSplashWindow();
305
+ await registerLoadersPhase(projectRoot, loaderData);
210
306
  } catch (err) {
211
307
  if (shuttingDown) return;
212
308
  throw err;
213
309
  }
214
310
  if (shuttingDown) return;
311
+ step("loaders registered");
215
312
  try {
216
313
  const { defaultServices } = await import("./services/default.mjs");
217
314
  await defaultServices();
@@ -234,7 +331,9 @@ async function setupGate() {
234
331
  if (isPluginController(controller)) await controller.main();
235
332
  }
236
333
  if (shuttingDown) return;
334
+ step("plugins evaluated");
237
335
  await globalThis.__zenbu_service_runtime__?.whenIdle();
336
+ step("ready");
238
337
  const autoQuitMs = envMs("ZENBU_AUTO_QUIT_AFTER_IDLE_MS");
239
338
  if (autoQuitMs != null) {
240
339
  if (verbose) console.log("[setup-gate] auto-quit scheduled:", autoQuitMs);