@omega.js/desktop 0.51.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 (289) hide show
  1. package/README.md +18 -13
  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 +145 -48
  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/.github/workflows/build.yml +2 -0
  48. package/dist/defaults/AGENTS.md +17 -8
  49. package/dist/defaults/config/omega.json5 +7 -7
  50. package/dist/defaults/hooks/build/post.js +1 -1
  51. package/dist/defaults/hooks/build/pre.js +1 -1
  52. package/dist/defaults/hooks/deploy/pre.js +1 -1
  53. package/dist/defaults/hooks/release/post.js +1 -1
  54. package/dist/defaults/hooks/release/pre.js +1 -1
  55. package/dist/defaults/src/assets/js/components/about/index.js +3 -5
  56. package/dist/defaults/src/assets/js/components/main/index.js +3 -5
  57. package/dist/defaults/src/assets/js/components/settings/index.js +3 -5
  58. package/dist/defaults/src/integrations/context-menu/index.js +2 -2
  59. package/dist/defaults/src/integrations/menu/index.js +3 -3
  60. package/dist/defaults/src/integrations/tray/index.js +3 -3
  61. package/dist/defaults/src/main.js +3 -5
  62. package/dist/defaults/src/preload.js +3 -5
  63. package/dist/defaults/test/README.md +2 -2
  64. package/dist/gulp/main.js +9 -10
  65. package/dist/gulp/tasks/audit.js +7 -7
  66. package/dist/gulp/tasks/build-config.js +8 -8
  67. package/dist/gulp/tasks/bundle.js +16 -16
  68. package/dist/gulp/tasks/defaults.js +3 -3
  69. package/dist/gulp/tasks/distribute.js +2 -2
  70. package/dist/gulp/tasks/html.js +9 -9
  71. package/dist/gulp/tasks/package-quick.js +3 -3
  72. package/dist/gulp/tasks/package.js +3 -3
  73. package/dist/gulp/tasks/release.js +3 -3
  74. package/dist/gulp/tasks/sass.js +6 -6
  75. package/dist/gulp/tasks/serve.js +4 -4
  76. package/dist/hooks/notarize-artifacts.js +1 -1
  77. package/dist/hooks/notarize.js +1 -1
  78. package/dist/index.js +5 -8
  79. package/dist/lib/_environment-mixin.js +50 -0
  80. package/dist/lib/_lifecycle-mixin.js +45 -0
  81. package/dist/lib/analytics.js +33 -35
  82. package/dist/lib/app-state.js +14 -14
  83. package/dist/lib/auth-flow.js +18 -18
  84. package/dist/lib/auth-persistence.js +12 -12
  85. package/dist/lib/auth.js +421 -0
  86. package/dist/lib/auto-updater.js +49 -49
  87. package/dist/lib/context-menu.js +13 -13
  88. package/dist/lib/context.js +19 -19
  89. package/dist/lib/deep-link.js +34 -34
  90. package/dist/lib/fontawesome.js +5 -5
  91. package/dist/lib/ipc.js +4 -4
  92. package/dist/lib/menu.js +25 -25
  93. package/dist/lib/protocol.js +5 -5
  94. package/dist/lib/remote-config.js +22 -22
  95. package/dist/lib/remote-scripts.js +21 -21
  96. package/dist/lib/restart-manager/index.js +28 -28
  97. package/dist/lib/sign-helpers/exec-with-limit.js +1 -1
  98. package/dist/lib/sign-helpers/sign-events.js +1 -1
  99. package/dist/lib/startup.js +18 -13
  100. package/dist/lib/storage.js +10 -10
  101. package/dist/lib/templating.js +16 -16
  102. package/dist/lib/theme.js +10 -10
  103. package/dist/lib/tray.js +27 -27
  104. package/dist/lib/usage.js +11 -11
  105. package/dist/lib/window-manager.js +26 -26
  106. package/dist/main.js +398 -483
  107. package/dist/preload.js +236 -176
  108. package/dist/renderer.js +417 -391
  109. package/dist/runner/job-started.js +1 -1
  110. package/dist/test/fixtures/consumer-app/config/omega.json5 +1 -1
  111. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +8 -11
  112. package/dist/test/fixtures/consumer-app/src/main.js +5 -7
  113. package/dist/test/fixtures/consumer-app/src/preload.js +2 -2
  114. package/dist/test/harness/boot-entry.js +22 -20
  115. package/dist/test/harness/main-entry.js +31 -30
  116. package/dist/test/harness/renderer-entry.js +5 -5
  117. package/dist/test/harness/renderer-preload.js +137 -141
  118. package/dist/test/index.js +10 -10
  119. package/dist/test/runner.js +2 -2
  120. package/dist/test/runners/boot.js +7 -6
  121. package/dist/test/runners/electron.js +3 -2
  122. package/dist/test/runners/render-event.js +2 -2
  123. package/dist/test/suites/boot/consumer-app-boots.test.js +57 -26
  124. package/dist/test/suites/boot/restart-manager.test.js +2 -2
  125. package/dist/test/suites/boot/storage-bundled.test.js +5 -5
  126. package/dist/test/suites/boot/theme.test.js +13 -13
  127. package/dist/test/suites/build/audit.test.js +1 -1
  128. package/dist/test/suites/build/auth-persistence-resolve.test.js +6 -6
  129. package/dist/test/suites/build/boot-fixture.test.js +2 -2
  130. package/dist/test/suites/build/boot-runner-timeout.test.js +6 -5
  131. package/dist/test/suites/build/brand-scss.test.js +1 -1
  132. package/dist/test/suites/build/build-json-bake.test.js +1 -1
  133. package/dist/test/suites/build/cli.test.js +2 -2
  134. package/dist/test/suites/build/config-schema.test.js +5 -5
  135. package/dist/test/suites/build/defaults-scaffold.test.js +2 -2
  136. package/dist/test/suites/build/deploy-hook.test.js +3 -3
  137. package/dist/test/suites/build/ensure-target.test.js +2 -2
  138. package/dist/test/suites/build/env-delivery.test.js +2 -2
  139. package/dist/test/suites/build/esm-only-dependency.test.js +2 -2
  140. package/dist/test/suites/build/exports.test.js +9 -8
  141. package/dist/test/suites/build/get-config.test.js +4 -4
  142. package/dist/test/suites/build/manifest-deps.test.js +1 -1
  143. package/dist/test/suites/build/merge-line-files.test.js +1 -1
  144. package/dist/test/suites/build/omega-shell.test.js +34 -2
  145. package/dist/test/suites/build/omega.test.js +350 -0
  146. package/dist/test/suites/build/renderer-auth-bridge.test.js +211 -78
  147. package/dist/test/suites/build/runner.test.js +612 -5
  148. package/dist/test/suites/build/sentry.test.js +2 -2
  149. package/dist/test/suites/build/sign-windows-e2e.test.js +2 -2
  150. package/dist/test/suites/build/templating.test.js +3 -3
  151. package/dist/test/suites/build/test-stealth.test.js +7 -9
  152. package/dist/test/suites/build/url-helpers.test.js +55 -56
  153. package/dist/test/suites/build/validate-config.test.js +2 -2
  154. package/dist/test/suites/build/wave5-pins.test.js +2 -2
  155. package/dist/test/suites/main/analytics.test.js +59 -59
  156. package/dist/test/suites/main/app-state.test.js +66 -66
  157. package/dist/test/suites/main/auth-flow.test.js +41 -41
  158. package/dist/test/suites/main/auth-persistence.test.js +54 -43
  159. package/dist/test/suites/main/{client-bridge.integration.test.js → auth.integration.test.js} +10 -9
  160. package/dist/test/suites/main/auth.test.js +336 -0
  161. package/dist/test/suites/main/auto-updater.test.js +134 -134
  162. package/dist/test/suites/main/boot-sequence.test.js +37 -49
  163. package/dist/test/suites/main/context-menu.test.js +51 -50
  164. package/dist/test/suites/main/context.test.js +25 -25
  165. package/dist/test/suites/main/deep-link.test.js +74 -74
  166. package/dist/test/suites/main/fontawesome.test.js +27 -27
  167. package/dist/test/suites/main/ipc.test.js +35 -35
  168. package/dist/test/suites/main/menu.test.js +101 -100
  169. package/dist/test/suites/main/protocol.test.js +19 -19
  170. package/dist/test/suites/main/remote-config.test.js +63 -63
  171. package/dist/test/suites/main/remote-scripts.test.js +103 -103
  172. package/dist/test/suites/main/request.test.js +71 -0
  173. package/dist/test/suites/main/restart-manager.test.js +33 -33
  174. package/dist/test/suites/main/startup-paths-and-ua.test.js +4 -4
  175. package/dist/test/suites/main/startup.test.js +26 -26
  176. package/dist/test/suites/main/stealth-window.test.js +1 -1
  177. package/dist/test/suites/main/storage.test.js +25 -25
  178. package/dist/test/suites/main/theme.test.js +34 -34
  179. package/dist/test/suites/main/tray.test.js +79 -79
  180. package/dist/test/suites/main/url-helpers.test.js +133 -133
  181. package/dist/test/suites/main/usage.test.js +25 -25
  182. package/dist/test/suites/main/window-bounds.test.js +27 -27
  183. package/dist/test/suites/main/window-manager.test.js +44 -44
  184. package/dist/test/suites/renderer/analytics-bridge.test.js +5 -5
  185. package/dist/test/suites/renderer/cross-context-helpers.test.js +37 -31
  186. package/dist/test/suites/renderer/round-trip.test.js +3 -3
  187. package/dist/test/suites/renderer/tooltips.test.js +15 -15
  188. package/dist/test/suites/renderer/{window-em-surface.test.js → window-desktop-surface.test.js} +27 -7
  189. package/dist/test/utils/extended-mode-warning.js +1 -1
  190. package/dist/utils/boot-harness.js +56 -0
  191. package/dist/utils/mode-helpers.js +2 -15
  192. package/dist/utils/ship-keys.js +3 -3
  193. package/dist/utils/signing-status.js +51 -0
  194. package/dist/utils/test-events.js +7 -0
  195. package/dist/utils/test-stealth.js +6 -6
  196. package/dist/utils/url-helpers.js +52 -42
  197. package/dist/utils/user-agent.js +44 -0
  198. package/dist/vendor/account/engine.js +3 -3
  199. package/dist/vendor/account/index.js +14 -45
  200. package/dist/vendor/account/resolve-account.js +44 -0
  201. package/dist/vendor/account/schema.js +1 -1
  202. package/dist/vendor/account/user.js +99 -0
  203. package/dist/vendor/config/client-config.js +1 -1
  204. package/dist/vendor/config/environment.js +11 -30
  205. package/dist/vendor/config/index.js +13 -12
  206. package/dist/vendor/config/load.js +1 -2
  207. package/dist/vendor/config/platforms.js +1 -1
  208. package/dist/vendor/config/repo.js +24 -0
  209. package/dist/vendor/config/retired-keys.js +2 -2
  210. package/dist/vendor/config/schema.js +5 -8
  211. package/dist/vendor/config/site-global.js +2 -3
  212. package/dist/vendor/config/validate.js +1 -2
  213. package/dist/vendor/config/winback.js +1 -1
  214. package/dist/vendor/devkit/actions-secrets.js +1 -1
  215. package/dist/vendor/devkit/attach-log-file.js +1 -1
  216. package/dist/vendor/devkit/brand-version.js +162 -1
  217. package/dist/vendor/devkit/build-json.js +1 -1
  218. package/dist/vendor/devkit/cli-router.js +3 -4
  219. package/dist/vendor/devkit/defaults-engine.js +9 -11
  220. package/dist/vendor/devkit/deploy.js +64 -4
  221. package/dist/vendor/devkit/git-remote.js +78 -1
  222. package/dist/vendor/devkit/local.js +53 -3
  223. package/dist/vendor/devkit/lockfile.js +127 -0
  224. package/dist/vendor/devkit/merge-line-files.js +2 -3
  225. package/dist/vendor/devkit/pack-local.js +4 -7
  226. package/dist/vendor/devkit/preludes/origin-heal.js +35 -49
  227. package/dist/vendor/devkit/target-secrets.js +34 -45
  228. package/dist/vendor/devkit/test/runner-core.js +6 -6
  229. package/dist/vendor/monitoring/env.js +2 -2
  230. package/dist/vendor/monitoring/index.js +1 -1
  231. package/dist/vendor/monitoring/main.js +1 -1
  232. package/dist/vendor/monitoring/preload.js +1 -1
  233. package/dist/vendor/monitoring/renderer.js +1 -1
  234. package/docs/analytics.md +8 -8
  235. package/docs/app-state.md +19 -19
  236. package/docs/audit.md +4 -4
  237. package/docs/{client-bridge.md → auth.md} +88 -73
  238. package/docs/auto-updater.md +8 -8
  239. package/docs/boot-sequence.md +11 -6
  240. package/docs/build-system.md +1 -1
  241. package/docs/cdp-debugging.md +1 -1
  242. package/docs/common-mistakes.md +7 -7
  243. package/docs/config-schema.md +1 -1
  244. package/docs/context-menu.md +13 -13
  245. package/docs/context.md +11 -11
  246. package/docs/css.md +3 -9
  247. package/docs/deep-link.md +25 -25
  248. package/docs/environment-detection.md +16 -16
  249. package/docs/fontawesome.md +7 -5
  250. package/docs/hooks.md +8 -8
  251. package/docs/index.md +39 -28
  252. package/docs/ipc.md +10 -10
  253. package/docs/lib-modules.md +9 -9
  254. package/docs/logging.md +12 -14
  255. package/docs/menu.md +18 -18
  256. package/docs/releasing.md +4 -2
  257. package/docs/remote-config.md +9 -9
  258. package/docs/remote-scripts.md +12 -12
  259. package/docs/restart-manager.md +8 -8
  260. package/docs/runner.md +12 -10
  261. package/docs/sentry.md +4 -4
  262. package/docs/shared/analytics.md +1 -1
  263. package/docs/shared/brands.md +1 -1
  264. package/docs/shared/breaking-changes.md +79 -13
  265. package/docs/shared/config.md +24 -21
  266. package/docs/shared/deploys.md +30 -7
  267. package/docs/shared/local-dev.md +5 -3
  268. package/docs/shared/logging.md +1 -1
  269. package/docs/shared/monitoring.md +5 -5
  270. package/docs/shared/testing.md +2 -2
  271. package/docs/shared/theming.md +1 -1
  272. package/docs/shared/translation.md +19 -10
  273. package/docs/signing.md +1 -1
  274. package/docs/startup.md +16 -16
  275. package/docs/storage.md +11 -11
  276. package/docs/templating.md +3 -3
  277. package/docs/test-boot-layer.md +12 -12
  278. package/docs/test-framework.md +17 -17
  279. package/docs/themes.md +7 -7
  280. package/docs/tooltips.md +2 -2
  281. package/docs/tray.md +31 -31
  282. package/docs/usage.md +7 -7
  283. package/docs/verts.md +1 -1
  284. package/docs/windows.md +22 -22
  285. package/package.json +3 -4
  286. package/dist/lib/client-bridge.js +0 -374
  287. package/dist/lib/logger.js +0 -4
  288. package/dist/test/suites/build/manager.test.js +0 -213
  289. package/dist/test/suites/main/client-bridge.test.js +0 -262
