@omega.js/desktop 0.52.0 → 0.53.0

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 (273) hide show
  1. package/README.md +17 -12
  2. package/dist/assets/css/core/_initialize.scss +1 -1
  3. package/dist/assets/js/core/app-shell.js +14 -15
  4. package/dist/assets/themes/_template/_theme.js +6 -6
  5. package/dist/assets/themes/base/_includes/global/sections/account.html +4 -4
  6. package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +5 -5
  7. package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +3 -3
  8. package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +1 -1
  9. package/dist/assets/themes/base/_layouts/frontend/pages/blog/tags/tag.html +1 -1
  10. package/dist/assets/themes/base/_layouts/frontend/pages/payment/confirmation.html +1 -1
  11. package/dist/assets/themes/base/_sections/marketing/newsletter-cta/section.js +2 -3
  12. package/dist/assets/themes/base/_sections/verts/unit/section.html +1 -1
  13. package/dist/assets/themes/base/_sections/verts/unit/section.js +3 -4
  14. package/dist/assets/themes/base/_theme.js +4 -3
  15. package/dist/assets/themes/bootstrap/_theme.js +2 -2
  16. package/dist/assets/themes/bootstrap/overrides/_links.scss +1 -1
  17. package/dist/assets/themes/classy/_theme.js +8 -6
  18. package/dist/assets/themes/classy/css/marketing/_sections.scss +1 -2
  19. package/dist/assets/themes/classy/js/hero-demo-form.js +3 -2
  20. package/dist/assets/themes/neobrutalism/_theme.js +5 -5
  21. package/dist/assets/themes/neobrutalism/js/pages/test/libraries/layers/index.js +1 -1
  22. package/dist/assets/themes/newsflash/_theme.js +6 -6
  23. package/dist/assets/themes/newsflash/js/pages/test/libraries/layers/index.js +1 -1
  24. package/dist/build.js +87 -111
  25. package/dist/cli.js +1 -1
  26. package/dist/commands/build.js +2 -2
  27. package/dist/commands/cdp/capture.js +2 -2
  28. package/dist/commands/cdp/quit.js +2 -2
  29. package/dist/commands/cdp/relaunch.js +2 -2
  30. package/dist/commands/cdp/theme.js +1 -1
  31. package/dist/commands/clean.js +2 -2
  32. package/dist/commands/deploy.js +4 -4
  33. package/dist/commands/finalize-release.js +4 -4
  34. package/dist/commands/install.js +3 -3
  35. package/dist/commands/launch.js +2 -2
  36. package/dist/commands/lib/deploy-precheck.js +3 -3
  37. package/dist/commands/lib/ensure-target.js +6 -6
  38. package/dist/commands/package.js +2 -2
  39. package/dist/commands/publish.js +2 -2
  40. package/dist/commands/release.js +3 -3
  41. package/dist/commands/runner.js +11 -11
  42. package/dist/commands/sign-windows.js +5 -5
  43. package/dist/commands/test.js +4 -4
  44. package/dist/commands/update.js +2 -2
  45. package/dist/commands/validate-certs.js +4 -4
  46. package/dist/commands/version.js +3 -3
  47. package/dist/defaults/AGENTS.md +17 -8
  48. package/dist/defaults/config/omega.json5 +7 -7
  49. package/dist/defaults/hooks/build/post.js +1 -1
  50. package/dist/defaults/hooks/build/pre.js +1 -1
  51. package/dist/defaults/hooks/deploy/pre.js +1 -1
  52. package/dist/defaults/hooks/release/post.js +1 -1
  53. package/dist/defaults/hooks/release/pre.js +1 -1
  54. package/dist/defaults/src/assets/js/components/about/index.js +3 -5
  55. package/dist/defaults/src/assets/js/components/main/index.js +3 -5
  56. package/dist/defaults/src/assets/js/components/settings/index.js +3 -5
  57. package/dist/defaults/src/integrations/context-menu/index.js +2 -2
  58. package/dist/defaults/src/integrations/menu/index.js +3 -3
  59. package/dist/defaults/src/integrations/tray/index.js +3 -3
  60. package/dist/defaults/src/main.js +3 -5
  61. package/dist/defaults/src/preload.js +3 -5
  62. package/dist/defaults/test/README.md +2 -2
  63. package/dist/gulp/main.js +9 -10
  64. package/dist/gulp/tasks/audit.js +7 -7
  65. package/dist/gulp/tasks/build-config.js +8 -8
  66. package/dist/gulp/tasks/bundle.js +16 -16
  67. package/dist/gulp/tasks/defaults.js +3 -3
  68. package/dist/gulp/tasks/distribute.js +2 -2
  69. package/dist/gulp/tasks/html.js +9 -9
  70. package/dist/gulp/tasks/package-quick.js +3 -3
  71. package/dist/gulp/tasks/package.js +3 -3
  72. package/dist/gulp/tasks/release.js +3 -3
  73. package/dist/gulp/tasks/sass.js +6 -6
  74. package/dist/gulp/tasks/serve.js +4 -4
  75. package/dist/hooks/notarize-artifacts.js +1 -1
  76. package/dist/hooks/notarize.js +1 -1
  77. package/dist/index.js +5 -8
  78. package/dist/lib/_environment-mixin.js +50 -0
  79. package/dist/lib/_lifecycle-mixin.js +45 -0
  80. package/dist/lib/analytics.js +33 -35
  81. package/dist/lib/app-state.js +14 -14
  82. package/dist/lib/auth-flow.js +18 -18
  83. package/dist/lib/auth-persistence.js +12 -12
  84. package/dist/lib/auth.js +421 -0
  85. package/dist/lib/auto-updater.js +49 -49
  86. package/dist/lib/context-menu.js +13 -13
  87. package/dist/lib/context.js +19 -19
  88. package/dist/lib/deep-link.js +34 -34
  89. package/dist/lib/fontawesome.js +5 -5
  90. package/dist/lib/ipc.js +4 -4
  91. package/dist/lib/menu.js +25 -25
  92. package/dist/lib/protocol.js +5 -5
  93. package/dist/lib/remote-config.js +22 -22
  94. package/dist/lib/remote-scripts.js +21 -21
  95. package/dist/lib/restart-manager/index.js +28 -28
  96. package/dist/lib/sign-helpers/exec-with-limit.js +1 -1
  97. package/dist/lib/sign-helpers/sign-events.js +1 -1
  98. package/dist/lib/startup.js +18 -13
  99. package/dist/lib/storage.js +10 -10
  100. package/dist/lib/templating.js +16 -16
  101. package/dist/lib/theme.js +10 -10
  102. package/dist/lib/tray.js +27 -27
  103. package/dist/lib/usage.js +11 -11
  104. package/dist/lib/window-manager.js +26 -26
  105. package/dist/main.js +398 -483
  106. package/dist/preload.js +236 -176
  107. package/dist/renderer.js +417 -391
  108. package/dist/test/fixtures/consumer-app/config/omega.json5 +1 -1
  109. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +8 -11
  110. package/dist/test/fixtures/consumer-app/src/main.js +5 -7
  111. package/dist/test/fixtures/consumer-app/src/preload.js +2 -2
  112. package/dist/test/harness/boot-entry.js +22 -20
  113. package/dist/test/harness/main-entry.js +31 -30
  114. package/dist/test/harness/renderer-entry.js +5 -5
  115. package/dist/test/harness/renderer-preload.js +137 -141
  116. package/dist/test/index.js +10 -10
  117. package/dist/test/runner.js +2 -2
  118. package/dist/test/runners/boot.js +7 -6
  119. package/dist/test/runners/electron.js +3 -2
  120. package/dist/test/runners/render-event.js +2 -2
  121. package/dist/test/suites/boot/consumer-app-boots.test.js +57 -26
  122. package/dist/test/suites/boot/restart-manager.test.js +2 -2
  123. package/dist/test/suites/boot/storage-bundled.test.js +5 -5
  124. package/dist/test/suites/boot/theme.test.js +13 -13
  125. package/dist/test/suites/build/audit.test.js +1 -1
  126. package/dist/test/suites/build/auth-persistence-resolve.test.js +6 -6
  127. package/dist/test/suites/build/boot-fixture.test.js +2 -2
  128. package/dist/test/suites/build/boot-runner-timeout.test.js +6 -5
  129. package/dist/test/suites/build/brand-scss.test.js +1 -1
  130. package/dist/test/suites/build/build-json-bake.test.js +1 -1
  131. package/dist/test/suites/build/cli.test.js +2 -2
  132. package/dist/test/suites/build/config-schema.test.js +5 -5
  133. package/dist/test/suites/build/defaults-scaffold.test.js +2 -2
  134. package/dist/test/suites/build/deploy-hook.test.js +3 -3
  135. package/dist/test/suites/build/ensure-target.test.js +2 -2
  136. package/dist/test/suites/build/env-delivery.test.js +2 -2
  137. package/dist/test/suites/build/esm-only-dependency.test.js +2 -2
  138. package/dist/test/suites/build/exports.test.js +9 -8
  139. package/dist/test/suites/build/get-config.test.js +4 -4
  140. package/dist/test/suites/build/manifest-deps.test.js +1 -1
  141. package/dist/test/suites/build/merge-line-files.test.js +1 -1
  142. package/dist/test/suites/build/omega-shell.test.js +34 -2
  143. package/dist/test/suites/build/omega.test.js +350 -0
  144. package/dist/test/suites/build/renderer-auth-bridge.test.js +211 -78
  145. package/dist/test/suites/build/runner.test.js +2 -2
  146. package/dist/test/suites/build/sentry.test.js +2 -2
  147. package/dist/test/suites/build/sign-windows-e2e.test.js +2 -2
  148. package/dist/test/suites/build/templating.test.js +3 -3
  149. package/dist/test/suites/build/test-stealth.test.js +7 -9
  150. package/dist/test/suites/build/url-helpers.test.js +55 -56
  151. package/dist/test/suites/build/validate-config.test.js +2 -2
  152. package/dist/test/suites/build/wave5-pins.test.js +2 -2
  153. package/dist/test/suites/main/analytics.test.js +59 -59
  154. package/dist/test/suites/main/app-state.test.js +66 -66
  155. package/dist/test/suites/main/auth-flow.test.js +41 -41
  156. package/dist/test/suites/main/auth-persistence.test.js +54 -43
  157. package/dist/test/suites/main/{client-bridge.integration.test.js → auth.integration.test.js} +10 -9
  158. package/dist/test/suites/main/auth.test.js +336 -0
  159. package/dist/test/suites/main/auto-updater.test.js +134 -134
  160. package/dist/test/suites/main/boot-sequence.test.js +37 -49
  161. package/dist/test/suites/main/context-menu.test.js +51 -50
  162. package/dist/test/suites/main/context.test.js +25 -25
  163. package/dist/test/suites/main/deep-link.test.js +74 -74
  164. package/dist/test/suites/main/fontawesome.test.js +27 -27
  165. package/dist/test/suites/main/ipc.test.js +35 -35
  166. package/dist/test/suites/main/menu.test.js +101 -100
  167. package/dist/test/suites/main/protocol.test.js +19 -19
  168. package/dist/test/suites/main/remote-config.test.js +63 -63
  169. package/dist/test/suites/main/remote-scripts.test.js +103 -103
  170. package/dist/test/suites/main/request.test.js +71 -0
  171. package/dist/test/suites/main/restart-manager.test.js +33 -33
  172. package/dist/test/suites/main/startup-paths-and-ua.test.js +4 -4
  173. package/dist/test/suites/main/startup.test.js +26 -26
  174. package/dist/test/suites/main/stealth-window.test.js +1 -1
  175. package/dist/test/suites/main/storage.test.js +25 -25
  176. package/dist/test/suites/main/theme.test.js +34 -34
  177. package/dist/test/suites/main/tray.test.js +79 -79
  178. package/dist/test/suites/main/url-helpers.test.js +133 -133
  179. package/dist/test/suites/main/usage.test.js +25 -25
  180. package/dist/test/suites/main/window-bounds.test.js +27 -27
  181. package/dist/test/suites/main/window-manager.test.js +44 -44
  182. package/dist/test/suites/renderer/analytics-bridge.test.js +5 -5
  183. package/dist/test/suites/renderer/cross-context-helpers.test.js +37 -31
  184. package/dist/test/suites/renderer/round-trip.test.js +3 -3
  185. package/dist/test/suites/renderer/tooltips.test.js +15 -15
  186. package/dist/test/suites/renderer/{window-em-surface.test.js → window-desktop-surface.test.js} +27 -7
  187. package/dist/test/utils/extended-mode-warning.js +1 -1
  188. package/dist/utils/boot-harness.js +56 -0
  189. package/dist/utils/mode-helpers.js +2 -15
  190. package/dist/utils/ship-keys.js +3 -3
  191. package/dist/utils/signing-status.js +51 -0
  192. package/dist/utils/test-events.js +7 -0
  193. package/dist/utils/test-stealth.js +6 -6
  194. package/dist/utils/url-helpers.js +52 -42
  195. package/dist/utils/user-agent.js +44 -0
  196. package/dist/vendor/account/engine.js +3 -3
  197. package/dist/vendor/account/index.js +14 -45
  198. package/dist/vendor/account/resolve-account.js +44 -0
  199. package/dist/vendor/account/schema.js +1 -1
  200. package/dist/vendor/account/user.js +99 -0
  201. package/dist/vendor/config/client-config.js +1 -1
  202. package/dist/vendor/config/environment.js +11 -30
  203. package/dist/vendor/config/index.js +8 -11
  204. package/dist/vendor/config/load.js +1 -2
  205. package/dist/vendor/config/platforms.js +1 -1
  206. package/dist/vendor/config/retired-keys.js +2 -2
  207. package/dist/vendor/config/schema.js +5 -8
  208. package/dist/vendor/config/site-global.js +2 -3
  209. package/dist/vendor/config/validate.js +1 -2
  210. package/dist/vendor/config/winback.js +1 -1
  211. package/dist/vendor/devkit/actions-secrets.js +1 -1
  212. package/dist/vendor/devkit/attach-log-file.js +1 -1
  213. package/dist/vendor/devkit/build-json.js +1 -1
  214. package/dist/vendor/devkit/cli-router.js +3 -4
  215. package/dist/vendor/devkit/defaults-engine.js +9 -11
  216. package/dist/vendor/devkit/local.js +2 -0
  217. package/dist/vendor/devkit/merge-line-files.js +2 -3
  218. package/dist/vendor/devkit/test/runner-core.js +6 -6
  219. package/dist/vendor/monitoring/env.js +2 -2
  220. package/dist/vendor/monitoring/index.js +1 -1
  221. package/dist/vendor/monitoring/main.js +1 -1
  222. package/dist/vendor/monitoring/preload.js +1 -1
  223. package/dist/vendor/monitoring/renderer.js +1 -1
  224. package/docs/analytics.md +8 -8
  225. package/docs/app-state.md +19 -19
  226. package/docs/audit.md +4 -4
  227. package/docs/{client-bridge.md → auth.md} +88 -73
  228. package/docs/auto-updater.md +8 -8
  229. package/docs/boot-sequence.md +11 -6
  230. package/docs/build-system.md +1 -1
  231. package/docs/cdp-debugging.md +1 -1
  232. package/docs/common-mistakes.md +7 -7
  233. package/docs/config-schema.md +1 -1
  234. package/docs/context-menu.md +13 -13
  235. package/docs/context.md +11 -11
  236. package/docs/css.md +3 -9
  237. package/docs/deep-link.md +25 -25
  238. package/docs/environment-detection.md +16 -16
  239. package/docs/fontawesome.md +7 -5
  240. package/docs/hooks.md +8 -8
  241. package/docs/index.md +38 -27
  242. package/docs/ipc.md +10 -10
  243. package/docs/lib-modules.md +9 -9
  244. package/docs/logging.md +12 -14
  245. package/docs/menu.md +18 -18
  246. package/docs/remote-config.md +9 -9
  247. package/docs/remote-scripts.md +12 -12
  248. package/docs/restart-manager.md +8 -8
  249. package/docs/sentry.md +4 -4
  250. package/docs/shared/analytics.md +1 -1
  251. package/docs/shared/breaking-changes.md +79 -13
  252. package/docs/shared/config.md +17 -21
  253. package/docs/shared/logging.md +1 -1
  254. package/docs/shared/monitoring.md +5 -5
  255. package/docs/shared/testing.md +2 -2
  256. package/docs/shared/theming.md +1 -1
  257. package/docs/shared/translation.md +19 -10
  258. package/docs/startup.md +16 -16
  259. package/docs/storage.md +11 -11
  260. package/docs/templating.md +3 -3
  261. package/docs/test-boot-layer.md +12 -12
  262. package/docs/test-framework.md +17 -17
  263. package/docs/themes.md +7 -7
  264. package/docs/tooltips.md +2 -2
  265. package/docs/tray.md +31 -31
  266. package/docs/usage.md +7 -7
  267. package/docs/verts.md +1 -1
  268. package/docs/windows.md +22 -22
  269. package/package.json +3 -4
  270. package/dist/lib/client-bridge.js +0 -374
  271. package/dist/lib/logger.js +0 -4
  272. package/dist/test/suites/build/manager.test.js +0 -213
  273. package/dist/test/suites/main/client-bridge.test.js +0 -262