@@ -49,7 +49,7 @@ There is deliberately no quit endpoint — RM updates itself (below); nothing ex
49
49
 
50
50
  1. **Boot** (step 12e): after `whenReady` + 15s (3s dev), `register()` runs the full flow — probe (`runtime.json` → pid alive via `process.kill(pid, 0)` (EPERM = alive) → `GET /v1/health` with protocolVersion match) → if RM isn't serving: `ensureInstalled()` + `ensureRunning()` (spawn detached, poll up to 15s) → `POST /v1/register` with **this process's pid**. Attempt budget: 3 per invocation. Never throws; failures land in `getStatus().lastError`.
51
51
  2. **Heartbeat**: re-POST register every 60s (idempotent upsert on RM's side). Doubles as the keep-alive — a failed tick runs the full `register()` flow, which respawns (or reinstalls, cooldown-gated) RM.
52
- 3. **Graceful quit**: the first `before-quit` is prevented once, deregister flushes with a hard 1s cap, then `manager.quit({ force: true })` re-quits (all framework before-quit listeners are double-fire safe — main.js `_isQuitting`, appState sentinel, usage stamp). Exception: when the auto-updater has a staged install (`_allowQuit` + status `downloaded`) we never intercept `quitAndInstall` — deregister goes fire-and-forget and RM's grace window covers the race. RM-side safety net: a crash only counts after 2 consecutive dead ticks (~20s), so a slightly-late deregister always wins.
52
+ 3. **Graceful quit**: the first `before-quit` is prevented once, deregister flushes with a hard 1s cap, then `omega.quit({ force: true })` re-quits (all framework before-quit listeners are double-fire safe: main.js `_isQuitting`, appState sentinel, usage stamp). Exception: when the auto-updater has a staged install (`_allowQuit` + status `downloaded`) we never intercept `quitAndInstall`, deregister goes fire-and-forget and RM's grace window covers the race. RM-side safety net: a crash only counts after 2 consecutive dead ticks (~20s), so a slightly-late deregister always wins.
53
53
  4. **Crash**: the pid dies with the registration still present → RM relaunches the app from its registered exe path (mac: `open` on the derived bundle). Never in dev — RM refuses to relaunch `environment !== 'production'` registrations (a dev exe path is the bare electron binary). Crash loops back off (3 relaunches / 10 min → RM gives up, visible in its dashboard).
54
54
 
55
55
  ## Silent install (smart existence first)
@@ -76,8 +76,8 @@ The update relaunch is safe by construction: RM's registrations are **storage-pe
76
76
 
77
77
  ## Bail conditions
78
78
 
79
- - **`manager.isTesting()`** — nothing fires on its own: no timers, no before-quit hook (a preventDefault would wedge the harness quit), and the root is isolated under the testing userData. Tests drive `register()`/`ensureInstalled()` explicitly against fixture servers; the network and spawn paths stay dead (`ensureInstalled` refuses network without `TEST_EXTENDED_MODE`, `ensureRunning` never spawns in testing).
80
- - `manager.config.brand.id === 'restart-manager'` — RM doesn't manage itself.
79
+ - **`omega.isTesting()`**: nothing fires on its own: no timers, no before-quit hook (a preventDefault would wedge the harness quit), and the root is isolated under the testing userData. Tests drive `register()`/`ensureInstalled()` explicitly against fixture servers; the network and spawn paths stay dead (`ensureInstalled` refuses network without `TEST_EXTENDED_MODE`, `ensureRunning` never spawns in testing).
80
+ - `omega.config.brand.id === 'restart-manager'`: RM doesn't manage itself.
81
81
  - `config.restartManager.enabled === false` — explicit opt-out.
82
82
  - Non-production without `OMEGA_RESTART_MANAGER_DEV=1` — dev noise guard.
83
83
 
@@ -96,11 +96,11 @@ Existing consumers with the old `{ enabled: true }` shape need zero changes.
96
96
  ## API
97
97
 
98
98
  ```js
99
- manager.restartManager.register() // full ensure-installed→ensure-running→POST flow; never throws
100
- manager.restartManager.unregister() // best-effort deregister (stops the heartbeat)
101
- manager.restartManager.ensureInstalled() // smart-existence install; true when present
102
- manager.restartManager.ensureRunning() // probe → spawn → poll; true when serving
103
- manager.restartManager.getStatus() // { enabled, bailed, bailReason, root, installed,
99
+ omega.restartManager.register() // full ensure-installed→ensure-running→POST flow; never throws
100
+ omega.restartManager.unregister() // best-effort deregister (stops the heartbeat)
101
+ omega.restartManager.ensureInstalled() // smart-existence install; true when present
102
+ omega.restartManager.ensureRunning() // probe → spawn → poll; true when serving
103
+ omega.restartManager.getStatus() // { enabled, bailed, bailReason, root, installed,
104
104
  // installedVersion, running, registered, port,
105
105
  // pid, lastHeartbeatAt, lastError }
106
106
  ```
package/docs/runner.md CHANGED
@@ -56,7 +56,7 @@ Then, in a normal (non-elevated) PowerShell:
56
56
 
57
57
  Then the orgs: a checkbox of every org your token administers, in alphabetical order. Ticked by default are the orgs this box already answered for — the saved `OMEGA_RUNNER_ORGS`, else the orgs it actually registered, else NOTHING. A token that administers 35 orgs must never register 35 runners on one Enter. Ticking nothing is refused too — a runner registered against no org is not a runner. Off a TTY the walk asks nothing: `config` says so and stops (edit the file instead), `install` refuses only when a required key is missing, and a blank org list still means every org the token administers, because CI has no keyboard.
58
58
 
59
- `start` is the exception, deliberately: it asks only for MISSING required keys, never the checkbox, and warns when the file's org list is not the one this install registered.
59
+ `start` is the exception, deliberately: it asks only for MISSING required keys, never the checkbox, and warns when the file's org list is not the one this install registered. On a bare box (nothing installed yet) `start` runs install itself, so there it is install's full walk, checkbox included.
60
60
 
61
61
  Orgs in `OMEGA_RUNNER_ORGS` you do not administer are named in a warning and skipped.
62
62
 
@@ -64,13 +64,13 @@ Orgs in `OMEGA_RUNNER_ORGS` you do not administer are named in a warning and ski
64
64
 
65
65
  ```powershell
66
66
  npx omega runner status # registered orgs, Startup shortcuts, live listeners, legacy leftovers
67
- npx omega runner start # bring EVERY registered org's runner up, detached (idempotent: an org already alive is skipped)
67
+ npx omega runner start # the one command: install a bare box, else heal HOME and the job guard, refresh a stale actions/runner in place,
68
+ # register any admin org not served yet, then bring EVERY registered org up, detached (an org already alive is skipped)
68
69
  npx omega runner restart # stop, wait for the listeners to go, then start
69
70
  npx omega runner stop # kill every Runner.Listener.exe under the runner home
70
- npx omega runner install # idempotent full setup — tears down first, so re-running is safe
71
+ npx omega runner install # the explicit clean rebuild: tears down first, so re-running is safe
71
72
  npx omega runner config # the same full walk install runs — every key, current values as the defaults, plus the orgs
72
73
  npx omega runner register-org <org># register one specific org
73
- npx omega runner self-update # npm i -g @omega.js/desktop@latest
74
74
  npx omega runner uninstall # remove everything, legacy services, tasks and the em-runner install included
75
75
  npx omega runner monitor # tail the signing event log
76
76
  ```
@@ -79,13 +79,13 @@ Notes worth knowing before you use them:
79
79
 
80
80
  - **`config` is `install`'s configuration step, alone.** Same walk, same defaults, same order — it just does not go on to register anything. A saved org your token no longer administers is named before the checkbox, since it cannot appear in it. Windows-only, and terminal-only (with nothing to ask with it stops and names the file). When the orgs you pick are not the ones this install registered, it says to re-run `install`.
81
81
  - **Every subcommand tees its output to `<runner home>\logs\runner.log`**, and so does `npx omega sign-windows` — including inside a `windows-sign` job, which is exactly when the box's own record is wanted (the log lives in the runner home, never in a workspace, so the usual "no logs in CI" rule does not apply to it). `runner status` prints the path on its own line. It APPENDS, because more than one process writes it: `start` returns as soon as the runners are spawned, and every `sign-windows` those listeners go on to run adds to the same file. Each run stamps its own `# omega log` header; nothing rotates it, so delete the file when you want a clean one. `uninstall` keeps `.env` and `logs\`, since the log it is writing while it runs is the trail of that uninstall.
82
- - **A refusal writes nothing.** Off Windows every subcommand but `self-update` and `monitor` stops at the platform check before the log file is opened, so running one on a Mac by accident leaves no `.gh-runners/` in the directory you were standing in.
82
+ - **A refusal writes nothing.** Off Windows every subcommand but `monitor` stops at the platform check before the log file is opened, so running one on a Mac by accident leaves no `.gh-runners/` in the directory you were standing in.
83
83
  - **The box verbs ignore the project's `.env` cascade.** `runner` and `sign-windows` read the shell and `<runner home>\.env`, nothing else, so running them from inside a brand folder can never hand a brand's `GH_TOKEN` to the box ([#337](https://github.com/Omega-JS-Stack/omega/issues/337)). Every other verb keeps the cascade.
84
- - **`start` brings up EVERY registered org, detached.** It walks the Startup shortcuts in order and spawns one hidden runner per org, so the terminal comes straight back and nothing is lost by not watching it: the listener's output is the JSONL log `monitor` tails. Running it again is safe, which is the point of it: an org whose listener is already alive is named with its PID and skipped, never duplicated and never a refusal. Exit 0 when every org ends up online, 1 when a spawn failed. A listener sitting in session 0 is skipped too, but loudly: it is alive and will fail every job it picks up, so the line names the session and points at `restart`. Killing a listener is `stop`'s job, never a start's.
84
+ - **`start` brings up EVERY registered org, detached.** It walks the Startup shortcuts in order and spawns one hidden runner per org, so the terminal comes straight back and nothing is lost by not watching it: the listener's output is the JSONL log `monitor` tails. Running it again is safe, which is the point of it: an org whose listener is already alive is named with its PID and skipped, never duplicated and never a refusal. Exit 0 when every org ends up online, 1 when a spawn or a registration failed. A listener sitting in session 0 is skipped too, but loudly: it is alive and will fail every job it picks up, so the line names the session and points at `restart`. Killing a listener is `stop`'s job, never a start's.
85
85
  - **`restart` is `stop` then `start`**, in that order, for the loop you would otherwise run by hand after a config change. It settles two things the two commands typed in sequence do not: the box config walk runs FIRST, so a box missing a required key is refused before anything is killed rather than left stopped, and each org's dir is polled (5 seconds) until no listener stands in it, because `taskkill` returns before the process is gone and a start that raced it would read the dying listener as "already running". A listener still standing after the wait is reported and that org is left alone, exit 1.
86
86
  - **`stop` leaves the Startup shortcuts in place**, so a logout/login brings the runner back. For a permanent stop, run `uninstall`.
87
- - **Every subcommand except `self-update` and `monitor` refuses on non-Windows.** `OMEGA_RUNNER_FORCE=1` overrides it, for framework tests only.
88
- - **A test process cannot touch a real box.** Every subcommand that changes the machine — `install`, `config`, `register-org`, `start`, `restart`, `stop`, `uninstall`, `self-update` — refuses, before the platform check and naming `OMEGA_TEST_RUNNER`, whenever the run is a test (`omega test` sets that variable; `OMEGA_TEST_MODE` counts too, the marker a sibling framework's runner or a consumer's own test script sets) and a home it could act on is not a scratch one, under a `.temp` directory or the OS temp dir. BOTH homes are checked, the passed one and the module-level `RUNNER_HOME`, because the box's `.env` was already read into the process from the latter when the command module was required; the refusal names whichever is real. The marker is read from the process environment only — an injected environment is a fixture for the config walk, never an answer to "am I a test". The Startup folder is the THIRD surface checked, because no home scopes it — `uninstall` sweeps every `omega-runner-*.cmd` in the folder whatever home it was given, and a case whose two homes were both scratch deleted the box's three real shortcuts. It has its own scratch seam, `OMEGA_RUNNER_STARTUP_DIR`, and a test run against the real folder refuses naming both. The surfaces that no path can redirect at all — the watcher service, the legacy logon tasks, the `actions.runner.*` services, and the legacy `em-runner` homes — are simply not swept under a test run; `uninstall` says so on one line instead. A test run also never kills a process whose ExecutablePath it cannot read: that path belongs to another account, and matching it scopes to no home at all, so `stop` and `uninstall` drop the clause instead of `taskkill /F`-ing a stranger's runner. And it never deregisters on its own: `deregisterOrgRunners` mints a real removal token and runs each org directory's `config.cmd`, neither of them home-scoped, so with a roster to remove a test run refuses, naming the seam, unless `exec` is injected and, while `GH_TOKEN` is set, `getRemoveToken` too. A framework suite that wants to drive these points `OMEGA_RUNNER_HOME` and `OMEGA_RUNNER_STARTUP_DIR` at scratches *before* the command module is required, since both are resolved once, at require time. `sign-windows` follows the same rule for its log: from a test run pointed at a real home it tees nowhere rather than write the box's own record. This exists because a suite once ran a real `install` on the box and registered 34 orgs.
87
+ - **Every subcommand except `monitor` refuses on non-Windows.** `OMEGA_RUNNER_FORCE=1` overrides it, for framework tests only.
88
+ - **A test process cannot touch a real box.** Every subcommand that changes the machine (`install`, `config`, `register-org`, `start`, `restart`, `stop`, `uninstall`) refuses, before the platform check and naming `OMEGA_TEST_RUNNER`, whenever the run is a test (`omega test` sets that variable; `OMEGA_TEST_MODE` counts too, the marker a sibling framework's runner or a consumer's own test script sets) and a home it could act on is not a scratch one, under a `.temp` directory or the OS temp dir. BOTH homes are checked, the passed one and the module-level `RUNNER_HOME`, because the box's `.env` was already read into the process from the latter when the command module was required; the refusal names whichever is real. The marker is read from the process environment only — an injected environment is a fixture for the config walk, never an answer to "am I a test". The Startup folder is the THIRD surface checked, because no home scopes it — `uninstall` sweeps every `omega-runner-*.cmd` in the folder whatever home it was given, and a case whose two homes were both scratch deleted the box's three real shortcuts. It has its own scratch seam, `OMEGA_RUNNER_STARTUP_DIR`, and a test run against the real folder refuses naming both. The surfaces that no path can redirect at all — the watcher service, the legacy logon tasks, the `actions.runner.*` services, and the legacy `em-runner` homes — are simply not swept under a test run; `uninstall` says so on one line instead. A test run also never kills a process whose ExecutablePath it cannot read: that path belongs to another account, and matching it scopes to no home at all, so `stop` and `uninstall` drop the clause instead of `taskkill /F`-ing a stranger's runner. And it never deregisters on its own: `deregisterOrgRunners` mints a real removal token and runs each org directory's `config.cmd`, neither of them home-scoped, so with a roster to remove a test run refuses, naming the seam, unless `exec` is injected and, while `GH_TOKEN` is set, `getRemoveToken` too. `start` and `restart` skip their org check the same way, on one line, unless both `_discoverOrgs` and `_registerOrg` are injected: registering acts on GitHub, never on a home. A framework suite that wants to drive these points `OMEGA_RUNNER_HOME` and `OMEGA_RUNNER_STARTUP_DIR` at scratches *before* the command module is required, since both are resolved once, at require time. `sign-windows` follows the same rule for its log: from a test run pointed at a real home it tees nowhere rather than write the box's own record. This exists because a suite once ran a real `install` on the box and registered 34 orgs.
89
89
  - **`uninstall` deregisters on the GitHub side first, and keeps whatever did not come off.** It walks every `actions-runner-<org>\` directory under the runner home and runs that directory's own `config.cmd remove` before anything is deleted. If a removal exits non-zero — or `GH_TOKEN` is not set, so no removal token can be minted — that runner is still registered, so its directory SURVIVES the uninstall and the summary names the org. Re-running `uninstall` retries it. Belt and braces: `register-org` also deletes every org-side runner starting with this host's prefix before it registers a new one.
90
90
 
91
91
  ## Upgrading from the electron-manager runner
@@ -102,7 +102,9 @@ So the upgrade on the box is the ordinary one: `npx omega runner install` with `
102
102
 
103
103
  ## Adding a new org
104
104
 
105
- Nothing happens automatically — no process is watching for new orgs. Run `npx omega runner install` again (it is idempotent and re-registers everything), or register just the one org:
105
+ No process watches for new orgs, but `start` checks every time it runs: `npx omega runner start` lists the orgs your `GH_TOKEN` administers, narrows them by `OMEGA_RUNNER_ORGS` when it is set, registers each one this box does not serve yet (Startup shortcut included), and brings it online with the rest. Orgs registered outside the list stay registered; `npx omega runner install` is the command that reshapes the box to the list exactly. An org check that cannot reach GitHub is one warn line, and `start` carries on and brings every registered org online.
106
+
107
+ To register one specific org alone:
106
108
 
107
109
  ```powershell
108
110
  npx omega runner register-org <org-name>
@@ -280,7 +282,7 @@ The SafeNet driver sometimes detaches after a major OS update. Open the SafeNet
280
282
 
281
283
  ## Upgrading the actions/runner binary
282
284
 
283
- `ACTIONS_RUNNER_VERSION` in `src/commands/runner.js` pins it. Bump the constant, ship a new `@omega.js/desktop`, then on the box: `npx omega runner self-update` followed by `npx omega runner install`. The install tears down the old tree and lays the new version down cleanly.
285
+ `ACTIONS_RUNNER_VERSION` in `src/commands/runner.js` pins it. Bump the constant, ship, then on the box run `npx omega runner start`. It sees that the version `config.json` recorded is not the pinned one, re-downloads `_template` in place, and copies it over each org dir whose listener is not running. `.runner`, `.credentials` and `.env` are not in the template, so every registration survives, and nothing is torn down. An org whose listener IS running has its binaries locked: it is skipped with one line naming `npx omega runner restart`, and the new version is recorded only once every org dir took it, so a `restart` (which stops first) finishes the job. `npx omega runner install` remains the clean rebuild when you want one.
284
286
 
285
287
  ## Related
286
288
 
package/docs/sentry.md CHANGED
@@ -63,16 +63,16 @@ default set. Desktop's own `process.on('uncaughtException'|'unhandledRejection')
63
63
  Same surface in main and renderer:
64
64
 
65
65
  ```js
66
- manager.sentry.captureException(error, { extra: { ...context } })
67
- manager.sentry.captureMessage('explicit log', 'info' | 'warning' | 'error')
68
- manager.sentry.setUser({ id, email }) // or null to clear
66
+ omega.sentry.captureException(error, { extra: { ...context } })
67
+ omega.sentry.captureMessage('explicit log', 'info' | 'warning' | 'error')
68
+ omega.sentry.setUser({ id, email }) // or null to clear
69
69
  ```
70
70
 
71
71
  In renderer (via preload bridge): `window.desktop.sentry` would expose the same surface — currently not wired (preload doesn't yet bridge sentry; renderer code can call `@sentry/electron/renderer` directly if it needs to).
72
72
 
73
73
  ## Auth attribution
74
74
 
75
- When the user signs in via `client-bridge`, @omega.js/desktop automatically calls `manager.sentry.setUser({ id, email })`. On sign-out, `setUser(null)` clears the context. So every error report is attributed to whoever was signed in at the time.
75
+ When the user signs in via `client-bridge`, @omega.js/desktop automatically calls `omega.sentry.setUser({ id, email })`. On sign-out, `setUser(null)` clears the context. So every error report is attributed to whoever was signed in at the time.
76
76
 
77
77
  The user object is **normalized** before being sent — only `uid`/`id` is kept, and everything else (display name, photo URL, OAuth provider data, etc.) is stripped to avoid accidentally leaking PII.
78
78
 
@@ -534,7 +534,7 @@ name, birthday, gender, location and phone.
534
534
  **One normalization table per provider, and no shared "close enough" normalizer.** The rules
535
535
  genuinely differ key by key, and a value normalized by the wrong platform's rule is ACCEPTED
536
536
  by the API and matched to nobody — the same silent nothing an unhashed value is. The server's
537
- tables live in `packages/backend/src/manager/libraries/analytics/match-data.js`; the browser
537
+ tables live in `packages/backend/src/omega/libraries/analytics/match-data.js`; the browser
538
538
  half's shared rules (email, phone, external id) live in `@omega.js/analytics/identity` so both
539
539
  halves of one person present identical keys.
540
540
 
@@ -16,7 +16,7 @@ Fixture for the AUTOMATED corpus/e2e suites: offline, `demo-*` Firebase, determi
16
16
 
17
17
  ### `brands/playground-omega` — "OMEGA Playground", the standing live test brand
18
18
 
19
- Renamed from omega-brand (Ian 2026-07-11, zero ambiguity). Born through the real wizard: id `playground` (shortened on 2026-09-10, [#808](https://github.com/Omega-JS-Stack/omega/issues/808), so the `<brand.id>-<role>` rule derives clean repo names off it), url playground.omegajs.dev, a SUBDOMAIN so derived surfaces never claim the real omegajs.dev. Its THREE repos all derive from that id, none of them typed ([#883](https://github.com/Omega-JS-Stack/omega/issues/883), all under the one `repo: { provider: 'github', org: 'Omega-JS-Stack' }` block): `playground-omega` (the PRIVATE source repo, and the folder name here, whose `main` every `omega deploy` snapshots this folder onto, [docs/shared/deploys.md](deploys.md)), `playground-releases` (public: the desktop installers, the extension zips and the autoupdater feed), and `playground-web` (public: the BUILT site alone, one force-orphan commit on `gh-pages`, served by Pages at playground.omegajs.dev, which is what lets the source repo stay private on a free org). The third, the private `playground-rehearsal` snapshot, retired with the rehearsal itself ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)). Points at the real-but-throwaway Firebase project `omegajs-playground` (ITW-org-owned since 2026-07-11; sanctioned for live proofs: Blaze it, break it, delete it; it is TEST INFRASTRUCTURE, never production).
19
+ Renamed from omega-brand (Ian 2026-07-11, zero ambiguity). Born through the real wizard: id `playground` (shortened on 2026-09-10, [#808](https://github.com/Omega-JS-Stack/omega/issues/808), so the `<brand.id>-<role>` rule derives clean repo names off it), url playground.omegajs.dev, a SUBDOMAIN so derived surfaces never claim the real omegajs.dev. Its THREE repos all derive from that id, none of them typed ([#883](https://github.com/Omega-JS-Stack/omega/issues/883), all under the one `repo: { provider: 'github', org: 'Omega-JS-Stack' }` block): `playground-omega` (the PUBLIC source repo since 2026-09-24, so its Actions runs never count against the org storage allowance; the folder name here, whose `main` every `omega deploy` snapshots this folder onto, [docs/shared/deploys.md](deploys.md)), `playground-releases` (public: the desktop installers, the extension zips and the autoupdater feed), and `playground-web` (public: the BUILT site alone, one force-orphan commit on `gh-pages`, served by Pages at playground.omegajs.dev, which is what let the source repo stay private on a free org before it went public). The third, the private `playground-rehearsal` snapshot, retired with the rehearsal itself ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)). Points at the real-but-throwaway Firebase project `omegajs-playground` (ITW-org-owned since 2026-07-11; sanctioned for live proofs: Blaze it, break it, delete it; it is TEST INFRASTRUCTURE, never production).
20
20
 
21
21
  REBRANDED **OMEGA Playground** (Ian 2026-08-21, [#433](https://github.com/Omega-JS-Stack/omega/issues/433) — REVERSES the 2026-07-19 fiction ruling, whose invented brand read as a real product in the ad and analytics consoles). The doctrine now: the playground is openly the OMEGA test surface — "we can mess around here, nothing live actually matters" — so drift from the real omegajs.dev site reads as a demo doing its job, never as a broken copy. Infra identity unchanged (id, url, green accent, classy theme); name and voice are the only things that moved, and the catalog's display names stay placeholder demo copy. Cloud-side display names (the Meta pixel, the GA property + streams, the GCP project name) are the manager's follow-up.
22
22
 
@@ -26,6 +26,73 @@ gap tables, not this file. Converter TOOLING is
26
26
  input, not its implementation. The legacy repos stay read-only reference
27
27
  (AGENTS.md HARD RULE 1): nothing here asks you to change them.
28
28
 
29
+ ## 2026-09-25: one runtime shape on every package ([#945](https://github.com/Omega-JS-Stack/omega/issues/945))
30
+
31
+ Every package's default export is ONE ready-made instance, `omega`, and a consumer never
32
+ writes `new`. `initialize(options)` returns that instance on every surface; on the async
33
+ surfaces `omega.ready` is the same promise, so a module that did not call it can still await
34
+ it. The class is exported by name (`Omega`) for tests only. Accessors are properties, never
35
+ zero-arg methods. `auth.user` is always a `User` from `@omega.js/account`: the stored account
36
+ document as own fields, the derived facts as getters, and a signed-out `User` (`authenticated`
37
+ false, `plan` basic) instead of null. The `@omega.js/manager` package, its `omega manage` verb
38
+ and its banners keep their names.
39
+
40
+ | Contract | Old form | New form | Manual migration step |
41
+ |---|---|---|---|
42
+ | Backend consumer entry (`src/index.js`) | `const Manager = (new (require('@omega.js/backend'))).init(exports, { … });` then `const { functions } = Manager.libraries;` | `const omega = require('@omega.js/backend');`, then `omega.initialize({ … });`, and `module.exports = omega.functions;` as the file's last line | Rewrite the two lines and add the export line. A function of your own joins the map before it: `omega.functions.items = omega.firebase.functions.region(omega.project.resourceZone).https.onRequest((req, res) => omega.routes.run('items', { req, res }));` |
43
+ | Desktop consumer entries (`src/main.js`, `src/preload.js`, each `src/assets/js/components/<view>/index.js`) | `new (require('@omega.js/desktop/main'))().initialize()`, or a `manager` instance destructured after `initialize()` | `const omega = require('@omega.js/desktop/main');` (or `/preload`, `/renderer`), then `omega.initialize().then(() => { const { logger, windows } = omega; })` | Rewrite each entry; every `manager.` becomes `omega.` |
44
+ | Extension contexts (`background`, `popup`, `sidepanel`, `options`, `page`, `content`, `offscreen`) | `import Manager from '@omega.js/extension/popup'; const manager = new Manager(); await manager.initialize();`, with the client read as `manager.omega` | `import omega from '@omega.js/extension/popup'; await omega.initialize();`: the instance IS the runtime, and the four page contexts carry the client's modules on it (`omega.auth`, `omega.storage`, `omega.bindings`) | Rewrite each context's entry and drop the `omega` destructure. The bare `@omega.js/extension` import is gone: import the context entry |
45
+ | Web service worker (`src/service-worker.js`) | `import Manager from '@omega.js/web/service-worker'; const manager = new Manager(); manager.initialize()` | `import omega from '@omega.js/web/service-worker'; omega.initialize()`; the framework file is `sw/omega.js` | Rewrite the three lines |
46
+ | Web page, layout, section and global modules | `export default ({ manager, options }) => { }`, and `import omega from '@omega.js/client'` for the client | `export default async ({ omega, options }) => { }`; a module that needs the instance outside that call imports `@omega.js/web/runtime` | Rename the argument, and repoint every `@omega.js/client` default import at `@omega.js/web/runtime` |
47
+ | A theme's `_theme.js` | A side-effect module: its initializers ran on import | `export default async ({ omega, options }) => { }`, which the host calls with the booted instance | Move the theme's initializers into the default export |
48
+ | Web page features | `omega.library().showExitPopup()` and the rest of the `omega.library()` / `omega._library` bag | Properties of the web instance: `omega.appearance`, `omega.shell`, `omega.motion`, `omega.exitPopup.show()` (`exitPopup` is null when the popup is off) | Rewrite each `omega.library()` read to the property |
49
+ | Client accessors | `omega.auth()`, `omega.utilities().escapeHTML(x)`, `omega.firestore()`, `omega.bindings()`, `omega.storage()`, `omega.dom()`, `omega.sentry()`, and the rest | `omega.auth`, `omega.utilities.escapeHTML(x)`, `omega.firestore`, `omega.bindings`, `omega.storage`, `omega.dom`, `omega.sentry` | Drop the `()` after every module name; a missed site throws `is not a function` |
50
+ | `@omega.js/client` default export | A live singleton, imported anywhere | The base class `Omega` and no instance: web, the extension page contexts and the desktop renderer each export the one instance | Import the instance from the host framework (`@omega.js/web/runtime`, the extension context, `@omega.js/desktop/renderer`), never from `@omega.js/client` |
51
+ | The signed-in user | `auth.getUser()` (the Firebase profile, or null), `auth.isAuthenticated()`, `auth.resolveSubscription(account)` | `auth.user`, a `User`: `authenticated`, `uid`, `email`, `plan`, `active`, `trialing`, `cancelling`, `everPaid`, the stored fields (`user.roles`, `user.subscription`), and `user.profile.{displayName,photoURL,emailVerified}` | Replace each call with the property. A `if (user)` check reads `user.authenticated`, since `auth.user` is never null |
52
+ | The auth listener state | `auth.listen((state) => …)` with `{ user, account, resolved, accountDenied }` | `{ user, denied }`: `user` is the one `User`, `denied` is true only when rules refused the account read. `auth.reload()` re-reads the account and resolves with the new state | Read `state.account.*` and `state.resolved.*` off `state.user`, and `state.accountDenied` as `state.denied` |
53
+ | Bindings | Three roots: `auth.user` (the Firebase profile), `auth.account.*` and `auth.resolved.*` | ONE root, `auth.user`: `auth.user.plan`, `auth.user.active`, `auth.user.roles.admin`, `auth.user.profile.displayName`, `auth.user.profile.photoURL`. `usage` stays its own root | Rewrite each `data-omega-bind`: `auth.account.x` and `auth.resolved.x` read `auth.user.x`, `auth.user.displayName` reads `auth.user.profile.displayName`, and `@show auth.user` reads `@show auth.user.authenticated` |
54
+ | `FormManager` | `new FormManager('#form', options)`, the class importing the singleton | `new FormManager(omega, '#form', options)` | Pass the instance first |
55
+ | Click triggers | `registerTrigger('name', handler)` from `@omega.js/client/modules/triggers.js` | `omega.triggers.register('name', handler)` | Rewrite each registration; the class stays `omega-<name>` |
56
+ | Extension auth page and messaging | `openAuthPage()`; `messenger.onMessage = (message, sender, sendResponse) => { }`; `messenger` absent in background and offscreen | `omega.auth.openPage()`; `messenger.onMessage(handler)`, which returns the unsubscribe; `messenger` on every context, and background's `omega.auth.user` built from the whole account document | Rewrite the call and every `onMessage` assignment |
57
+ | Desktop main auth | `manager.omega` (the client bridge): `getCurrentUser()`, `onAuthChange(fn)`, `getResolvedPlan()`, `getResolvedRoles()` | `omega.auth`: `.user` (a `User` built from the whole account document), `.listen(fn)`, `.signOut()`, `.getIdToken()`, `.handleToken()` | `getCurrentUser()` reads `omega.auth.user`, `onAuthChange` is `listen`, and the plan and roles read `omega.auth.user.plan` and `omega.auth.user.roles` |
58
+ | Desktop renderer | `manager.omega` for the client; `manager.storage` and `window.desktop.*` for the main process | The renderer instance extends the client (`omega.auth`, `omega.firestore`); everything that crosses to main is `omega.desktop.{ipc,storage,theme,fontawesome,autoUpdater,analytics,context,usage,remoteConfig}`. `omega.storage` is the page store; the app store is `omega.desktop.storage` | Read the client off the instance, and the app store as `omega.desktop.storage` |
59
+ | Desktop integrations (`src/integrations/<tray,menu,context-menu>/index.js`) | `module.exports = ({ manager, tray }) => { }` | `module.exports = ({ omega, tray }) => { }` (menu and context menu likewise) | Rename the argument |
60
+ | Desktop test harness | Boot `inspect` bodies received `{ manager, expect, … }`; the harness wrote `__EM_TEST__` lines on stdout | `{ omega, expect, … }`; the prefix is `__OMEGA_TEST__` | Rename the argument, and any grep of a run's stdout |
61
+ | Desktop logger export | `require('@omega.js/desktop/lib/logger')` | `require('@omega.js/desktop/build').logger(name)` at build time, `omega.logger` at runtime | Repoint the require |
62
+ | Build modules (`@omega.js/desktop/build`, `@omega.js/extension/build`) | `const Manager = new (require('@omega.js/extension/build')); Manager.getConfig();`; hook context `{ manager, projectRoot, mode }` | `const build = require('@omega.js/extension/build'); build.getConfig();` (no class, no `new`); hook context `{ build, projectRoot, mode }` | Rewrite each hook's require and its destructure |
63
+ | Backend route handler | `async ({ Manager, ctx, analytics, usage, user, settings, libraries, utilities }) => { }` | `async ({ ctx, omega, user, data, usage, analytics }) => { }`; everything in the list is also on `ctx` | Rename `settings` to `data`, `Manager` to `omega`; `libraries.admin` reads `omega.firebase.admin`, `utilities` reads `omega.utilities` |
64
+ | Backend event and cron handlers | Events `({ Manager, ctx, libraries, user, context, change, snapshot })`; cron `({ Manager, ctx, context, libraries })` | Events `({ ctx, omega, user, context, change, snapshot })`; cron `({ ctx, omega, context })` | Same renames as a route |
65
+ | Backend `ctx` | `RouteContext`: `ctx.Manager`, `ctx.getUser()`, `ctx.request.user`, `ctx.resolvedUser`, `ctx.settings`, `ctx.ref`, `ctx.constant.pastTime` | `Context`: `ctx.omega`, `ctx.user` (a `User`), `ctx.data`, `ctx.req` / `ctx.res`; request services built on first read: `ctx.usage`, `ctx.analytics`, `ctx.email`, `ctx.ai`, `ctx.metadata(doc)` | Rewrite each read. `ctx.constant.pastTime` has no replacement |
66
+ | Backend instance | `Manager.libraries.admin` / `.functions`, the factories (`Manager.User()`, `Manager.Usage()`, `Manager.Analytics()`, `Manager.Settings()`, `Manager.Utilities()`, `Manager.AI()`, `Manager.Email()`, `Manager.Metadata()`), `Manager.Middleware(req, res).run('items', options)` | `omega.firebase.admin` / `omega.firebase.functions` (the SDK; `omega.functions` is the exported map); the request services on `ctx` and the process services on `omega` (`omega.utilities`, `omega.email`, `omega.ai`, `omega.storage({ name })`); `omega.routes.run('items', { req, res }, options)` | A route never constructs a service: read it off `ctx` or `omega` |
67
+ | Backend events from a consumer's own trigger | `Manager.EventMiddleware(payload).run('users/on-create')` | `omega.events.run('users/on-create', payload)`: the dispatcher the framework's own triggers use; a relative name loads `<cwd>/events/<name>.js`, a leading `/` is verbatim | Rewrite each trigger: `.onCreate((user, context) => omega.events.run('users/on-create', { user, context }))` |
68
+ | Backend consumer routes | A consumer route rode `omega_api` at `/omega/<name>` and won over a framework route of the same name; `omega.run(name, req, res, options)` and `omega.runEvent(name, payload)` | `omega_api` and `/omega/*` serve only the framework's routes and the MCP endpoint. A consumer route is its own function, `omega.routes.run(name, { req, res }, options)` in the region `omega.project.resourceZone`, behind its own hosting rewrite at its own path (`/notes`); `omega.events.run(name, payload)` runs a trigger's handler. A consumer MCP tool's `path` is the path as served (`/notes`) | Give each route its own function and a `{ "source": "{/notes,/notes/**}", "function": "notes" }` rewrite after the `omega_api` one, repoint every caller from `/omega/<name>` to `/<name>`, prefix each consumer tool `path` with `/`, and rename `omega.run`/`omega.runEvent` |
69
+ | Backend `initialize()` options | `setupFunctionsIdentity`, and the test-runner switches `initialize`, `setupFunctions`, `setupServer`, `log` | `identity`; the switches are gone (`OMEGA_TEST_RUNNER` decides) | Rename `setupFunctionsIdentity`; delete the switches |
70
+ | Backend pipeline options | `setupSettings`, `includeNonSchemaSettings`, `parseMultipartFormData` | `validate`, `includeUnknown`, `parseMultipart` | Rename each option passed to `omega.routes.run()` |
71
+ | Config environment mixin | `attachTo(Manager)` / `attachEnvironment` from `@omega.js/config` | Each instance calls `getEnvironment()` and the three checks directly | Delete the mixin call |
72
+ | Removed outright (backend) | `functions/_legacy/` and the `setupFunctionsLegacy` option, `functions/wrappers/mailchimp/addToList.js`, `helpers/api-manager.js` (`ApiManager`), `helpers/roles.js` (`Roles`), `Manager.install()`, `Manager.debug()`, `self.interface`, `libraries.localDatabase` and `initializeLocalStorage`, `fetchStats`, `server-manager.js` | Gone | Delete every reference; `ctx.usage` covers what `ApiManager` counted |
73
+ | The `/backend-manager/*` URL alias | The router stripped the prefix, the edge worker forwarded it, and the `omega_api` hosting rewrite listed `/backend-manager` and `/backend-manager/**` | Gone: a call to the old prefix answers 404 | Repoint every caller at `/omega/*`, and drop the two sources from the brand's `firebase.json` rewrite |
74
+ | Removed outright (frontends) | Client `omega.library()` / `omega._library`; extension `openAuthPage()`, the root `.` export, the `attachTo` mixins; desktop `manager.omega`, `wmBridge`, `./lib/logger`, `__EM_TEST__` | Gone; each row above names its replacement | Rewrite per the rows above |
75
+
76
+ ## 2026-09-25: one request-schema system ([#823](https://github.com/Omega-JS-Stack/omega/issues/823))
77
+
78
+ A route's schema file exports a function of the request and returns a plain field
79
+ declaration. ONE adapter turns that declaration into a zod schema and validates with it, so
80
+ zod is the only validator underneath and a schema stays a plain object until it runs: a split
81
+ on the plan, the query, the path or any other request fact is ordinary code
82
+ (`if (user.plan === 'pro') fields.limit.max = 200;`). Defaults still coerce and never reject.
83
+ The hand-rolled engine and the zod builder layer are both gone. The full vocabulary and the
84
+ split kinds: [schemas.md](../../packages/backend/docs/schemas.md).
85
+
86
+ | Contract | Old form | New form | Manual migration step |
87
+ |---|---|---|---|
88
+ | The schema function's argument | `({ ctx, user, data, method, headers, geolocation, client })` | `({ user, body, query, path, method, headers, geolocation })`: `user` is the caller's `User`, `body` and `query` are the raw parts (the route still receives their validated merge as `data`) | Read `data.x` as `body.x` or `query.x`, `user.subscription.product.id` as `user.plan`; a schema no longer reaches `ctx` or `client` |
89
+ | A field | `{ types: ['string'], default: '' }`, or a builder: `f.string({ default: '' })`, `f.number(…)`, `f.array(…)`, `f.passthrough(…)`, `f.multi(['string', 'number'], …)`, `f.any(…)` | `{ type: 'string', default: '' }`; `type` is one of `string number boolean array object any`, or a list of them | Rewrite each field; `f.passthrough` is `type: 'object'`, `f.multi(list)` is `type: list` |
90
+ | Nesting | A nested plain object, or `f.object({ … })` around the whole schema | The function returns the declaration itself; a nested object is `{ type: 'object', fields: { … } }`, array items are `of: { … }` | Return the map directly and wrap each nested group in `fields` |
91
+ | `required` | A boolean or a function (`required: () => isPremium`) | A boolean computed in the schema function (`required: isPremium`), never paired with `default` | Compute the condition before the declaration and assign the boolean |
92
+ | Path ids | A `default` computed from the request path, plus `min: 1` | `{ type: 'string', path: true }`, filled from the request path's trailing segments in declaration order | Replace the computed default with `path: true` |
93
+ | Field keys | `types`, `default`, `value`, `min`, `max`, `required`, `clean`, `sanitize`, `enum` | `type`, `default`, `value`, `min`, `max`, `required`, `clean`, `sanitize`, `enum`, plus `pattern` (a RegExp a sent value must match), `path`, `of` and `fields`; any other key throws, naming its dot-path | Rename `types` to `type`; nothing else to migrate |
94
+ | Removed | `helpers/schema-engine.js`, `helpers/schema-zod.js` and its `fields` builders, and a schema file exporting raw zod | Gone: `helpers/schema.js` is the one adapter | Delete the builder require from every schema file |
95
+
29
96
  ## 2026-09-14: the companion extension leaves `@omega.js/manager` ([#927](https://github.com/Omega-JS-Stack/omega/issues/927))
30
97
 
31
98
  The companion extension lived at `@omega.js/manager`'s `extension/`, a whole extension
@@ -306,7 +373,7 @@ Full contract: [config.md](config.md#the-repo-block-and-the-repos-it-derives-883
306
373
  | Classy gradient utilities | `.gradient-animated` (gradient shimmer) + `.gradient-grain` (noise overlay) on hero markup | `omega-dotgrid` — the masked dot backdrop v2 puts behind every hero — plus `data-omega-dotfield` where the animation was. Classy v2 ships zero gradients, so the old classes are silent no-ops | Codemod rule `gradient-utilities` converts both once ([#296](https://github.com/Omega-JS-Stack/omega/issues/296)); the pair on one element collapses to ONE `omega-dotgrid`. `.bg-gradient-*` names are NOT touched — v2 still neutralizes those to flat token paint |
307
374
  | Sass entry | `@use 'ultimate-jekyll-manager' as * with (…)` in `src/assets/css/main.scss`; page CSS files `@use`-ing themselves | `@use 'omega:main' with (…)`; the self-`@use` lines are gone (every layer's page sheet already loads, [#624](https://github.com/Omega-JS-Stack/omega/issues/624)) | `omega migrate`'s consumer-assets pass rewrites both; by hand it is a one-line edit plus deleting the self-`@use` lines |
308
375
  | JS entry | Seeded `src/assets/js/main.js` bootstrapping the manager | Deleted — core main + the boot runtime own it | `omega migrate` deletes an untouched seed and FLAGS a customized one; port custom logic into a page or section module |
309
- | Client bootstrap | `import webManager from 'web-manager'`; `window.Manager` global | `import omega from '@omega.js/client'`; no window global (the client is a singleton — import it) | Replace the import in every module; delete `window.Manager` references |
376
+ | Client bootstrap | `import webManager from 'web-manager'`; `window.Manager` global | `import omega from '@omega.js/web/runtime'`, the web instance; no window global | Replace the import in every module and delete `window.Manager` references. The accessor and auth shapes are in [one runtime shape](#2026-09-25-one-runtime-shape-on-every-package-945) |
310
377
  | Deploy | `npu sync --message='Deploy'` shell-out | `omega deploy` (plain git sync + `workflow_dispatch`, or the direct lane) | Replace the script; contract in [deploys.md](deploys.md) |
311
378
  | Version maintenance | Setup-time `ensureManagerVersion()` + peer-dependency auto-install | The explicit `omega update` verb | Stop expecting self-updates; run `npx omega update` (`--apply` to install) — [updates.md](updates.md) |
312
379
  | Charts | A page imported `chart.js` bare (it was a `@omega.js/web` dependency) and built its own `new Chart(canvas, config)` | `chart.js` is gone — the framework draws with TanStack Charts ([#772](https://github.com/Omega-JS-Stack/omega/issues/772)), reached ONLY through `__main_assets__/js/libs/charts.js` (`loadCharts`, `chartSlot`, `barChart`/`stackedBarChart`/`doughnutChart`/`lineChart`) | Replace the bare import and the hand-built config with the helpers, and the page's `<canvas>` with `chartSlot`'s markup (an SVG chart has no canvas). A page that genuinely needs the raw grammar imports `@tanstack/charts` bare instead — same framework-resolution rule, new name |
@@ -315,13 +382,13 @@ Full contract: [config.md](config.md#the-repo-block-and-the-repos-it-derives-883
315
382
 
316
383
  | Contract | Old form | New form | Manual migration step |
317
384
  |---|---|---|---|
318
- | Package + init | `require('backend-manager')`; `Manager.init({ backendManagerConfigPath: 'backend-manager-config.json' })` | `require('@omega.js/backend')`; the config loader discovers the file — the option is GONE | Swap the dependency and delete the `backendManagerConfigPath` option from `functions/index.js` |
385
+ | Package + init | `require('backend-manager')`; `Manager.init({ backendManagerConfigPath: 'backend-manager-config.json' })` | `const omega = require('@omega.js/backend');`, then `omega.initialize({ … });` and `module.exports = omega.functions;`; the config loader discovers the file, so there is no config-path option | Swap the dependency, rewrite the entry to that shape and delete the `backendManagerConfigPath` option. The handler and `ctx` shapes are in [one runtime shape](#2026-09-25-one-runtime-shape-on-every-package-945) |
319
386
  | Config | `functions/backend-manager-config.json` | `functions/config/omega.json5` | Key-by-key table in [config.md](config.md#backend-manager-functionsbackend-manager-configjson--functionsconfigomegajson5--done-checkpoint-19) |
320
387
  | Exported Cloud Functions | `bm_api`, `bm_signUpHandler`, `bm_createPost`, `bm_cronDaily`, … | `omega_api`, `omega_signUpHandler`, `omega_createPost`, `omega_cronDaily`, … | Deploy the new names, repoint every trigger/scheduler/webhook that names a function, then DELETE the orphaned `bm_*` functions from the Firebase project (a rename leaves the old ones running) |
321
- | Hosting rewrite | `{ source: '/backend-manager/**', function: 'bm_api' }` | `{ source: '{/omega,/omega/**,/backend-manager,/backend-manager/**,/mcp,/mcp/**,/.well-known/oauth-protected-resource,/.well-known/oauth-authorization-server,/authorize,/token,/register}', function: 'omega_api' }` | The next verb's `ensureTarget()` writes it (and removes duplicates); by hand, replace the rewrite and keep it FIRST in the list |
388
+ | Hosting rewrite | `{ source: '/backend-manager/**', function: 'bm_api' }` | `{ source: '{/omega,/omega/**,/mcp,/mcp/**,/.well-known/oauth-protected-resource,/.well-known/oauth-authorization-server,/authorize,/token,/register}', function: 'omega_api' }` | The next verb's `ensureTarget()` writes it (and removes duplicates); by hand, replace the rewrite and keep it FIRST in the list |
322
389
  | API dispatch | Command-based: `POST /backend-manager` with `{ command: 'user:sign-up', payload: {…} }` | REST: `POST /omega/user/sign-up` with the payload AS the body | Rewrite each caller: the command's `:` becomes a path segment, `payload` becomes the body. On the client, `omega.request('/omega/user/sign-up', { method: 'POST', body: {…} })` |
323
- | URL prefix | `/backend-manager/*` | `/omega/*` (also `/omega_api/*` on the direct function URL) | Repoint every first-party caller. The old prefix still resolves — a deliberate external-client alias, see [Deliberate compatibility that REMAINS](#deliberate-compatibility-that-remains) |
324
- | Per-request object | `BackendAssistant`; handler signature `module.exports = async ({ assistant, settings, analytics }) => …` | `RouteContext`; handler signature `module.exports = async ({ ctx, settings, analytics }) => …` | Rename the destructured argument and every `assistant.` call site (`ctx.respond`, `ctx.log`, `ctx.request`) in each custom route, event, and cron handler |
390
+ | URL prefix | `/backend-manager/*` | `/omega/*` (also `/omega_api/*` on the direct function URL) | Repoint every caller: the old prefix answers 404 |
391
+ | Per-request object | `BackendAssistant`; handler signature `module.exports = async ({ assistant, settings, analytics }) => …` | `Context` (`ctx`); handler signature `module.exports = async ({ ctx, omega, user, data, usage, analytics }) => …` | Rename the destructured argument and every `assistant.` call site (`ctx.respond`, `ctx.log`, `ctx.request`) in each custom route, event, and cron handler; `settings` is `data` |
325
392
  | Environment | `BACKEND_MANAGER_KEY`, `BACKEND_MANAGER_WEBHOOK_KEY`, `BEM_TEST_RUNNER`, `BEM_HTTPS_PORT` | `OMEGA_ADMIN_KEY`, `OMEGA_WEBHOOK_KEY`, `OMEGA_TEST_RUNNER`, `OMEGA_HTTPS_PORT` | Rename in `.env`, in CI secrets, and in anything that reads them. Values carry over unchanged |
326
393
  | AI provider keys | TWO names per provider: `BACKEND_MANAGER_OPENAI_API_KEY` (the company-wide fallback) beside a bare `OPENAI_API_KEY` (the brand's own), and the same pair for Anthropic. The provider preferred the bare one and fell back to the prefixed; `inferContact` read ONLY the prefixed one | ONE name per provider: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` ([#639](https://github.com/Omega-JS-Stack/omega/issues/639)). Every reader takes the bare name through the one env reader; the OMEGA-era `OMEGA_OPENAI_API_KEY` / `OMEGA_ANTHROPIC_API_KEY` twins are removed outright, with no dual-read | Rename `BACKEND_MANAGER_OPENAI_API_KEY` (or `OMEGA_OPENAI_API_KEY`) to `OPENAI_API_KEY` in `.env` and CI secrets, same for Anthropic, and delete the old rows. A key that served EVERY brand moves to the COMPANY `.env` under that same bare name — the cascade is the fallback, never a second key. An interactive `npx omega manage` asks for both once (the `ai` service) and writes them for you |
327
394
  | CLI | `bm` / `bem` / `backend-manager` / `mgr` bins | `omega` / `omg` / `mgr` (one dispatcher; a backend's `functions/` dir resolves to the backend CLI) | Replace the bin name in npm scripts and workflows |
@@ -352,7 +419,7 @@ Full contract: [config.md](config.md#the-repo-block-and-the-repos-it-derives-883
352
419
  | Config validation | EM's local validation util | The shared `@omega.js/config` schema, at boot and in the audit task | Nothing to move — but expect boot to report schema findings a legacy config silently carried, and fix them |
353
420
  | Preload global | `contextBridge.exposeInMainWorld('em', …)` → `window.em` | `window.desktop` | Rename every `window.em.*` call in renderer code |
354
421
  | Theme controls | `data-em-theme-set="system\|light\|dark"` | `data-omega-theme-set="system\|light\|dark"` | Rename the attribute in every view |
355
- | Client bridge | `web-manager-bridge.js`; `manager.webManager` in renderer entries | `client-bridge.js` + `@omega.js/client`; `manager.omega` | Rename the destructured property (`const { logger, ipc, storage, omega } = manager`) and every `webManager.` call to `omega.` |
422
+ | Client bridge | `web-manager-bridge.js`; `manager.webManager` in renderer entries | The renderer instance extends `@omega.js/client` (`omega.auth`, `omega.firestore`); main reads the account as `omega.auth` | Rewrite every `webManager.` call to `omega.` on the instance; the desktop shapes are in [one runtime shape](#2026-09-25-one-runtime-shape-on-every-package-945) |
356
423
  | Environment | `BACKEND_MANAGER_KEY` in the app's `.env` | `OMEGA_ADMIN_KEY`, resolved through the `.env` cascade (shell > local > brand root > company) | Rename the key; in a brand monorepo put the value at the brand root and leave the target's placeholder commented |
357
424
  | Bundler overrides | `config.em.webpack.externals`: an array of extra module names the desktop webpack build marked `commonjs2` external | GONE ([#737](https://github.com/Omega-JS-Stack/omega/issues/737)): the bundler is esbuild and there is no consumer-facing override key. The externals set is the framework's native-module list plus what the consumer's own `package.json` declares from it | Delete the key. Nothing replaces it: ESM-only dependencies BUNDLE (a CommonJS bundle keeps `import.meta.url`, [#906](https://github.com/Omega-JS-Stack/omega/issues/906)), and native modules stay external through the framework's own list. A consumer with a genuinely native module the list does not name raises it upstream: the list lives in `src/gulp/tasks/bundle.js` (`nativeExternals`) and grows there, so every brand gets the fix |
358
425
  | Gulp task name | `webpack` — `npm run gulp -- webpack`, and `[@omega.js/desktop:webpack]` in the logs | `bundle` — `npm run gulp -- bundle`, `[@omega.js/desktop:bundle]` ([#737](https://github.com/Omega-JS-Stack/omega/issues/737)) | Nothing for a normal consumer: the `build` / `package` / `publish` verbs are unchanged and nobody's npm scripts name the sub-task. Rename it in anything that invokes the gulp task directly, or greps `build.log` for the old tag |
@@ -367,7 +434,7 @@ Full contract: [config.md](config.md#the-repo-block-and-the-repos-it-derives-883
367
434
  | CLI | `xm` / `bxm` / `ext` / `browser-extension-manager` / `mgr` bins | `omega` / `omg` / `mgr` | Replace the bin name in npm scripts and workflows |
368
435
  | Config | `config/browser-extension-manager.json` | `config/omega.json5` (`targets.extension: {}` — key presence enables the target) | Key-by-key table in [config.md](config.md#browser-extension-manager-configbrowser-extension-managerjson--configomegajson5--done-checkpoint-20) |
369
436
  | Analytics secret | `analytics.providers.google.secret` in the config file | `.env` → `GOOGLE_ANALYTICS_SECRET` (the loader hard-fails secret-shaped config keys) | Move the value to `.env`; the build snapshot bakes it exactly as before |
370
- | Runtime singleton | `import webManager from 'web-manager'`; `manager.webManager` | `import omega from '@omega.js/client'`; `manager.omega` | Swap the import and rename the property in every context (background, popup, options, sidepanel, content scripts) |
437
+ | Runtime singleton | `import webManager from 'web-manager'`; `manager.webManager` | `import omega from '@omega.js/extension/<context>'`: the context's instance carries the client's modules | Swap the import in every context (background, popup, options, sidepanel, content scripts) and read the client off `omega`; the extension shapes are in [one runtime shape](#2026-09-25-one-runtime-shape-on-every-package-945) |
371
438
  | DOM bindings | `data-wm-bind` | `data-omega-bind` | Rename the attribute in every view |
372
439
  | Cross-context messages | `{ command: 'bxm:syncAuth' }`, `{ command: 'bxm:signOut' }` | `{ command: 'omega:syncAuth' }`, `{ command: 'omega:signOut' }` | Rename in any custom `runtime.onMessage` handler or sender the extension ships |
373
440
  | Bundler | webpack 5 + `babel-loader` + `@babel/preset-env`, with per-lane code splitting (`*.chunk.<hash>.js` plus `.LICENSE.txt` sidecars beside every bundle) | esbuild through @omega.js/devkit's `bundle()` wrapper ([#738](https://github.com/Omega-JS-Stack/omega/issues/738)). No consumer-facing bundler override key existed and none was added; there are no chunks and no license sidecars — every entry is ONE self-contained iife — and the syntax floor is esbuild's `target`, `chrome88, firefox91` (the MV3 minimums), not a browserslist guess | Nothing in a normal project: entry filenames (`assets/js/components/<name>.bundle.js`) are unchanged, and the manifest / views ask for the same paths. Two things to check: anything that referenced a chunk file BY NAME (nothing generated does), and any code that imported a package @omega.js/extension merely carries TRANSITIVELY — only the framework's DECLARED dependencies resolve from the framework now, so declare that package yourself or raise it upstream ([#87](https://github.com/Omega-JS-Stack/omega/issues/87)) |
@@ -378,11 +445,11 @@ Full contract: [config.md](config.md#the-repo-block-and-the-repos-it-derives-883
378
445
 
379
446
  | Contract | Old form | New form | Manual migration step |
380
447
  |---|---|---|---|
381
- | Package + import | `import webManager from 'web-manager'` (npm `web-manager`) | `import omega from '@omega.js/client'` — still a singleton default export | Swap the dependency and every import; the module API (`auth()`, `firestore()`, `bindings()`, `initialize(configuration)`) carries over unchanged |
382
- | Global | `window.Manager` / a page-attached `webManager` | No window global — import the singleton wherever it is needed | Delete the window assignments and the code that reads them |
448
+ | Package + import | `import webManager from 'web-manager'` (npm `web-manager`) | `@omega.js/client`, the browser base class `Omega`; each host framework exports the one instance | Swap the dependency and import the host's instance. The modules read as properties (`omega.auth`, `omega.firestore`, `omega.bindings`), and `initialize(configuration)` carries over: [one runtime shape](#2026-09-25-one-runtime-shape-on-every-package-945) |
449
+ | Global | `window.Manager` / a page-attached `webManager` | No window global: import the host framework's instance wherever it is needed | Delete the window assignments and the code that reads them |
383
450
  | DOM bindings | `data-wm-bind` | `data-omega-bind` | Codemod rule `client-markup` (templates, consumer JS, and section `.json` descriptors alike) |
384
451
  | Sign-out hook | `.auth-signout-btn` class, hardcoded in the auth module | The generic `omega-signout` click trigger (`registerTrigger('signout', …)` → class `omega-signout`) | Codemod rule `client-markup` renames the class everywhere it appears; custom behavior registers its own trigger instead of patching auth |
385
- | Device module | `webManager.usage()` | `omega.device()` | Rename the call sites (`usage.js` became `device.js` verbatim) |
452
+ | Device module | `webManager.usage()` | `omega.device` | Rename the call sites (`usage.js` became `device.js` verbatim) |
386
453
  | Configuration payload | The site emitted a `web_manager` block into `window.Configuration` | The `client` section of `OMEGA_BUILD_JSON.config` (#894, the row above) | Config-side rename: see the UJM row and [config.md](config.md); nothing dual-reads the old key |
387
454
  | Version-check manifest | The client probed `/build.json` then `/@output/build/build.json`, reading `data.timestamp` OR `data['npm-build'].timestamp` | One fetch of the site's `build.json` — mounted under the page's `data-omega-path-prefix` stamp on a site served from a URL path ([#364](https://github.com/Omega-JS-Stack/omega/issues/364)), `/build.json` at the domain root — and one read of `data.timestamp` (the omega web build emits exactly this) | Nothing to do on a migrated site. A site still serving the old path or the `npm-build` wrapper logs "No timestamp found in build.json" and never auto-reloads on a new deploy |
388
455
 
@@ -610,7 +677,7 @@ fallback would be the exact bug this removes.
610
677
  | Contract | Old form | New form | Manual migration step |
611
678
  |---|---|---|---|
612
679
  | ASM group ids | `GROUPS` in `packages/backend/src/manager/libraries/email/constants.js` — seven literal ids compiled into the framework | `marketing.campaigns.providers.sendgrid.groups.<key>` in the brand's `config/omega.json5`, one integer per key (`orders`, `hello`, `account`, `marketing`, `security`, `newsletter`, `internal`) | **Per brand, once**: run `npx omega manage` (or `--service=campaigns`) — the campaigns service creates any group the account is missing and writes all seven ids into `config/omega.json5`. By hand: read the ids from SendGrid → Suppressions → Unsubscribe Groups and write the block yourself. The ensure matches by NAME, so on an account whose groups carry legacy-prefixed names ("BEM - Order Updates"), RENAME them to the canonical "OMEGA - " names FIRST (ids never change, so legacy sends keep working) — otherwise the ensure creates a duplicate set and unsubscribe state splits (this happened once on the shared account, repaired 2026-08-29). A brand on its own fresh account just gets its own groups. Until the block exists, every send throws instead of mailing under a foreign group |
613
- | `prepare.resolveSender()` signature | `resolveSender({ sender, from, group }, brand, brandDomain)` | `resolveSender({ sender, from, group }, brand, brandDomain, Manager)` — the fourth argument carries the config the ids live in | Only affects code calling `prepare.js` directly: pass `Manager` as the fourth argument. `group:` still accepts a raw numeric id AND now accepts a group KEY, which resolves through config |
680
+ | `prepare.resolveSender()` signature | `resolveSender({ sender, from, group }, brand, brandDomain)` | `resolveSender({ sender, from, group }, brand, brandDomain, omega)`: the fourth argument carries the config the ids live in | Only affects code calling `prepare.js` directly: pass `omega` as the fourth argument. `group:` still accepts a raw numeric id AND now accepts a group KEY, which resolves through config |
614
681
 
615
682
  ## `brand.subdomains` becomes web targets ([#588](https://github.com/Omega-JS-Stack/omega/issues/588))
616
683
 
@@ -704,7 +771,7 @@ retry; its stale `usage/{uid}.oauth2` session clears with the daily clean.
704
771
  | Config section | `oauth2: { <provider>: {…} }` | `connections: { <provider>: {…} }` — the per-provider block is byte-identical, with ONE change of meaning ([#793](https://github.com/Omega-JS-Stack/omega/issues/793)): a PACKAGED provider (google, discord, spotify, twitch, kick) is `enabled: false` in the framework defaults, so an entry that relied on "absent means enabled" is now off | Rename the key in `config/omega.json5`, and add `enabled: true` to each packaged provider's block that does not already carry it — a brand's OWN provider is unchanged (present unless `enabled: false`). A config still carrying `oauth2` is a retired-key error naming the move ([config.md](config.md#retired-keys-fail-loudly)) |
705
772
  | Credentials | `OAUTH2_<PROVIDER>_CLIENT_ID` / `OAUTH2_<PROVIDER>_CLIENT_SECRET` | `CONNECTIONS_<PROVIDER>_CLIENT_ID` / `CONNECTIONS_<PROVIDER>_CLIENT_SECRET` | **Rename the pair by hand** in the brand `.env` (and every `.env.<environment>` overlay), and in the CI secrets any workflow injects them from. A layer still carrying the Google pair FAILS the load naming the new name ([#845](https://github.com/Omega-JS-Stack/omega/issues/845)): they are registered retired env names ([config.md](config.md#retired-env-keys-srcenv-retiredjs)), spelled out one provider at a time. For any other provider there is no check, and the env schema does not know the old name, so the provider reads an empty client id and its authorize leg 500s |
706
773
  | Provider console redirect URI | `<websiteUrl>/oauth2` | `<websiteUrl>/connections/callback` | **Register the new URI at every provider** whose block the brand declares (Google Cloud console, Discord developer portal, Spotify dashboard, …). Add it BESIDE the old one, deploy, then remove the old one — a redirect URI is matched exactly, so a deploy ahead of the console edit breaks every link attempt |
707
- | Brand provider module | `targets/backend/src/oauth2/<name>.js` | `targets/backend/src/connections/<name>.js` | Move the directory. The lane resolves `${Manager.cwd}/connections/` first and the package's own second, exactly as before |
774
+ | Brand provider module | `targets/backend/src/oauth2/<name>.js` | `targets/backend/src/connections/<name>.js` | Move the directory. The lane resolves `${omega.cwd}/connections/` first and the package's own second, exactly as before |
708
775
  | Website callback page | the `/oauth2` default page (`blueprint/auth/oauth2`) | `/connections/callback` (`blueprint/connections/callback`) | **Nothing for a brand that never overrode it.** A brand carrying its own copy moves it to the new path and layout name; `/connections` itself stays free for a future listing page |
709
776
  | Provider module shape ([#793](https://github.com/Omega-JS-Stack/omega/issues/793)) | `verifyIdentity(tokenizeResult, Manager, ctx, uid)`, `buildAuthorizeUrl(context)`, `revokeToken(token, {…})`, `verifyConnection(refreshToken, {…})`, `authParams`, `urls.tokenize` + `urls.refresh` + `urls.status`, and a copy of the "already connected" query inside `verifyIdentity` | ONE context object for every step, called as a method on the module: `identity(context)` (required, → `{ id, … }`), `authorize`, `exchange`, `refresh`, `revoke`, `status`; `params` for extra authorize params; ONE `urls.token`; `urls.revoke` optional; `urls.status` deleted | **Per provider module, by hand** (switchboard's YouTube provider is the known one outside this repo): rename the six members, fold `urls.tokenize`/`urls.refresh` into `urls.token`, rename `authParams` → `params`, drop `urls.status`, read `tokenizeResult`/`Manager`/`ctx`/`uid`/`clientId` off the one `context` argument, and DELETE the uniqueness query — the route owns it now and matches on `identity.id`, so the step just answers the identity, with a string `id`. A module missing `identity()` or `urls.token` throws at load naming the file |
710
777
 
@@ -842,7 +909,6 @@ retires when its named condition is met, and never unilaterally.
842
909
 
843
910
  | Compatibility | Who still speaks it | Retirement condition |
844
911
  |---|---|---|
845
- | The `/backend-manager/*` URL alias — the backend router's prefix strip and its Cloudflare edge-worker twin (`packages/manager/src/services/edge/workers/omega-api-proxy.js`) | In-the-wild clients of migrated brands: shipped apps, third-party integrations, and pages still calling the old path | Every known caller moved to `/omega/*` and the alias shows no traffic |
846
912
  | `backendManagerKey` sent in outbound request bodies (`process.env.OMEGA_ADMIN_KEY` under the OLD field name) | Legacy-BEM parent deployments, the Ghostii API, and ITW's `wrapper` Cloud Function — all still reading that field | Each upstream migrates to the new stack and accepts the `omega-admin-key` header; fix per upstream, never unilaterally |
847
913
  | The Ghostii flat-article response fallback | api.ghostii.ai, whose production backend runs legacy BEM and can return the flat field shape | Ghostii returns only the structured shape |
848
914
  | The `gatherings/online` sign-out leg | Old somiibo / electron-manager desktop clients that still write that RTDB path | Those app versions are out of circulation |
@@ -338,8 +338,8 @@ targets: {
338
338
  and runs locally on the emulator suite. Everything OMEGA does today.
339
339
  - **`'custom'`** — the SAME backend (same routes, same schemas, same auth middleware, same
340
340
  helpers, same `.env`) served by its Express app on `process.env.PORT`, for a container host
341
- (Render & co). `Manager.init()` reads the mode off this key, so a brand's `src/index.js` is
342
- unchanged; an explicit `init` option still wins.
341
+ (Render & co). `initialize()` reads the mode off this key, so a brand's `src/index.js` is
342
+ unchanged; an explicit `projectType` option still wins.
343
343
  - **What custom mode removes is the Firebase LANE, not Firebase**: no Functions deploy, no
344
344
  emulator, no emulator test run — those four verbs refuse loudly and name their replacement
345
345
  ([docs/backend/index.md](../backend/index.md)). `firebase-admin` still loads, so a custom
@@ -752,9 +752,8 @@ Who derives from it:
752
752
  `src/environment.js` is the ONE environment module every OMEGA target answers
753
753
  from ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)). Four calls,
754
754
  one implementation, one call form everywhere: `getEnvironment()` plus
755
- `isDevelopment()` / `isProduction()` / `isTesting()`, mixed into a framework's
756
- Manager with `attachTo()` (exported from the package index as
757
- `attachEnvironment`). It requires NOTHING, so it is bundled into browser
755
+ `isDevelopment()` / `isProduction()` / `isTesting()`, called directly by every
756
+ framework's `omega` instance and build module. It requires NOTHING, so it is bundled into browser
758
757
  artifacts (the desktop renderer, every extension bundle) and vendored into
759
758
  `@omega.js/client`, which reaches the same four through its own methods.
760
759
 
@@ -762,24 +761,21 @@ artifacts (the desktop renderer, every extension bundle) and vendored into
762
761
  context, which can read neither env nor files, it is `config.environment`, the
763
762
  build fact every surface already bakes into `OMEGA_BUILD_JSON.config`
764
763
  ([#896](https://github.com/Omega-JS-Stack/omega/issues/896)), reached as
765
- `this.config.environment` off the Manager the call is made on. Nothing else is
764
+ `this.config.environment` off the instance the call is made on. Nothing else is
766
765
  consulted: no `app.isPackaged`, no `manifest.update_url`, no `NODE_ENV`, no
767
766
  terminal sniffing.
768
767
 
769
- **NO DEFAULT.** A context with no input throws and names the variable. The four
770
- framework copies this replaced each had their own default and they DISAGREED:
771
- desktop answered `production` with no signal while the extension answered
772
- `development`, so a desktop `npm start` bundled itself as a production artifact.
773
- @omega.js/client seeded `environment: 'production'` into its own config defaults,
774
- and web's browser Manager defaulted to `development`.
768
+ **NO DEFAULT.** A context with no input throws and names the variable. A guessed
769
+ environment is how a dev build ships as production and how a production build talks
770
+ to an emulator, and both failures stay silent until a user finds them.
775
771
 
776
772
  **`setEnvironment(value)` is the only writer**, so a fourth word can never reach
777
773
  the variable. Who calls it, and with what:
778
774
 
779
775
  | Lane | Names |
780
776
  |---|---|
781
- | `@omega.js/backend`'s boot (`Manager.init`, right after the `.env` cascade loads) | `envEnvironment()`, the AMBIENT answer (below) |
782
- | `@omega.js/desktop` / `@omega.js/extension` `src/build.js`, at load | `buildLaneEnvironment(Manager.isBuildMode())`: `production` under `OMEGA_BUILD_MODE` (which WINS over an inherited value, so a production build spawned from a test run still bakes production), else an already-named value, else `development`. That rule is ONE exported helper beside `setEnvironment`, not a copy of the expression per framework, and those two build lanes are its only callers |
777
+ | `@omega.js/backend`'s boot (`initialize()`, right after the `.env` cascade loads) | `envEnvironment()`, the AMBIENT answer (below) |
778
+ | `@omega.js/desktop` / `@omega.js/extension` `src/build.js`, at load | `buildLaneEnvironment(isBuildMode())`: `production` under `OMEGA_BUILD_MODE` (which WINS over an inherited value, so a production build spawned from a test run still bakes production), else an already-named value, else `development`. That rule is ONE exported helper beside `setEnvironment`, not a copy of the expression per framework, and those two build lanes are its only callers |
783
779
  | `@omega.js/desktop`'s main process, at `initialize()` | the `environment` its baked config carries, for a packaged app that has no parent lane |
784
780
  | `@omega.js/web`'s verbs | `production` for `omega build`, `development` for `omega dev`, `testing` for `omega test` |
785
781
  | the desktop test runners, the extension `test` verb | `testing` |
@@ -828,7 +824,7 @@ byte-identical to the pre-N7 behavior (no bumping, no artifacts).
828
824
  livereload 35729, cdp 9222). **This map is the ONLY source of those numbers**
829
825
  ([#834](https://github.com/Omega-JS-Stack/omega/issues/834)): @omega.js/client
830
826
  carried four of them plus a classic dev origin, @omega.js/desktop's url-helpers
831
- and client-bridge three more, and @omega.js/extension's url-helpers and
827
+ and the desktop auth lib three more, and @omega.js/extension's url-helpers and
832
828
  background worker two, each as a last-resort fallback "for a build made with no
833
829
  stack up". Every one of those is gone. The numbers reach a browser exactly one
834
830
  way: each surface bakes the map into its build json with the classics as the
@@ -1352,7 +1348,7 @@ decision, declared once and made once, and it is DELIVERED one way:
1352
1348
  rest are facts about the BUILD and never part of the client contract. `mode` is the same
1353
1349
  THREE keys everywhere, `{ environment, build, publish }`: the build's verdict, whether it
1354
1350
  was a build rather than a dev/watch run, and whether it publishes. A surface's own extra
1355
- verdicts (desktop's `server`) stay inside that surface's Manager.
1351
+ verdicts (desktop's `server`) stay inside that surface's build module.
1356
1352
  - **The delivery** is ONE FILE, `build.js` at the artifact's web root
1357
1353
  ([#743](https://github.com/Omega-JS-Stack/omega/issues/743), Ian 2026-09-12: "make it
1358
1354
  same shape and consumption everywhere as much as possible"). Two plain statements, written
@@ -1367,7 +1363,7 @@ decision, declared once and made once, and it is DELIVERED one way:
1367
1363
  Every HTML shell loads it with one script tag, FIRST (web's `core/_includes/core/head.html`
1368
1364
  at `/build.js`, desktop's page template at `../../build.js`, the extension's at
1369
1365
  `/build.js`), and every worker with one `importScripts('/build.js')` line (web's
1370
- `sw/manager.js`, the extension's `background.js`). No bundle carries a copy: the esbuild
1366
+ `sw/omega.js`, the extension's `background.js`). No bundle carries a copy: the esbuild
1371
1367
  banners and defines the browser bundles grew are retired, and so is web's inline foot
1372
1368
  script. The `dev` map is its own statement because `omega dev` REWRITES that one line per
1373
1369
  request (#346), so a site built before the emulator came up still serves the map of the
@@ -1400,7 +1396,7 @@ always applies.
1400
1396
  ([#290](https://github.com/Omega-JS-Stack/omega/issues/290)). A brand target cannot require
1401
1397
  this private package at runtime, so a framework that owns a derivation publishes its
1402
1398
  ANSWER on the runtime config object the target already holds, under `resolved.*`: the
1403
- backend's `Manager.config.resolved.sourceRepo` carries `{ owner, name, slug }`, the brand's
1399
+ backend's `omega.config.resolved.github` carries `{ owner, name, slug }`, the brand's
1404
1400
  SOURCE repo as one finished value. The derivations themselves stay here (`src/repo.js`):
1405
1401
  one implementation, called by the framework, so no brand re-implements the rules and
1406
1402
  drifts from them. New derived values join a framework's `resolved` group as real brand
@@ -1433,6 +1429,13 @@ of any kind exists: a repo name that must differ is a brand id that must differ.
1433
1429
  | Releases | `releasesRepo(config)` → `<repo.org>/<brand.id>-releases` | always public (the desktop updater polls it with no token) | every target's built artifacts: desktop installers, the extension's zips, tagged per target |
1434
1430
  | Website, one per GitHub-hosted web target | `websiteRepo(config, name)` → `<repo.org>/<brand.id>-<target name>` | private only when the brand is private AND the org's plan allows Pages from a private repo, else public, and the manage walk says which | the BUILT site only, one force-orphan commit on `gh-pages`, served by Pages at the target's url |
1435
1431
 
1432
+ **`repoDrift(originSlug, config)`** is the one comparison of a checkout's `origin` with
1433
+ the source repo ([#934](https://github.com/Omega-JS-Stack/omega/issues/934)): the whole
1434
+ slug, case-insensitive, null when they agree or the config derives no source repo, else
1435
+ the line `origin is <slug> but config derives <derived>: fix repo.org in
1436
+ config/omega.json5 or move the repo`. The boot prelude prints it; `omega manage` and
1437
+ `omega deploy` refuse with it ([deploys.md](deploys.md#the-origin-gate-934)).
1438
+
1436
1439
  **Visibility lives in the brand root's `package.json`**, never in omega.json5
1437
1440
  (`brandVisibility(brandRoot)`): `private: true` or the field ABSENT is a private brand
1438
1441
  (every brand monorepo is private by default, Ian 2026-09-11), and only a literal `false`
@@ -1821,8 +1824,8 @@ resolved config would fire on its own answer. The by-hand step, and the `parent`
1821
1824
  | `parent` | RETIRED outright ([#677](https://github.com/Omega-JS-Stack/omega/issues/677)): a URL or `'self'` becomes **`company: { id }`**, and `parent: false` becomes **`company: { webhooks: false }`**. The key itself fails the load now, naming its new home |
1822
1825
 
1823
1826
  Notes: @omega.js/backend's framework-defaults layer is `templates/config/omega.json5` resolved through
1824
- the same loader and passed as `options.defaults`; `Manager.init()`'s
1825
- `backendManagerConfigPath` option is gone (the loader discovers the file); boot warns on
1827
+ the same loader and passed as `options.defaults`; `initialize()` takes no
1828
+ `backendManagerConfigPath` option (the loader discovers the file); boot warns on
1826
1829
  schema findings, `npx omega test`'s target checks are the hard audit. The sandbox brand dogfoods the full
1827
1830
  hierarchy: shared sections live in `brands/sandbox-brand/config/omega.json5` (brand level),
1828
1831
  the backend target's local file carries only `targets.backend`.
@@ -1838,7 +1841,7 @@ the backend target's local file carries only `targets.backend`.
1838
1841
  | `analytics.providers.google.secret` | **`.env` → `GOOGLE_ANALYTICS_SECRET`** (secrets never in omega.json5; loader hard-fails) |
1839
1842
  | *(no extension-specific keys yet)* | `targets.extension: {}` — presence = enabled; extension-specific settings land here |
1840
1843
 
1841
- Notes: `Manager.getConfig()` returns the RESOLVED config (missing file → `{}`; schema
1844
+ Notes: `build.getConfig()` (`require('@omega.js/extension/build')`) returns the RESOLVED config (missing file → `{}`; schema
1842
1845
  findings warn once per process — BXM has no separate audit surface). The build snapshot
1843
1846
  (`OMEGA_BUILD_JSON`, the artifact's one `build.js`) bakes `GOOGLE_ANALYTICS_SECRET` from the environment at
1844
1847
  build time, same value flow as before. `bxm setup` scaffolds + merges `config/omega.json5`