@@ -1,6 +1,6 @@
1
- # Client Bridge — Auth State Sync
1
+ # Auth: `omega.auth` and the state sync
2
2
 
3
- @omega.js/desktop keeps Firebase auth state in sync across all processes (main + every renderer window). The pattern mirrors BXM's background/foreground architecture: **main is the source of truth**, renderers reflect.
3
+ @omega.js/desktop keeps Firebase auth state in sync across all processes (main + every renderer window). **Main is the source of truth**, renderers reflect: the same pattern as @omega.js/extension's background and page contexts. In main the lib is `omega.auth` ([src/lib/auth.js](../src/lib/auth.js)); in a renderer `omega.auth` is @omega.js/client's Auth module. Both sides hold the account as one `User` (`@omega.js/account`), never null.
4
4
 
5
5
  ## Why this exists
6
6
 
@@ -16,7 +16,7 @@ The bridge handles all three.
16
16
 
17
17
  ```
18
18
  ┌─────────────────────────────────────────────────────────────┐
19
- │ MAIN (client-bridge.js) │
19
+ │ MAIN (lib/auth.js, omega.auth) │
20
20
  │ - Owns Firebase Auth instance ("omega-auth" app) │
21
21
  │ - Source of truth for auth state │
22
22
  │ - Listens for desktop:auth:* IPC from renderers │
@@ -32,13 +32,13 @@ The bridge handles all three.
32
32
 
33
33
  ### Auth flow: deep-link → all processes signed in
34
34
 
35
- **Consumers never collect credentials.** There is no login form to build — call `manager.openAuthFlow()` (documented in `lib/auth-flow.js`), which opens the brand website's sign-in page in the user's browser and receives the result through the deep link below.
35
+ **Consumers never collect credentials.** There is no login form to build: call `omega.openAuthFlow()` (documented in `lib/auth-flow.js`), which opens the brand website's sign-in page in the user's browser and receives the result through the deep link below.
36
36
 
37
- 1. User signs in on the website. Web-manager generates a custom token. Website opens `myapp://auth/token?token=XYZ` (deep link).
38
- 2. @omega.js/desktop's deep-link `auth/token` built-in fires `manager.omega.handleAuthToken(token)`.
37
+ 1. User signs in on the website. The website's token page mints a custom token and opens `myapp://auth/token?authToken=XYZ` (deep link).
38
+ 2. @omega.js/desktop's deep-link `auth/token` built-in calls `omega.auth.handleToken(token)`.
39
39
  3. Main calls `signInWithCustomToken(auth, token)` against its own Firebase Auth → main is now signed in.
40
40
  4. Main broadcasts `desktop:auth:sign-in-with-token` IPC with the same token to all renderer windows.
41
- 5. Each renderer receives the broadcast, calls `omega.auth().signInWithCustomToken(token)` against its own (@omega.js/client-managed) Firebase Auth → all renderers signed in with the same user.
41
+ 5. Each renderer receives the broadcast, calls `omega.auth.signInWithCustomToken(token)` against its own (@omega.js/client-managed) Firebase Auth → all renderers signed in with the same user.
42
42
  6. Tokens are NOT stored — they expire in 1 hour. Auth state persists via Firebase's built-in IndexedDB persistence.
43
43
 
44
44
  ### Auth flow: renderer load → sync with main
@@ -53,68 +53,79 @@ When a renderer window opens (cold or warm), it asks main for the current state:
53
53
 
54
54
  ### Sign-out flow
55
55
 
56
- Any renderer (or main code) calls `manager.omega.signOut()`:
56
+ Main code calls `omega.auth.signOut()`; a renderer calls `omega.signOut()`, which a click on any `.omega-signout` element runs (@omega.js/client's trigger: confirm, then `desktop:auth:sign-out` to main). Either way:
57
57
 
58
58
  1. Main signs out its own Firebase.
59
59
  2. Main broadcasts `desktop:auth:sign-out` IPC to all renderers.
60
- 3. Each renderer signs out its own Firebase.
60
+ 3. Each renderer signs out its own Firebase, on that broadcast alone (the clicked window included), so nothing signs out twice.
61
61
 
62
62
  ## Public API
63
63
 
64
- ### Main process (`manager.omega`)
64
+ ### Main process (`omega.auth`)
65
65
 
66
66
  ```js
67
- // Sign in via a custom token (called automatically by the auth/token deep-link route).
68
- await manager.omega.handleAuthToken(token);
67
+ // The account, always a `User`: signed out until a renderer pushes the account
68
+ // document of the uid main's session holds, and signed out again the moment that
69
+ // session ends.
70
+ omega.auth.user;
71
+ // → User: .authenticated .uid .email .plan .active .trialing .cancelling .everPaid,
72
+ // .roles, .subscription, ..., .profile { displayName, photoURL, emailVerified }
73
+
74
+ // Subscribe to state changes (e.g. to refresh tray/menu items). Called with
75
+ // `{ user }`, plus a catch-up with the current state after listen() returns.
76
+ const off = omega.auth.listen(({ user }) => {
77
+ omega.tray.refresh();
78
+ omega.menu.refresh();
79
+ });
80
+ off(); // unsubscribe
69
81
 
70
- // Read the currently signed-in user (snapshot, no sensitive fields).
71
- manager.omega.getCurrentUser();
72
- // → { uid, email, displayName, photoURL, emailVerified } | null
82
+ // Sign in via a custom token (called automatically by the auth/token deep-link route).
83
+ await omega.auth.handleToken(token);
73
84
 
74
85
  // Fresh Firebase ID token for calling authenticated backend routes from main
75
86
  // (send as `Authorization: Bearer <token>`). null when signed out.
76
- await manager.omega.getIdToken();
87
+ await omega.auth.getIdToken();
77
88
 
78
- // Subscribe to auth state changes (e.g. to refresh tray/menu items).
79
- const off = manager.omega.onAuthChange((user) => {
80
- manager.tray.refresh();
81
- manager.menu.refresh();
82
- });
83
- off(); // unsubscribe
89
+ // Or let the instance fetch: @omega.js/client's request, built once on main,
90
+ // attaches that token itself (the renderer's and the extension's shape).
91
+ await omega.request('/notes', { method: 'POST', body: { text: 'hi' } });
84
92
 
85
- // Sign out from any process.
86
- await manager.omega.signOut();
87
-
88
- // The renderer-resolved subscription — THE main-side plan source. Renderers run
89
- // @omega.js/client's full auth cycle (Firestore account fetch → resolveSubscription)
90
- // and push the result to main (desktop:auth:account-resolved, uid-guarded); main can't
91
- // run Firestore itself. null until a renderer has resolved.
92
- manager.omega.getResolvedPlan();
93
- // → { plan, active, trialing, cancelling } | null
94
- manager.omega.getResolvedRoles();
95
- // → { admin, betaTester, ... } | null
93
+ // Sign main out and broadcast the sign-out to every renderer.
94
+ await omega.auth.signOut();
96
95
  ```
97
96
 
98
- ### Renderer process (the @omega.js/desktop Manager you `initialize()`)
97
+ Main can't run Firestore, so the account crosses from the renderers: each renderer runs @omega.js/client's full auth cycle and pushes the WHOLE stored document (`desktop:auth:account-resolved`, `{ uid, document, identity }`, uid-guarded), and main builds its `omega.auth.user` from it. `user.plan`, `user.active` and `user.roles.admin` read the same in main as in the renderer.
98
+
99
+ ### Renderer process (the renderer's `omega`)
99
100
 
100
101
  ```js
101
- // Read the user from main (always returns main's authoritative state).
102
- const user = await renderer.getMainUser();
102
+ // The renderer's own account, from @omega.js/client
103
+ omega.auth.user; // a User, the same class main holds
104
+ omega.auth.listen(({ user, denied }) => { });
105
+
106
+ // Main's authoritative account: { uid, document, identity }
107
+ const main = await omega.getMainUser();
103
108
 
104
- // Sign out (goes through main; broadcasts to all other renderers).
105
- await renderer.signOut();
109
+ // Sign out through main (broadcasts to every renderer). omega.auth.signOut()
110
+ // signs out this renderer alone.
111
+ await omega.signOut();
106
112
  ```
107
113
 
108
- The renderer's `Manager.initialize()` automatically:
114
+ The renderer's `omega.initialize()` automatically:
109
115
  - Boots @omega.js/client (so renderer-side Firebase is available).
110
116
  - Wires the auth bridge (`desktop:auth:sync-request` on load + listens for broadcasts).
111
- - Runs @omega.js/client's **full auth cycle** (`auth().listen()`): waits for auth to settle,
112
- fetches the Firestore account, resolves the subscription, and auto-populates the
113
- **`data-omega-bind` bindings** — so @omega.js/desktop app views can use UJM/BXM-style reactive HTML
114
- (`@show auth.user`, `@text auth.account.plan.id`, `@show auth.account.plan.id === 'premium'`,
115
- see @omega.js/client's docs/bindings.md). Each settle pushes `{ resolved, roles }` to main
116
- (`desktop:auth:account-resolved`) and re-offers it whenever main announces a state change,
117
- so a renderer that resolved before main signed in still delivers.
117
+ - Registers the auth click triggers the extension's pages carry, so a view signs in with markup
118
+ alone: `.omega-signin` runs main's `omega.openAuthFlow()` (`desktop:auth:open-flow`),
119
+ `.omega-account` opens the website's `/account` page in the user's browser
120
+ (`desktop:auth:open-account`), and `.omega-signout` (@omega.js/client's) runs `omega.signOut()`,
121
+ signing the whole app out through main (`desktop:auth:sign-out`).
122
+ - Runs @omega.js/client's **full auth cycle** (`omega.auth.listen()`): waits for auth to settle,
123
+ fetches the Firestore account, lands one `User`, and auto-populates the
124
+ **`data-omega-bind` bindings**, so @omega.js/desktop app views use the same reactive HTML as
125
+ every OMEGA browser surface (`@show auth.user.authenticated`, `@text auth.user.plan`,
126
+ `@show auth.user.plan === 'premium'`, see @omega.js/client's docs/bindings.md). Each signed-in
127
+ state pushes its account to main (`desktop:auth:account-resolved`) and re-offers it whenever
128
+ main announces a state change, so a renderer that resolved before main signed in still delivers.
118
129
 
119
130
  You don't write any of this — it just works.
120
131
 
@@ -148,7 +159,7 @@ alike: the harness never signs a real user in, so it never asks the OS keychain
148
159
 
149
160
  Storage only — distribution across processes stays the IPC sync protocol above.
150
161
  The session restores at boot (offline included: no network round trip), so a restart
151
- keeps the user signed in; renderers then re-resolve the account and re-push the plan.
162
+ keeps the user signed in; renderers then re-resolve the account and re-push it.
152
163
 
153
164
  ## Config
154
165
 
@@ -166,11 +177,11 @@ keeps the user signed in; renderers then re-resolve the account and re-push the
166
177
  }
167
178
  ```
168
179
 
169
- If `cloud.config` is empty/missing, the bridge logs a warning and runs in no-op mode (everything returns harmless defaults).
180
+ If `cloud.config` is empty/missing, `omega.auth` logs a warning and runs in no-op mode (`user` stays the signed-out `User`, everything else returns harmless defaults).
170
181
 
171
182
  ## Firebase (bundled)
172
183
 
173
- Firebase is **bundled from @omega.js/desktop's module context** (@omega.js/client owns it in @omega.js/desktop's dependency tree) — the same treatment `json5` gets in main. It was previously runtime-resolved, which silently failed in every symlinked dev app (see CHANGELOG 1.11.1).
184
+ Firebase is **bundled from @omega.js/desktop's module context** (@omega.js/client owns it in @omega.js/desktop's dependency tree), the same treatment `json5` gets in main.
174
185
 
175
186
  If you're building a no-auth Electron app, just leave `cloud.config` empty — the bridge is a clean no-op.
176
187
 
@@ -181,24 +192,24 @@ In a TESTING run (`OMEGA_ENVIRONMENT=testing`) the bridge connects its auth inst
181
192
  ### Refresh tray when auth state changes
182
193
 
183
194
  ```js
184
- // In src/tray/index.js or wherever you have access to manager:
185
- manager.omega.onAuthChange((user) => {
186
- manager.tray.refresh(); // re-evaluates dynamic labels
195
+ // In src/integrations/tray/index.js, or anywhere main code reaches omega:
196
+ omega.auth.listen(() => {
197
+ omega.tray.refresh(); // re-evaluates dynamic labels
187
198
  });
188
199
  ```
189
200
 
190
201
  ```js
191
- // In src/tray/index.js:
202
+ // In src/integrations/tray/index.js:
192
203
  tray.item({
193
204
  label: () => {
194
- const user = manager.omega.getCurrentUser();
195
- return user ? `Signed in as ${user.email}` : 'Sign in';
205
+ const { user } = omega.auth;
206
+ return user.authenticated ? `Signed in as ${user.email}` : 'Sign in';
196
207
  },
197
208
  click: () => {
198
- if (manager.omega.getCurrentUser()) {
199
- manager.omega.signOut();
209
+ if (omega.auth.user.authenticated) {
210
+ omega.auth.signOut();
200
211
  } else {
201
- require('electron').shell.openExternal(`${manager.config.brand.url}/sign-in?desktop=true`);
212
+ omega.openAuthFlow();
202
213
  }
203
214
  },
204
215
  });
@@ -207,14 +218,14 @@ tray.item({
207
218
  ### Gate a deep-link route on auth
208
219
 
209
220
  ```js
210
- manager.deepLink.on('user/profile/:id', (ctx) => {
211
- if (!manager.omega.getCurrentUser()) {
212
- require('electron').shell.openExternal(`${manager.config.brand.url}/sign-in?return=profile/${ctx.params.id}`);
221
+ omega.deepLink.on('user/profile/:id', (ctx) => {
222
+ if (!omega.auth.user.authenticated) {
223
+ omega.openAuthFlow();
213
224
  ctx.handled = true;
214
225
  return;
215
226
  }
216
- manager.windows.show('main');
217
- manager.windows.get('main').webContents.send('navigate', { to: `/profile/${ctx.params.id}` });
227
+ omega.windows.show('main');
228
+ omega.windows.get('main').webContents.send('navigate', { to: `/profile/${ctx.params.id}` });
218
229
  });
219
230
  ```
220
231
 
@@ -224,7 +235,7 @@ manager.deepLink.on('user/profile/:id', (ctx) => {
224
235
  <button id="signout">Sign out</button>
225
236
  <script>
226
237
  document.getElementById('signout').addEventListener('click', async () => {
227
- await renderer.signOut(); // goes through main, propagates everywhere
238
+ await omega.signOut(); // goes through main, propagates everywhere
228
239
  });
229
240
  </script>
230
241
  ```
@@ -234,8 +245,12 @@ manager.deepLink.on('user/profile/:id', (ctx) => {
234
245
  | Channel | Direction | Payload | Description |
235
246
  |---|---|---|---|
236
247
  | `desktop:auth:sync-request` | renderer → main | `{ contextUid }` | "I'm at this UID, are we in sync?" |
237
- | `desktop:auth:sign-out` | renderer → main | (none) | "Sign me (and everyone) out." |
238
- | `desktop:auth:get-user` | renderer → main | (none) | Read main's current user. |
248
+ | `desktop:auth:sign-out` | renderer → main | (none) | "Sign me (and everyone) out." The `.omega-signout` trigger and `omega.signOut()`; main runs `omega.auth.signOut()`. |
249
+ | `desktop:auth:get-user` | renderer → main | (none) | Read main's account: `{ uid, document, identity }`. |
250
+ | `desktop:auth:account-resolved` | renderer → main | `{ uid, document, identity }` | The account this renderer's client resolved; main builds `omega.auth.user` from it (uid-guarded). |
251
+ | `desktop:auth:open-flow` | renderer → main | (none) | The `.omega-signin` trigger: main runs `omega.openAuthFlow()`. |
252
+ | `desktop:auth:open-account` | renderer → main | (none) | The `.omega-account` trigger: main opens `<getWebsiteUrl()>/account` in the user's browser. |
253
+ | `desktop:auth:plan-changed` | main → all renderers | `{ document }` | Main landed a new account. |
239
254
  | `desktop:auth:sign-in-with-token` | main → all renderers | `{ token }` | "Sign in with this custom token now." |
240
255
  | `desktop:auth:sign-out` | main → all renderers | `{}` | "Sign out now." |
241
256
  | `desktop:auth:state-changed` | main → all renderers | `{ uid, email, ... } \| null` | Auth state changed (informational). |
@@ -244,7 +259,7 @@ manager.deepLink.on('user/profile/:id', (ctx) => {
244
259
 
245
260
  ### Unit tests (always run)
246
261
 
247
- `client-bridge.test.js` covers the dispatch logic, IPC handler shape, sync-request comparison, and the `auth/token` deep-link integration — all without hitting Firebase.
262
+ `auth.test.js` covers the dispatch logic, IPC handler shape, sync-request comparison, and the `auth/token` deep-link integration, all without hitting Firebase.
248
263
 
249
264
  ### The real-surface e2e lane (monorepo root)
250
265
 
@@ -252,18 +267,18 @@ manager.deepLink.on('user/profile/:id', (ctx) => {
252
267
 
253
268
  ### Extended tests (skip without the opt-in)
254
269
 
255
- `client-bridge.integration.test.js` talks to REAL Firebase, so it is gated behind extended mode (the cross-framework `TEST_EXTENDED_MODE` opt-in; see [test-framework.md](test-framework.md#extended-vs-normal-mode)):
270
+ `auth.integration.test.js` talks to REAL Firebase, so it is gated behind extended mode (the cross-framework `TEST_EXTENDED_MODE` opt-in; see [test-framework.md](test-framework.md#extended-vs-normal-mode)):
256
271
 
257
272
  ```bash
258
273
  npx omega test --extended # or: TEST_EXTENDED_MODE=true npx omega test
259
274
  ```
260
275
 
261
- It asks for NO credential of its own ([#819](https://github.com/Omega-JS-Stack/omega/issues/819), Ian 2026-09-13): the service-account path and the test uid it used to mint a custom token from are retired env keys now. The SIGN-IN proof belongs to [#904](https://github.com/Omega-JS-Stack/omega/issues/904), which signs desktop in as a persona the backend emulator seeds, the same mechanism web and the extension use. Without the opt-in the suite skips cleanly with a reason, so CI stays green.
276
+ It asks for NO credential of its own. The SIGN-IN proof belongs to [#904](https://github.com/Omega-JS-Stack/omega/issues/904), which signs desktop in as a persona the backend emulator seeds, the same mechanism web and the extension use. Without the opt-in the suite skips cleanly with a reason, so CI stays green.
262
277
 
263
278
  ## Implementation notes
264
279
 
265
280
  - Firebase app name in main is `omega-auth` (avoids clashes if a consumer's main code also wants its own Firebase instance).
266
- - The bridge does NOT persist user info to @omega.js/desktop storage — Firebase's IndexedDB persistence handles session restoration. Matches BXM.
267
- - Custom tokens are NEVER stored. Renderers receive them once via broadcast, sign in, discard. Fresh tokens are minted on demand from `POST /omega/user/token` via @omega.js/client's shared request layer (`createRequest` from `@omega.js/client/modules/request.js`) — same code path as the extension background's token sync.
268
- - `manager.getApiUrl()` returns the dev or prod URL, so the bridge automatically hits the right backend. Available across all four Manager contexts (main / renderer / preload / build) via the shared `src/utils/url-helpers.js` module — same code path everywhere. See the Cross-context helpers section of the framework guide ([docs/desktop/index.md](../../../docs/desktop/index.md)).
269
- - All sensitive Firebase user fields (`stsTokenManager`, `providerData`, etc.) are stripped before sending over IPC. Only `{uid, email, displayName, photoURL, emailVerified}` cross the bridge.
281
+ - The bridge does NOT persist user info to @omega.js/desktop storage: the session vault (above) and the renderers' IndexedDB persistence handle session restoration, the same as @omega.js/extension.
282
+ - Custom tokens are NEVER stored. Renderers receive them once via broadcast, sign in, discard. Fresh tokens are minted on demand from `POST /omega/user/token` through main's `omega.request()` (@omega.js/client's `createRequest`, built once on the instance), the same code path as the extension background's token sync.
283
+ - `omega.getApiUrl()` returns the dev or prod URL, so the bridge automatically hits the right backend. Available on all three process instances (main / renderer / preload) via the shared `src/utils/url-helpers.js` module, the same code path everywhere. See the Cross-context helpers section of the framework guide ([docs/desktop/index.md](../../../docs/desktop/index.md)).
284
+ - All sensitive Firebase user fields (`stsTokenManager`, `providerData`, etc.) are stripped before sending over IPC. Only the identity `{uid, email, displayName, photoURL, emailVerified}` and the stored account document cross the bridge.
@@ -10,7 +10,7 @@ Wraps `electron-updater` with three triggers: startup check, periodic check, and
10
10
  | **Feed check** | Every `feedCheckIntervalMs` (default 1h) | HTTP poll of the release feed; also re-evaluates the 30-day gate each tick. |
11
11
  | **Idle evaluation** | Every `idleEvalIntervalMs` (default 60s) | Cheap in-process check: install a downloaded update once the user has been idle long enough. |
12
12
  | **30-day gate** | When a download lands + every feed tick | If a pending update was downloaded ≥ `maxAgeMs` ago (default 30 days), force `quitAndInstall()`. A pending update carried from a prior session keeps its original `downloadedAt`, so the gate trips as soon as the startup check re-downloads it. |
13
- | **Manual check** | `manager.autoUpdater.checkNow()` (main) or `window.desktop.autoUpdater.checkNow()` (renderer) | Same as a periodic check but `userInitiated: true`. |
13
+ | **Manual check** | `omega.autoUpdater.checkNow()` (main) or `window.desktop.autoUpdater.checkNow()` (renderer) | Same as a periodic check but `userInitiated: true`. |
14
14
 
15
15
  ## State machine
16
16
 
@@ -93,7 +93,7 @@ Subtle: `_userInitiated` is only flipped AFTER the `_readyToCheck` guard. So a u
93
93
  Consumers can force-bump the activity timestamp from anywhere:
94
94
 
95
95
  ```js
96
- manager.autoUpdater.markActive();
96
+ omega.autoUpdater.markActive();
97
97
  ```
98
98
 
99
99
  Call this from app-specific signals the framework can't see — e.g. just received an auth event, finished a long renderer task, finished a backend sync. Use sparingly; the built-in renderer mouse/keyboard/focus signals cover almost everything.
@@ -110,7 +110,7 @@ Long enough that an actively-used app won't surprise-quit mid-task. Short enough
110
110
 
111
111
  ### Test mode behavior
112
112
 
113
- When `manager.isTesting() === true` (the one input: `OMEGA_ENVIRONMENT=testing`), the auto-updater swaps in test-friendly defaults so a real download → idle wait → install can complete in seconds instead of minutes:
113
+ When `omega.isTesting() === true` (the one input: `OMEGA_ENVIRONMENT=testing`), the auto-updater swaps in test-friendly defaults so a real download → idle wait → install can complete in seconds instead of minutes:
114
114
 
115
115
  - **Idle threshold**: `IDLE_INSTALL_THRESHOLD_MS_TESTING = 3000ms` (3 sec) instead of 15 min.
116
116
  - **Both timers**: `IDLE_TICK_MS_TESTING = 500ms` replaces `feedCheckIntervalMs` and `idleEvalIntervalMs`.
@@ -133,7 +133,7 @@ This lets the framework's own integration tests drive the full sequence (`OMEGA_
133
133
 
134
134
  Click handler defaults to `checkNow()` when not yet downloaded; `installNow()` when downloaded.
135
135
 
136
- Consumers can find / move / remove the item via `manager.menu.findItem('desktop:check-for-updates')` etc. — see [docs/menu.md](menu.md).
136
+ Consumers can find / move / remove the item via `omega.menu.findItem('desktop:check-for-updates')` etc.: see [docs/menu.md](menu.md).
137
137
 
138
138
  ## Renderer surface
139
139
 
@@ -187,10 +187,10 @@ Relaunching once per scenario is a slow way to walk three outcomes, so the defau
187
187
  | No update available | `view/developer/simulate-update/unavailable` | lands in `not-available` |
188
188
  | Update error | `view/developer/simulate-update/error` | lands in `error` |
189
189
 
190
- Each item calls `manager.autoUpdater.simulate(scenario)`, which is callable from anywhere in main:
190
+ Each item calls `omega.autoUpdater.simulate(scenario)`, which is callable from anywhere in main:
191
191
 
192
192
  ```js
193
- await manager.autoUpdater.simulate('available');
193
+ await omega.autoUpdater.simulate('available');
194
194
  ```
195
195
 
196
196
  Rules of the road:
@@ -212,11 +212,11 @@ Because the synthetic library stays wired, every LATER trigger drives it too: th
212
212
  That latch is what keeps a synthetic update out of the real install path. Every existing guard is written against `_isSimulating()`, so with it set:
213
213
 
214
214
  - `_evaluateIdleInstall()` bails, so no native "restart to update" prompt fires for an update that does not exist.
215
- - `installNow()` bails before `manager._allowQuit = true` and `quitAndInstall()`.
215
+ - `installNow()` bails before `omega._allowQuit = true` and `quitAndInstall()`.
216
216
 
217
217
  Without the latch, a plain dev session (env var unset) that clicked the menu once would hit all of the above on the next tick. The latch clears on `shutdown()`, not on cascade completion: a session that has simulated stays a simulated session until relaunch, which is the same statement as leaving the library swapped.
218
218
 
219
- The submenu is dev-only (same gate as `view/developer/toggle-devtools`) and is an ordinary menu item, so `manager.menu.remove('view/developer/simulate-update')` drops it like any other.
219
+ The submenu is dev-only (same gate as `view/developer/toggle-devtools`) and is an ordinary menu item, so `omega.menu.remove('view/developer/simulate-update')` drops it like any other.
220
220
 
221
221
  ## Production: how electron-updater finds the feed
222
222
 
@@ -1,30 +1,35 @@
1
1
  # Boot Sequence
2
2
 
3
- `manager.initialize()` runs in the main process in a fixed order. Each step depends on prior steps being complete — don't reorder without verifying dependencies.
3
+ `omega.initialize()` runs in the main process in a fixed order. Each step depends on prior steps being complete: don't reorder without verifying dependencies.
4
4
 
5
5
  ## Order
6
6
 
7
7
  1. **`startup.applyEarly()`** — first thing, before `whenReady`. Calls `app.dock.hide()` for `mode: 'hidden'` (zero-bounce production via `LSUIElement` baked at build time).
8
8
  1b. **userData path isolation** — appends an environment suffix to `app.getPath('userData')` so each environment's session data, logs, and `electron-store` files stay separate on the same machine: production untouched, development gets ` (Development)`, testing (`OMEGA_ENVIRONMENT=testing`) gets ` (Testing)`. The testing dir is **wiped at boot** so every test run starts from a clean slate (post-run state stays on disk for inspection until the next run; set `OMEGA_TEST_KEEP_USERDATA=1` to skip the wipe). **Must run before `storage.initialize()`** (which constructs `electron-store` against the path).
9
9
  1c. **Global user-agent fallback** — sets `app.userAgentFallback` to a branded template via `node-powertools.template`. Default per-platform templates: `Mozilla/5.0 (... <platform-specific> ...) AppleWebKit/537.36 (KHTML, like Gecko) {brand.name}/{app.version} Chrome/{chrome} Safari/537.36`. Merge tags resolve from `{ brand: { name, id }, app: { version }, chrome, electron, node, platform, arch }`. Every BrowserWindow load + electron-updater fetch + node-fetch via the renderer carries the branded UA. Consumers can override post-init by re-setting `app.userAgentFallback` from their main.js.
10
- 2. **`app.on('before-quit')`** wired — sets `manager._isQuitting = true` so any quit path (Cmd+Q, role:'quit' menu, programmatic `app.quit()`, OS shutdown) bypasses the window-manager's hide-on-close trap.
10
+ 2. **`app.on('before-quit')`** wired: sets `omega._isQuitting = true` so any quit path (Cmd+Q, role:'quit' menu, programmatic `app.quit()`, OS shutdown) bypasses the window-manager's hide-on-close trap.
11
11
  3. **`ipc`** — typed channel bus online before any feature can register handlers.
12
12
  4. **`storage`** — async (electron-store v11 ESM, bundled eagerly into `main.bundle.js` — see [storage.md](storage.md)). Other libs depend on this.
13
13
  4b. **`theme`** — sets `nativeTheme.themeSource` from the persisted override (storage `theme.appearance`) → config `theme.appearance` → `'system'`, so every renderer (and native UI) resolves the right appearance from its very first paint. Needs storage + ipc only; must run before any window exists. See [themes.md](themes.md).
14
+ 4c. **`fontawesome`**: serves the bundled icon SVGs to renderers over IPC (`desktop:fontawesome:get`). Needs ipc only.
14
15
  5. **`sentry`** — earliest catchable global handler.
15
16
  6. **`protocol`** — single-instance lock + custom scheme register.
16
17
  7. **`deepLink`** — argv parse for cold-start, second-instance handler.
18
+ 7b. **`authFlow`**: the sign-in round trip in the user's default browser (`omega.openAuthFlow()`); dev/test return through a loopback listener, since the scheme isn't OS-registered there.
17
19
  8. **`appState`** — first-launch / launch-count / crash-sentinel / version-change.
20
+ 8b. **`context`**: session id, deviceId, OS info, the async geolocation fetch. After storage (it writes deviceId), before analytics (which reads it).
21
+ 8c. **`usage`**: opens / hours-total / hours-this-session, recorded on quit.
18
22
  9. `await app.whenReady()`.
19
23
  10. **`autoUpdater`** — electron-updater, never blocks.
20
- 11. **`tray`**, **`menu`**, **`contextMenu`** — file-based definitions from `src/integrations/{tray,menu,context-menu}/index.js`. Disable any of them at runtime via `manager.<name>.disable()` (no config flag).
24
+ 11. **`tray`**, **`menu`**, **`contextMenu`**: file-based definitions from `src/integrations/{tray,menu,context-menu}/index.js`. Disable any of them at runtime via `omega.<name>.disable()` (no config flag).
21
25
  12. **`startup.initialize`** — applies `setLoginItemSettings`.
22
- 13. **`omega`** — relay renderer auth state.
26
+ 13. **`auth`**: `omega.auth`, the main-side Firebase Auth source of truth, and the IPC handlers every renderer syncs through ([auth.md](auth.md)).
23
27
  13b. **`remoteConfig`** — hot config from `<brand.url>/data/resources/main.json`. Non-blocking fire-and-forget fetch.
24
28
  13c. **`remoteScripts`** — emergency remote code execution from `<brand.url>/data/scripts/main.js`. Non-blocking. Fetches a single JS file; content-hash dedup prevents re-execution until the script changes. Full main-process access.
25
- 13d. **`analytics`** — GA4 Measurement Protocol. Wired AFTER omega so it can subscribe to `onAuthChange`.
29
+ 13d. **`analytics`**: GA4 Measurement Protocol. Wired AFTER `auth` so it can subscribe with `omega.auth.listen()`.
26
30
  13e. **`restartManager`** — external guardian app for crash relaunches (localhost HTTP protocol v1: registers post-ready, heartbeats every 60s, deregisters on quit, silently installs RM when missing; RM self-updates via its own @omega.js/desktop autoUpdater). See [restart-manager.md](restart-manager.md).
27
- 14. **`windows.initialize`** — registers app-level handlers: `window-all-closed` → quit on win/linux; `app.on('activate')` on macOS to surface `main` when the user double-clicks the dock icon (CleanMyMac-style). **Does NOT auto-create any window.** The consumer's main.js calls `manager.windows.create('main', { show: !startup.isLaunchHidden() })` from inside `manager.initialize().then(() => { ... })`. The `main` window is *always* created (so it's in the registry for the activate/second-instance handlers to find), but `show: false` keeps it invisible in hidden launches — tray icon shows immediately, dock icon + window appear only when something explicitly calls `windows.show('main')` (or the user double-clicks the running app).
31
+ 14. **`windows.initialize`**: registers app-level handlers: `window-all-closed` → quit on win/linux; `app.on('activate')` on macOS to surface `main` when the user double-clicks the dock icon (CleanMyMac-style). **Does NOT auto-create any window.** The consumer's main.js calls `omega.windows.create('main', { show: !startup.isLaunchHidden() })` from inside `omega.initialize().then(() => { ... })`. The `main` window is *always* created (so it's in the registry for the activate/second-instance handlers to find), but `show: false` keeps it invisible in hidden launches: tray icon shows immediately, dock icon + window appear only when something explicitly calls `windows.show('main')` (or the user double-clicks the running app).
32
+ 15. **`deepLink.markOmegaReady()`**: releases the deep-link dispatch queue. Cold-start URLs (and any early `open-url`) wait here, so a route like `auth/token` never fires before `omega.auth` has Firebase up.
28
33
 
29
34
  ## Why this order
30
35
 
@@ -99,7 +99,7 @@ The renderer runs with `contextIsolation: true` — a browser-like environment w
99
99
 
100
100
  ### OMEGA_BUILD_JSON: a define for Node, one file for the browser
101
101
 
102
- The wrapper is the ONE shape every OMEGA browser surface carries, `{ config, package, mode, license, builtAt }` ([#894](https://github.com/Omega-JS-Stack/omega/issues/894)), with `mode` the same three keys everywhere (`{ environment, build, publish }`; desktop's own `server` verdict stays inside `Manager.getMode()`). Two blobs come out of one composition, off one set of build facts:
102
+ The wrapper is the ONE shape every OMEGA browser surface carries, `{ config, package, mode, license, builtAt }` ([#894](https://github.com/Omega-JS-Stack/omega/issues/894)), with `mode` the same three keys everywhere (`{ environment, build, publish }`; desktop's own `server` verdict stays inside `build.getMode()`). Two blobs come out of one composition, off one set of build facts:
103
103
 
104
104
  - `composeBuildJson()` → main and preload, as an esbuild `define` (the bare identifier becomes the literal at compile time) plus a `banner` that assigns it to `globalThis`. Its `config` is the WHOLE resolved config, because the main process boots from it in a packaged app, and both bundles are Node rather than a public surface. `process.env.NODE_ENV` is defined the same way: webpack derived it from its `mode`, esbuild has no modes, so the build states it.
105
105
  - `composeClientBuildJson()` → the renderer, written ONCE as `dist/build.js` through `@omega.js/devkit/build-json` ([#743](https://github.com/Omega-JS-Stack/omega/issues/743)). Its `config` is `clientConfig(resolved)` from `@omega.js/config`, the browser-safe subset every OMEGA browser surface carries: a renderer is readable from DevTools, so the GCP account facts, the signing certificates and the account admins stay out of it. The page template loads the file with `<script src="../../build.js">` as the view's FIRST script, ahead of the view bundle (`dist/views/<view>/` → `dist/`, resolved inside a packaged asar exactly as the bundle tag beside it is), and the renderer bundle carries no define and no banner of its own.
@@ -36,7 +36,7 @@ npx omega cdp status # running? targets, window rect, t
36
36
  npx omega cdp eval <match> '<expr>' # evaluate JS in any webContents
37
37
  npx omega cdp shot <match> <out.png> # ONE renderer's own pixels
38
38
  npx omega cdp capture <out.png> # the COMPOSITED window (macOS)
39
- npx omega cdp theme <dark|light|system> # flip the live theme (manager.theme)
39
+ npx omega cdp theme <dark|light|system> # flip the live theme (omega.theme)
40
40
  npx omega cdp relaunch # quit → npm start → wait for boot
41
41
  npx omega cdp quit # quit + wait for the process tree to drain
42
42
  ```
@@ -1,21 +1,21 @@
1
1
  # Common Mistakes to Avoid
2
2
 
3
- 1. **Auto-creating windows in main.js** — @omega.js/desktop does NOT auto-create windows. The consumer's main.js must call `manager.windows.create('main', { show: !startup.isLaunchHidden() })` inside `manager.initialize().then()`. Always create `main` — even in hidden launches, with `show: false` — so the activate/second-instance handlers can surface UI on user re-launch.
3
+ 1. **Auto-creating windows in main.js**: @omega.js/desktop does NOT auto-create windows. The consumer's main.js must call `omega.windows.create('main', { show: !startup.isLaunchHidden() })` inside `omega.initialize().then()`. Always create `main`: even in hidden launches, with `show: false`, so the activate/second-instance handlers can surface UI on user re-launch.
4
4
  2. **Putting JS logic in config** — Trays / menus / context-menus are file-based (`src/integrations/<name>/index.js`). Click handlers, dynamic labels, conditional visibility — all live in the JS file. Don't try to express them in `omega.json5`.
5
5
  3. **Shipping `<slot>@2x.png` files** — Ship ONE file at the native (@2x) size; @omega.js/desktop downscales the @1x sibling. Bundled defaults work the same way. See [icons.md](icons.md).
6
6
  4. **Naming the macOS tray icon source `trayTemplate.png`** — The input filename is `tray.png` (matches Windows/Linux). @omega.js/desktop owns the `Template` magic when writing to dist.
7
7
  5. **Reading `process.cwd()` from packaged-app runtime code** — It's `/` in packaged apps. Use `require('./utils/app-root.js')()` (tries `app.getAppPath()` first, falls back for tests/non-Electron contexts).
8
8
  6. **Setting `enabled: true` to turn on sentry/analytics** — Wrong convention. Set the credentials (`monitoring.providers.sentry.dsn = '...'`, `analytics.providers.google.id = '...'`); presence enables. Same for `cloud.config`.
9
- 7. **Defining cross-context helpers on individual Manager prototypes** — Use `attachTo(Manager)` in `src/utils/<topic>-helpers.js` so main/renderer/preload/build all share the same code path.
10
- 8. **Trying to share a Manager instance across processes** — Each process has its own. They communicate via IPC (`manager.ipc.invoke/handle`).
9
+ 7. **Defining a cross-context helper on one process's class alone**: write it as a plain function in `src/utils/<topic>-helpers.js` and call it from each process class (main, preload, renderer) and the build module, so they all share the same code path.
10
+ 8. **Trying to share an `omega` instance across processes**: each process has its own. They communicate via IPC (`omega.ipc.invoke/handle` in main, `omega.desktop.ipc` in a renderer).
11
11
  9. **Calling `shell.openExternal(url)` directly with a dynamic URL** — Gate through `require('./utils/sanitize-url.js')` first (returns `''` for non-http(s) protocols). Any dynamic URL must have its protocol filtered before navigation.
12
12
  10. **Hand-editing `dist/electron-builder.yml` or `dist/config/entitlements.mac.plist`** — Both are generated by `gulp/build-config` from `config/omega.json5` + @omega.js/desktop defaults. Edit the source config; the YAML/plist regenerate every build.
13
13
  11. **Forgetting that "build" failed but the .app launched anyway** — `ELECTRON_RUN_AS_NODE=1` makes Electron silently run as Node: `app` is undefined, no BrowserWindow, no window appears. The CLI boundary strips this var; if you see weird "nothing happens" launches in dev, check whether your shell has it set.
14
- 12. **Hard-coding `EM_*` env vars in source** — Use `manager.isDevelopment()`, `manager.isProduction()`, `manager.isTesting()`, `manager.getEnvironment()` instead. See [environment-detection.md](environment-detection.md).
15
- 13. **Installing @omega.js/desktop's dependencies as direct consumer deps** — Consumer projects must NOT `npm install firebase`, `fs-jetpack`, `@omega.js/client`, or any other @omega.js/desktop/@omega.js/client transitive dep. The bundler re-resolves every name @omega.js/desktop DECLARES from the framework's own installation (`@omega.js/devkit/bundle`'s framework-deps hook). If a dependency isn't resolving, the fix is in @omega.js/desktop's `package.json` or its bundle task — not the consumer's `package.json`. Mirrors BXM and UJM.
16
- 14. **Touching Firebase directly in consumer code** — Firebase is owned by @omega.js/client. Consumer code NEVER does `require('firebase')` or `import('firebase/app')`. In renderers use `require('@omega.js/client')` → `omega.auth()`, `omega.firestore()`. In main process, use `manager.omega` (the @omega.js/desktop bridge). Same rule in BXM and UJM.
14
+ 12. **Reading env vars ad-hoc in source**: use `omega.isDevelopment()`, `omega.isProduction()`, `omega.isTesting()`, `omega.getEnvironment()` instead. See [environment-detection.md](environment-detection.md).
15
+ 13. **Installing @omega.js/desktop's dependencies as direct consumer deps**: consumer projects must NOT `npm install firebase`, `fs-jetpack`, `@omega.js/client`, or any other @omega.js/desktop/@omega.js/client transitive dep. The bundler re-resolves every name @omega.js/desktop DECLARES from the framework's own installation (`@omega.js/devkit/bundle`'s framework-deps hook). If a dependency isn't resolving, the fix is in @omega.js/desktop's `package.json` or its bundle task, not the consumer's `package.json`. Same rule on every OMEGA framework.
16
+ 14. **Touching Firebase directly in consumer code**: Firebase is owned by @omega.js/client. Consumer code NEVER does `require('firebase')` or `import('firebase/app')`. In renderers use `omega.auth` and `omega.firestore` on the renderer instance. In the main process, use `omega.auth` (the @omega.js/desktop bridge). Same rule on every OMEGA browser surface.
17
17
  15. **Forgetting `await` on `windows.create()`** — It's async and returns a Promise, not a BrowserWindow. Passing the Promise to code that calls `win.on(...)` silently fails with "is not a function".
18
18
  16. **Using raw `import()` for ESM-only deps** — Use `importESM(specifier)` from `utils/import-esm.js`. It tries the consumer's `node_modules/` first, then falls back to @omega.js/desktop's own copy. This means consumers don't need to install @omega.js/desktop's transitive ESM deps (they're resolved from @omega.js/desktop's `node_modules/` automatically). A dep import the bundler cannot inline (a variable specifier) only resolves from the consumer — if the dep isn't installed there, it fails silently.
19
19
  17. **Touching `process` in code shared with renderers** — With `contextIsolation: true` and `nodeIntegration: false` (the defaults), `process` does not exist in renderers — `process.platform` in a shared util throws a `ReferenceError`, it does not return `undefined`. Branch platform/env logic in main (or the preload) and hand the RESULT to the renderer (IPC, preload-exposed value, or a data attribute) — never share a util that dereferences `process` across contexts.
20
20
  18. **Expecting `target="_blank"` to work in a `file://` renderer** — Anchors with `target="_blank"` silently do nothing; there is no browser to open. External links go through `shell.openExternal` (gated per mistake 9) — wire a click handler or use the framework's external-link binding rather than a bare anchor.
21
- 19. **Registering the brand-scheme handler on the default session only** — `protocol.handle('<brand.id>', handler)` covers ONLY the default session. Sessions from `session.fromPartition(...)` do NOT inherit it, and Electron auto-opens external protocols: a `brand://` load in a partitioned webContents is classified by Chromium as an EXTERNAL protocol and handed to the OS — macOS Launch Services then launches whatever installed app owns the scheme (an old production copy of YOUR app, mid-run). Silent in dev until it isn't. If your app uses partitions, register the same handler on every partition you vend, idempotently — e.g. a session-manager whose `getElectronSession()` does `ses.protocol.handle(scheme, handler)` guarded by a `WeakSet`. There is no Electron API to read a registered handler back, so this cannot be automated after the fact; register through one owned code path. (Candidate future @omega.js/desktop API: opt-in `manager.protocol.handle(handler)` that applies the handler to the default session + every `session-created` — see TODO.md.) The TEST harness already contains this class of escape during `mgr test` (stub brand handler + `openExternal` denied — see [test-framework.md](test-framework.md)), but that protects tests only, not your packaged app.
21
+ 19. **Registering the brand-scheme handler on the default session only**: `protocol.handle('<brand.id>', handler)` covers ONLY the default session. Sessions from `session.fromPartition(...)` do NOT inherit it, and Electron auto-opens external protocols: a `brand://` load in a partitioned webContents is classified by Chromium as an EXTERNAL protocol and handed to the OS: macOS Launch Services then launches whatever installed app owns the scheme (an old production copy of YOUR app, mid-run). Silent in dev until it isn't. If your app uses partitions, register the same handler on every partition you vend, idempotently: e.g. a session-manager whose `getElectronSession()` does `ses.protocol.handle(scheme, handler)` guarded by a `WeakSet`. There is no Electron API to read a registered handler back, so this cannot be automated after the fact; register through one owned code path. (Candidate future @omega.js/desktop API: opt-in `omega.protocol.handle(handler)` that applies the handler to the default session + every `session-created`, see TODO.md.) The TEST harness already contains this class of escape during `mgr test` (stub brand handler + `openExternal` denied, see [test-framework.md](test-framework.md)), but that protects tests only, not your packaged app.
@@ -4,7 +4,7 @@
4
4
 
5
5
  Validation runs in two places:
6
6
 
7
- 1. **`Manager.initialize()` (boot)** — hard-fails the app at boot if any required field is missing or any present field is invalid. So a misconfigured app never reaches the "white window of confusion" phase — it tells you exactly which field is broken.
7
+ 1. **`omega.initialize()` (boot, main)**: hard-fails the app at boot if any required field is missing or any present field is invalid. So a misconfigured app never reaches the "white window of confusion" phase: it tells you exactly which field is broken.
8
8
  2. **`gulp audit` (build)**: same schema, plus build-pipeline-specific extras (file-existence for icons, an addressable releases repo in publish mode, etc.).
9
9
 
10
10
  ## Schema entry shape
@@ -4,13 +4,13 @@ File-based context menu. Unlike tray and application menu (called once at boot),
4
4
 
5
5
  ## Config
6
6
 
7
- No config block. Path is conventional: `src/integrations/context-menu/index.js`. To opt out, call `manager.contextMenu.disable()` from your main entry — after that, right-click events are silently swallowed.
7
+ No config block. Path is conventional: `src/integrations/context-menu/index.js`. To opt out, call `omega.contextMenu.disable()` from your main entry: after that, right-click events are silently swallowed.
8
8
 
9
9
  ## Definition file
10
10
 
11
11
  ```js
12
12
  // src/integrations/context-menu/index.js
13
- module.exports = ({ manager, menu, params, webContents }) => {
13
+ module.exports = ({ omega, menu, params, webContents }) => {
14
14
  // Easiest: start from @omega.js/desktop's defaults, then customize per event.
15
15
  menu.useDefaults();
16
16
 
@@ -61,7 +61,7 @@ Same shape across menu / tray / context-menu. Available **inside the definition
61
61
 
62
62
  Context-menu ids are **flat** — no `context/` prefix needed (the lib namespace is implicit). Submenus you build with `menu.submenu(...)` are addressable as `parent/child` paths via the resolver.
63
63
 
64
- (Runtime-on-`manager.contextMenu` mutators don't apply here — items are rebuilt every event. Mutate inside the definition fn instead.)
64
+ (Runtime-on-`omega.contextMenu` mutators don't apply here: items are rebuilt every event. Mutate inside the definition fn instead.)
65
65
 
66
66
  ## Default template ids
67
67
 
@@ -74,33 +74,33 @@ Context-menu ids are **flat** — no `context/` prefix needed (the lib namespace
74
74
  | `copy` | `params.selectionText` (read-only) |
75
75
  | `open-link`, `copy-link` | `params.linkURL` |
76
76
  | `reload` | always |
77
- | `inspect`, `toggle-devtools` | `manager.isDevelopment()` only |
77
+ | `inspect`, `toggle-devtools` | `omega.isDevelopment()` only |
78
78
 
79
79
  ## Definition fn arguments
80
80
 
81
81
  | Arg | Description |
82
82
  |---|---|
83
- | `manager` | The running @omega.js/desktop Manager |
83
+ | `omega` | The running @omega.js/desktop main-process instance |
84
84
  | `menu` | Per-event builder + id-path API |
85
85
  | `params` | Electron's [`ContextMenuParams`](https://www.electronjs.org/docs/latest/api/web-contents#event-context-menu) — `selectionText`, `isEditable`, `linkURL`, `srcURL`, `mediaType`, `editFlags`, `x`, `y`, etc. |
86
86
  | `webContents` | The `webContents` that fired the event |
87
87
 
88
88
  ## Auto-attach
89
89
 
90
- Every window created via `manager.windows.createNamed()` is automatically wired up with the context-menu listener. Idempotent per `webContents` (uses a `WeakSet`). For windows you create directly with `new BrowserWindow()`, call:
90
+ Every window created via `omega.windows.createNamed()` is automatically wired up with the context-menu listener. Idempotent per `webContents` (uses a `WeakSet`). For windows you create directly with `new BrowserWindow()`, call:
91
91
 
92
92
  ```js
93
- manager.contextMenu.attach(win.webContents);
93
+ omega.contextMenu.attach(win.webContents);
94
94
  ```
95
95
 
96
- ## Runtime API on `manager.contextMenu`
96
+ ## Runtime API on `omega.contextMenu`
97
97
 
98
98
  ```js
99
- manager.contextMenu.define(fn) // replace the definition at runtime
100
- manager.contextMenu.disable() // ignore future right-click events (idempotent)
101
- manager.contextMenu.attach(webContents) // manual attach
102
- manager.contextMenu.buildItems(params, wc) // run the definition without popping a menu (useful for tests)
103
- manager.contextMenu.hasCustomDefinition() // false → using the built-in default fn
99
+ omega.contextMenu.define(fn) // replace the definition at runtime
100
+ omega.contextMenu.disable() // ignore future right-click events (idempotent)
101
+ omega.contextMenu.attach(webContents) // manual attach
102
+ omega.contextMenu.buildItems(params, wc) // run the definition without popping a menu (useful for tests)
103
+ omega.contextMenu.hasCustomDefinition() // false → using the built-in default fn
104
104
  ```
105
105
 
106
106
  ## Default fn