@omega.js/desktop 0.52.0 → 0.54.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 (345) hide show
  1. package/README.md +53 -48
  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-run.js +4 -1
  26. package/dist/cli.js +3 -3
  27. package/dist/commands/build.js +2 -2
  28. package/dist/commands/cdp/capture.js +2 -2
  29. package/dist/commands/cdp/client.js +1 -1
  30. package/dist/commands/cdp/quit.js +2 -2
  31. package/dist/commands/cdp/relaunch.js +2 -2
  32. package/dist/commands/cdp/theme.js +1 -1
  33. package/dist/commands/cdp.js +1 -1
  34. package/dist/commands/clean.js +4 -5
  35. package/dist/commands/deploy.js +4 -4
  36. package/dist/commands/dev.js +25 -0
  37. package/dist/commands/finalize-release.js +4 -4
  38. package/dist/commands/launch.js +2 -2
  39. package/dist/commands/lib/deploy-precheck.js +3 -3
  40. package/dist/commands/lib/ensure-target.js +18 -23
  41. package/dist/commands/lib/migrate.js +17 -0
  42. package/dist/commands/logs.js +1 -1
  43. package/dist/commands/package.js +2 -2
  44. package/dist/commands/publish.js +2 -2
  45. package/dist/commands/release.js +4 -4
  46. package/dist/commands/runner.js +11 -11
  47. package/dist/commands/sign-windows.js +5 -5
  48. package/dist/commands/test.js +8 -8
  49. package/dist/commands/update.js +7 -6
  50. package/dist/commands/validate-certs.js +4 -4
  51. package/dist/commands/version.js +3 -3
  52. package/dist/defaults/.github/workflows/build.yml +18 -18
  53. package/dist/defaults/_.gitignore +0 -2
  54. package/dist/defaults/_mas/README.md +3 -3
  55. package/dist/defaults/config/certs/README.md +1 -1
  56. package/dist/defaults/config/omega.json5 +43 -43
  57. package/dist/defaults/docs/README.md +3 -3
  58. package/dist/defaults/gulpfile.js +1 -1
  59. package/dist/defaults/hooks/build/post.js +2 -2
  60. package/dist/defaults/hooks/build/pre.js +2 -2
  61. package/dist/defaults/hooks/deploy/pre.js +1 -1
  62. package/dist/defaults/hooks/notarize/post.js +2 -2
  63. package/dist/defaults/hooks/release/post.js +2 -2
  64. package/dist/defaults/hooks/release/pre.js +2 -2
  65. package/dist/defaults/src/assets/js/components/about/index.js +3 -5
  66. package/dist/defaults/src/assets/js/components/main/index.js +3 -5
  67. package/dist/defaults/src/assets/js/components/settings/index.js +3 -5
  68. package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
  69. package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
  70. package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
  71. package/dist/defaults/src/integrations/context-menu/index.js +13 -13
  72. package/dist/defaults/src/integrations/menu/index.js +8 -8
  73. package/dist/defaults/src/integrations/tray/index.js +12 -12
  74. package/dist/defaults/src/main.js +5 -7
  75. package/dist/defaults/src/preload.js +4 -6
  76. package/dist/defaults/test/README.md +5 -5
  77. package/dist/defaults/test/_init.js +1 -1
  78. package/dist/gulp/main.js +9 -10
  79. package/dist/gulp/tasks/audit.js +11 -14
  80. package/dist/gulp/tasks/build-config.js +8 -8
  81. package/dist/gulp/tasks/bundle.js +16 -16
  82. package/dist/gulp/tasks/defaults.js +3 -3
  83. package/dist/gulp/tasks/distribute.js +2 -2
  84. package/dist/gulp/tasks/html.js +9 -9
  85. package/dist/gulp/tasks/package-quick.js +3 -3
  86. package/dist/gulp/tasks/package.js +3 -3
  87. package/dist/gulp/tasks/release.js +3 -3
  88. package/dist/gulp/tasks/sass.js +6 -6
  89. package/dist/gulp/tasks/serve.js +4 -4
  90. package/dist/hooks/notarize-artifacts.js +1 -1
  91. package/dist/hooks/notarize.js +1 -1
  92. package/dist/index.js +5 -8
  93. package/dist/lib/_environment-mixin.js +50 -0
  94. package/dist/lib/_lifecycle-mixin.js +45 -0
  95. package/dist/lib/analytics.js +33 -35
  96. package/dist/lib/app-state.js +14 -14
  97. package/dist/lib/auth-flow.js +18 -18
  98. package/dist/lib/auth-persistence.js +12 -12
  99. package/dist/lib/auth.js +421 -0
  100. package/dist/lib/auto-updater.js +49 -49
  101. package/dist/lib/context-menu.js +13 -13
  102. package/dist/lib/context.js +19 -19
  103. package/dist/lib/deep-link.js +34 -34
  104. package/dist/lib/fontawesome.js +5 -5
  105. package/dist/lib/ipc.js +4 -4
  106. package/dist/lib/menu.js +25 -25
  107. package/dist/lib/protocol.js +5 -5
  108. package/dist/lib/remote-config.js +22 -22
  109. package/dist/lib/remote-scripts.js +21 -21
  110. package/dist/lib/restart-manager/index.js +29 -29
  111. package/dist/lib/restart-manager/install.js +1 -1
  112. package/dist/lib/restart-manager/protocol.js +1 -1
  113. package/dist/lib/sign-helpers/exec-with-limit.js +1 -1
  114. package/dist/lib/sign-helpers/sign-events.js +1 -1
  115. package/dist/lib/startup.js +18 -13
  116. package/dist/lib/storage.js +10 -10
  117. package/dist/lib/templating.js +16 -16
  118. package/dist/lib/theme.js +10 -10
  119. package/dist/lib/tray.js +27 -27
  120. package/dist/lib/usage.js +11 -11
  121. package/dist/lib/window-manager.js +26 -26
  122. package/dist/main.js +399 -483
  123. package/dist/preload.js +236 -176
  124. package/dist/renderer.js +417 -391
  125. package/dist/test/fixtures/consumer-app/config/omega.json5 +1 -1
  126. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +8 -11
  127. package/dist/test/fixtures/consumer-app/src/main.js +5 -7
  128. package/dist/test/fixtures/consumer-app/src/preload.js +2 -2
  129. package/dist/test/harness/boot-entry.js +22 -20
  130. package/dist/test/harness/main-entry.js +31 -30
  131. package/dist/test/harness/renderer-entry.js +5 -5
  132. package/dist/test/harness/renderer-preload.js +137 -141
  133. package/dist/test/index.js +10 -10
  134. package/dist/test/runner.js +2 -2
  135. package/dist/test/runners/boot.js +7 -6
  136. package/dist/test/runners/electron.js +3 -2
  137. package/dist/test/runners/render-event.js +2 -2
  138. package/dist/test/suites/boot/consumer-app-boots.test.js +57 -26
  139. package/dist/test/suites/boot/restart-manager.test.js +2 -2
  140. package/dist/test/suites/boot/storage-bundled.test.js +5 -5
  141. package/dist/test/suites/boot/theme.test.js +13 -13
  142. package/dist/test/suites/build/audit.test.js +21 -8
  143. package/dist/test/suites/build/auth-persistence-resolve.test.js +6 -6
  144. package/dist/test/suites/build/boot-fixture.test.js +2 -2
  145. package/dist/test/suites/build/boot-runner-timeout.test.js +6 -5
  146. package/dist/test/suites/build/brand-scss.test.js +1 -1
  147. package/dist/test/suites/build/build-json-bake.test.js +1 -1
  148. package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
  149. package/dist/test/suites/build/cli.test.js +30 -2
  150. package/dist/test/suites/build/config-schema.test.js +5 -5
  151. package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
  152. package/dist/test/suites/build/defaults-scaffold.test.js +21 -7
  153. package/dist/test/suites/build/deploy-direct.test.js +7 -5
  154. package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
  155. package/dist/test/suites/build/deploy-hook.test.js +7 -5
  156. package/dist/test/suites/build/dev-verb.test.js +67 -0
  157. package/dist/test/suites/build/ensure-target.test.js +13 -5
  158. package/dist/test/suites/build/env-delivery.test.js +2 -2
  159. package/dist/test/suites/build/esm-only-dependency.test.js +2 -2
  160. package/dist/test/suites/build/exports.test.js +9 -8
  161. package/dist/test/suites/build/get-config.test.js +4 -4
  162. package/dist/test/suites/build/manifest-deps.test.js +1 -1
  163. package/dist/test/suites/build/merge-line-files.test.js +7 -7
  164. package/dist/test/suites/build/migrate.test.js +29 -0
  165. package/dist/test/suites/build/omega-shell.test.js +34 -2
  166. package/dist/test/suites/build/omega.test.js +350 -0
  167. package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
  168. package/dist/test/suites/build/renderer-auth-bridge.test.js +211 -78
  169. package/dist/test/suites/build/runner-env-write.test.js +73 -0
  170. package/dist/test/suites/build/runner.test.js +11 -10
  171. package/dist/test/suites/build/sentry.test.js +2 -2
  172. package/dist/test/suites/build/setup-scripts.test.js +27 -0
  173. package/dist/test/suites/build/sign-windows-e2e.test.js +2 -2
  174. package/dist/test/suites/build/templating.test.js +3 -3
  175. package/dist/test/suites/build/test-stealth.test.js +7 -9
  176. package/dist/test/suites/build/url-helpers.test.js +55 -56
  177. package/dist/test/suites/build/validate-config.test.js +15 -4
  178. package/dist/test/suites/build/verb-logs.test.js +20 -0
  179. package/dist/test/suites/build/wave5-pins.test.js +2 -2
  180. package/dist/test/suites/main/analytics.test.js +59 -59
  181. package/dist/test/suites/main/app-state.test.js +66 -66
  182. package/dist/test/suites/main/auth-flow.test.js +41 -41
  183. package/dist/test/suites/main/auth-persistence.test.js +54 -43
  184. package/dist/test/suites/main/{client-bridge.integration.test.js → auth.integration.test.js} +10 -9
  185. package/dist/test/suites/main/auth.test.js +336 -0
  186. package/dist/test/suites/main/auto-updater.test.js +134 -134
  187. package/dist/test/suites/main/boot-sequence.test.js +37 -49
  188. package/dist/test/suites/main/context-menu.test.js +51 -50
  189. package/dist/test/suites/main/context.test.js +25 -25
  190. package/dist/test/suites/main/deep-link.test.js +74 -74
  191. package/dist/test/suites/main/fontawesome.test.js +27 -27
  192. package/dist/test/suites/main/ipc.test.js +35 -35
  193. package/dist/test/suites/main/menu.test.js +101 -100
  194. package/dist/test/suites/main/protocol.test.js +19 -19
  195. package/dist/test/suites/main/remote-config.test.js +63 -63
  196. package/dist/test/suites/main/remote-scripts.test.js +103 -103
  197. package/dist/test/suites/main/request.test.js +71 -0
  198. package/dist/test/suites/main/restart-manager.test.js +33 -33
  199. package/dist/test/suites/main/startup-paths-and-ua.test.js +4 -4
  200. package/dist/test/suites/main/startup.test.js +26 -26
  201. package/dist/test/suites/main/stealth-window.test.js +1 -1
  202. package/dist/test/suites/main/storage.test.js +25 -25
  203. package/dist/test/suites/main/theme.test.js +34 -34
  204. package/dist/test/suites/main/tray.test.js +79 -79
  205. package/dist/test/suites/main/url-helpers.test.js +133 -133
  206. package/dist/test/suites/main/usage.test.js +25 -25
  207. package/dist/test/suites/main/window-bounds.test.js +27 -27
  208. package/dist/test/suites/main/window-manager.test.js +44 -44
  209. package/dist/test/suites/renderer/analytics-bridge.test.js +5 -5
  210. package/dist/test/suites/renderer/cross-context-helpers.test.js +37 -31
  211. package/dist/test/suites/renderer/round-trip.test.js +3 -3
  212. package/dist/test/suites/renderer/tooltips.test.js +15 -15
  213. package/dist/test/suites/renderer/{window-em-surface.test.js → window-desktop-surface.test.js} +28 -8
  214. package/dist/test/utils/extended-mode-warning.js +1 -1
  215. package/dist/utils/boot-harness.js +56 -0
  216. package/dist/utils/build-pipeline.js +4 -4
  217. package/dist/utils/mode-helpers.js +2 -15
  218. package/dist/utils/runner-env.js +13 -28
  219. package/dist/utils/ship-keys.js +3 -3
  220. package/dist/utils/signing-status.js +51 -0
  221. package/dist/utils/test-events.js +7 -0
  222. package/dist/utils/test-stealth.js +6 -6
  223. package/dist/utils/url-helpers.js +52 -42
  224. package/dist/utils/user-agent.js +44 -0
  225. package/dist/vendor/account/engine.js +3 -3
  226. package/dist/vendor/account/index.js +14 -45
  227. package/dist/vendor/account/resolve-account.js +44 -0
  228. package/dist/vendor/account/schema.js +1 -1
  229. package/dist/vendor/account/user.js +99 -0
  230. package/dist/vendor/config/client-config.js +1 -1
  231. package/dist/vendor/config/company.js +46 -14
  232. package/dist/vendor/config/defaults.js +30 -7
  233. package/dist/vendor/config/edit.js +25 -3
  234. package/dist/vendor/config/env-delivery.js +1 -1
  235. package/dist/vendor/config/env-schema.js +3 -6
  236. package/dist/vendor/config/env.js +34 -22
  237. package/dist/vendor/config/environment.js +11 -30
  238. package/dist/vendor/config/index.js +21 -28
  239. package/dist/vendor/config/load.js +16 -9
  240. package/dist/vendor/config/platforms.js +1 -1
  241. package/dist/vendor/config/repo.js +10 -27
  242. package/dist/vendor/config/schema-client.js +64 -0
  243. package/dist/vendor/config/schema-cloud.js +38 -0
  244. package/dist/vendor/config/schema-manager.js +118 -0
  245. package/dist/vendor/config/schema-overrides.js +68 -0
  246. package/dist/vendor/config/schema.js +104 -160
  247. package/dist/vendor/config/site-global.js +2 -3
  248. package/dist/vendor/config/validate.js +97 -78
  249. package/dist/vendor/config/winback.js +1 -1
  250. package/dist/vendor/devkit/actions-secrets.js +1 -1
  251. package/dist/vendor/devkit/agents-md.js +233 -0
  252. package/dist/vendor/devkit/attach-log-file.js +16 -2
  253. package/dist/vendor/devkit/build-json.js +1 -1
  254. package/dist/vendor/devkit/ci-workflows.js +30 -30
  255. package/dist/vendor/devkit/cli-router.js +16 -11
  256. package/dist/vendor/devkit/defaults-engine.js +15 -51
  257. package/dist/vendor/devkit/deploy-snapshot.js +44 -9
  258. package/dist/vendor/devkit/env-lines.js +183 -0
  259. package/dist/vendor/devkit/local.js +64 -10
  260. package/dist/vendor/devkit/lockfile.js +32 -13
  261. package/dist/vendor/devkit/logger.js +7 -2
  262. package/dist/vendor/devkit/merge-line-files.js +219 -177
  263. package/dist/vendor/devkit/omega-bin.js +208 -111
  264. package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
  265. package/dist/vendor/devkit/preludes/index.js +1 -0
  266. package/dist/vendor/devkit/target-picker.js +45 -0
  267. package/dist/vendor/devkit/test/dashed-files.js +37 -0
  268. package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
  269. package/dist/vendor/devkit/test/runner-core.js +6 -6
  270. package/dist/vendor/devkit/update.js +15 -15
  271. package/dist/vendor/devkit/verb-scripts.js +40 -0
  272. package/dist/vendor/devkit/verbs.js +170 -0
  273. package/dist/vendor/monitoring/env.js +2 -2
  274. package/dist/vendor/monitoring/index.js +1 -1
  275. package/dist/vendor/monitoring/main.js +1 -1
  276. package/dist/vendor/monitoring/preload.js +1 -1
  277. package/dist/vendor/monitoring/renderer.js +1 -1
  278. package/package.json +19 -26
  279. package/dist/commands/install.js +0 -37
  280. package/dist/defaults/AGENTS.md +0 -110
  281. package/dist/defaults/CLAUDE.md +0 -1
  282. package/dist/lib/client-bridge.js +0 -374
  283. package/dist/lib/logger.js +0 -4
  284. package/dist/test/suites/build/manager.test.js +0 -213
  285. package/dist/test/suites/main/client-bridge.test.js +0 -262
  286. package/dist/vendor/config/env-retired.js +0 -137
  287. package/dist/vendor/config/retired-keys.js +0 -635
  288. package/docs/analytics.md +0 -140
  289. package/docs/app-state.md +0 -92
  290. package/docs/audit.md +0 -69
  291. package/docs/auto-updater.md +0 -243
  292. package/docs/boot-sequence.md +0 -39
  293. package/docs/build-system.md +0 -169
  294. package/docs/cdp-debugging.md +0 -169
  295. package/docs/client-bridge.md +0 -269
  296. package/docs/common-mistakes.md +0 -21
  297. package/docs/config-schema.md +0 -120
  298. package/docs/context-menu.md +0 -112
  299. package/docs/context.md +0 -81
  300. package/docs/css.md +0 -90
  301. package/docs/deep-link.md +0 -186
  302. package/docs/environment-detection.md +0 -112
  303. package/docs/fontawesome.md +0 -107
  304. package/docs/hooks.md +0 -89
  305. package/docs/icons.md +0 -79
  306. package/docs/index.md +0 -317
  307. package/docs/installer-options.md +0 -165
  308. package/docs/ipc.md +0 -61
  309. package/docs/lib-modules.md +0 -53
  310. package/docs/logging.md +0 -229
  311. package/docs/menu.md +0 -160
  312. package/docs/releasing.md +0 -239
  313. package/docs/remote-config.md +0 -118
  314. package/docs/remote-scripts.md +0 -144
  315. package/docs/restart-manager.md +0 -144
  316. package/docs/runner.md +0 -290
  317. package/docs/sentry.md +0 -97
  318. package/docs/shared/agent-docs.md +0 -89
  319. package/docs/shared/analytics.md +0 -612
  320. package/docs/shared/brands.md +0 -57
  321. package/docs/shared/breaking-changes.md +0 -851
  322. package/docs/shared/config.md +0 -1952
  323. package/docs/shared/deploys.md +0 -341
  324. package/docs/shared/icons.md +0 -219
  325. package/docs/shared/local-dev.md +0 -167
  326. package/docs/shared/logging.md +0 -205
  327. package/docs/shared/monitoring.md +0 -167
  328. package/docs/shared/publishing.md +0 -187
  329. package/docs/shared/rulings.md +0 -34
  330. package/docs/shared/testing.md +0 -147
  331. package/docs/shared/theming.md +0 -629
  332. package/docs/shared/translation.md +0 -333
  333. package/docs/shared/updates.md +0 -61
  334. package/docs/signing.md +0 -293
  335. package/docs/startup.md +0 -142
  336. package/docs/storage.md +0 -59
  337. package/docs/templating.md +0 -101
  338. package/docs/test-boot-layer.md +0 -157
  339. package/docs/test-framework.md +0 -362
  340. package/docs/themes.md +0 -149
  341. package/docs/tooltips.md +0 -99
  342. package/docs/tray.md +0 -164
  343. package/docs/usage.md +0 -58
  344. package/docs/verts.md +0 -62
  345. package/docs/windows.md +0 -149
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Canonical schema for config/omega.json5 — pure data, no logic.
3
3
  *
4
- * Entry format is electron-manager's proven src/config/schema.js shape:
4
+ * Entry format:
5
5
  *
6
6
  * {
7
7
  * path: 'brand.id', // dot-path into the RESOLVED config
@@ -61,11 +61,15 @@
61
61
  * target entry overrides the shared value.
62
62
  *
63
63
  * SHARED_SCHEMA always applies; TARGET_SCHEMAS[target] adds that target's
64
- * refinements. Sections grow as each framework adopts dual-read — seed
65
- * entries come from EM's schema (desktop) and the sandbox brand's real @omega.js/backend
66
- * config (backend), never from guesses.
64
+ * refinements, seeded from real brand configs, never from guesses.
67
65
  */
68
66
 
67
+ // Rule sets kept in their own files, one concern each.
68
+ const { MANAGER_RULES } = require('./schema-manager.js');
69
+ const { CLIENT_RULES } = require('./schema-client.js');
70
+ const { CLOUD_CONFIG_RULES } = require('./schema-cloud.js');
71
+ const { OVERRIDE_RULES } = require('./schema-overrides.js');
72
+
69
73
  // The durations a winback coupon can be built for on every provider that
70
74
  // supports the offer (#268). Stripe's third option ('repeating') needs a
71
75
  // duration_in_months beside it, which is provider surface nothing here asks
@@ -203,13 +207,14 @@ const CLIENT_SECTIONS = {
203
207
  const BRAND_ID_PATTERN = /^[a-z][a-z0-9+\-.]*$/;
204
208
 
205
209
  const SHARED_SCHEMA = [
210
+ ...MANAGER_RULES,
206
211
  // ── the target's own url ─────────────────────────────────────────────────
207
212
  {
208
213
  path: 'url',
209
214
  type: 'string',
210
215
  required: false,
211
216
  match: /^https?:\/\//,
212
- description: "This TARGET's own public URL, and the one key a target entry may set at the top level. A target whose name is not its type derives it from the name (`admin: { type: 'web' }` → https://admin.<host of brand.url>, #588/#886; docs/shared/config.md → Targets); an entry's own `url` overrides it for a custom host. `brand.url` stays the BRAND's url, which is what brand-level facts (authDomain, the persona domain) read.",
217
+ description: "This TARGET's own public URL, and the one key a target entry may set at the top level. A target whose name is not its type derives it from the name (`admin: { type: 'web' }` → https://admin.<host of brand.url>; docs/shared/config.md → Targets); an entry's own `url` overrides it for a custom host. `brand.url` stays the BRAND's url, which is what brand-level facts (authDomain, the persona domain) read.",
213
218
  },
214
219
 
215
220
  // ── the target's own type (#886) ─────────────────────────────────────────
@@ -222,7 +227,7 @@ const SHARED_SCHEMA = [
222
227
  type: 'string',
223
228
  required: false,
224
229
  enum: [...TARGETS, CUSTOM_TARGET_TYPE],
225
- description: "The FRAMEWORK that runs this target (#886), or `custom` for a target no framework owns. Key presence in `targets` enables a target by NAME, and this is what says which code runs there, so a target named `admin` with `type: 'web'` is a web build. A resolved target config carries it at the top level like every other key in the entry.",
230
+ description: "The FRAMEWORK that runs this target, or `custom` for a target no framework owns. Key presence in `targets` enables a target by NAME, and this is what says which code runs there, so a target named `admin` with `type: 'web'` is a web build. A resolved target config carries it at the top level like every other key in the entry.",
226
231
  },
227
232
 
228
233
  // ── brand ────────────────────────────────────────────────────────────────
@@ -262,7 +267,7 @@ const SHARED_SCHEMA = [
262
267
  path: 'brand.type',
263
268
  type: 'string',
264
269
  required: false,
265
- description: "The brand's schema.org type ('Organization', 'Corporation', 'LocalBusiness'). @omega.js/web stamps it on the JSON-LD brand node and every @id that cross-references it; unset renders the framework default 'Organization' (#271).",
270
+ description: "The brand's schema.org type ('Organization', 'Corporation', 'LocalBusiness'). @omega.js/web stamps it on the JSON-LD brand node and every @id that cross-references it; unset renders the framework default 'Organization'.",
266
271
  },
267
272
  {
268
273
  path: 'brand.font',
@@ -287,7 +292,7 @@ const SHARED_SCHEMA = [
287
292
  path: 'brand.contact.person.name',
288
293
  type: 'string',
289
294
  required: false,
290
- description: 'The human who signs "personal" emails, as it should read ("Jane Doe, CEO"). Required only when something sends a personal-signoff email — that path fails loudly rather than substituting a framework identity.',
295
+ description: 'The human who signs "personal" emails, as it should read ("Jane Doe, CEO"). Required only when something sends a personal-signoff email; that path fails loudly rather than substituting a framework identity.',
291
296
  },
292
297
  {
293
298
  path: 'brand.contact.person.firstName',
@@ -331,7 +336,7 @@ const SHARED_SCHEMA = [
331
336
  type: 'string',
332
337
  required: false,
333
338
  match: /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,
334
- description: 'Brand accent color as a hex string (#RGB or #RRGGBB). @omega.js/web composes the --omega-accent ramps (light + dark) from it; unset leaves the theme\'s neutral placeholder standing.',
339
+ description: "Brand accent color as a hex string, #RGB or #RRGGBB. @omega.js/web composes the --omega-accent ramps (light + dark) from it; unset leaves the theme's neutral placeholder standing.",
335
340
  },
336
341
  {
337
342
  path: 'brand.images',
@@ -345,7 +350,7 @@ const SHARED_SCHEMA = [
345
350
  path: 'socials',
346
351
  type: 'object',
347
352
  required: false,
348
- description: "Platform → handle ({ twitter: 'somiibo' }). The handle derives the profile URL every surface reads (JSON-LD sameAs, the footer row, omega_social), and @omega.js/web emits a redirect shortlink page at /<platform> for each entry (#429). An entry that redirects somewhere OTHER than its profile URL takes the object form { handle, redirect } — the redirect target wins for the shortlink, the handle still names the profile.",
353
+ description: "Platform → handle ({ twitter: 'somiibo' }). The handle derives the profile URL every surface reads (JSON-LD sameAs, the footer row, omega_social), and @omega.js/web emits a redirect shortlink page at /<platform> for each entry. An entry that redirects somewhere OTHER than its profile URL takes the object form { handle, redirect }: the redirect target wins for the shortlink, the handle still names the profile.",
349
354
  },
350
355
 
351
356
  // ── cloud (role: app/cloud platform; D12 provider-discriminated) ─────────
@@ -354,25 +359,14 @@ const SHARED_SCHEMA = [
354
359
  type: 'string',
355
360
  required: false,
356
361
  enum: ['firebase'],
357
- description: "App/cloud platform provider. Only 'firebase' today — the discriminator exists so a second provider slots in without a key rename.",
358
- },
359
- {
360
- path: 'cloud.config',
361
- type: 'object',
362
- required: false,
363
- description: 'Provider app config. For firebase: the web-app config verbatim from the console. Public by design — the web API key is not a secret.',
364
- },
365
- {
366
- path: 'cloud.config.projectId',
367
- type: 'string',
368
- required: false,
369
- description: 'Drives auth, emulator project selection, analytics uuidv5 namespace, remote-config URL fallbacks.',
362
+ description: "App/cloud platform provider. Only 'firebase' today; the discriminator exists so a second provider slots in without a key rename.",
370
363
  },
364
+ ...CLOUD_CONFIG_RULES,
371
365
  {
372
366
  path: 'cloud.messaging.vapidKey',
373
367
  type: 'string',
374
368
  required: false,
375
- description: 'Web-push VAPID public key (Firebase console → Cloud Messaging → Web Push certificates). Public by design — it ships to every browser; the private half stays in the console.',
369
+ description: 'Web-push VAPID public key (Firebase console → Cloud Messaging → Web Push certificates). Public by design: it ships to every browser, and the private half stays in the console.',
376
370
  },
377
371
  {
378
372
  path: 'cloud.shared',
@@ -386,19 +380,19 @@ const SHARED_SCHEMA = [
386
380
  type: 'string',
387
381
  required: false,
388
382
  match: /@/,
389
- description: "OAuth consent screen support email. Defaults to the AUTHORIZING user's email — the provider rejects any address the caller doesn't own; set only for an owned Google Group.",
383
+ description: "OAuth consent screen support email. Defaults to the AUTHORIZING user's email, because the provider rejects any address the caller doesn't own; set only for an owned Google Group.",
390
384
  },
391
385
  {
392
386
  path: 'cloud.oauthRedirectsConfigured',
393
387
  type: 'boolean',
394
388
  required: false,
395
- description: "true = the Google OAuth client's authorized origins + redirect URIs are configured by hand. The ONE reconcile flag config keeps (#434) — that surface has no API to re-check, so the cloud service asks once, writes the answer back here, and every later run trusts it. Never set it without doing the work: the confirm is the only proof there is.",
389
+ description: "true = the Google OAuth client's authorized origins + redirect URIs are configured by hand. The ONE reconcile flag config keeps: that surface has no API to re-check, so the cloud service asks once, writes the answer back here, and every later run trusts it. Never set it without doing the work: the confirm is the only proof there is.",
396
390
  },
397
391
  {
398
392
  path: 'cloud.consentAudience',
399
393
  type: 'boolean',
400
394
  required: false,
401
- description: "false = stop asking about the OAuth consent screen's audience. Tri-state opt-out (#667): Google gives NO API write for the audience, so an Internal screen (Error 403: org_internal for every non-org account) is a manage-time stopper that opens the console page and polls — `false` records the deliberate choice to live with an org-only sign-in, and only the cloud service's audience step goes quiet.",
395
+ description: "false = stop asking about the OAuth consent screen's audience. A tri-state opt-out: Google gives NO API write for the audience, so an Internal screen (Error 403: org_internal for every non-org account) is a manage-time stopper that opens the console page and polls; `false` records the deliberate choice to live with an org-only sign-in, and only the cloud service's audience step goes quiet.",
402
396
  },
403
397
  {
404
398
  path: 'cloud.apiSubdomain',
@@ -411,13 +405,13 @@ const SHARED_SCHEMA = [
411
405
  path: 'cloud.organizationId',
412
406
  type: 'string|boolean',
413
407
  required: false,
414
- description: 'Cloud organization ID the project is created inside — tri-state: unset = ask at project create, false = no org (standalone), value = create inside it.',
408
+ description: 'Cloud organization ID the project is created inside, a tri-state: unset = ask at project create, false = no org (standalone), value = create inside it.',
415
409
  },
416
410
  {
417
411
  path: 'cloud.billingAccount',
418
412
  type: 'string|boolean',
419
413
  required: false,
420
- description: "Billing account ('billingAccounts/XXXXXX-XXXXXX-XXXXXX') — tri-state: unset = ask, false = stay on the free tier, value = auto-upgrade.",
414
+ description: "Billing account ('billingAccounts/XXXXXX-XXXXXX-XXXXXX'), a tri-state: unset = ask, false = stay on the free tier, value = auto-upgrade.",
421
415
  },
422
416
 
423
417
  // ── analytics ────────────────────────────────────────────────────────────
@@ -432,13 +426,13 @@ const SHARED_SCHEMA = [
432
426
  path: 'analytics.providers.google.propertyId',
433
427
  type: 'string',
434
428
  required: false,
435
- description: 'GA4 property id (digits, as the Admin API returns it) — the manager\'s analytics service reconciles the brand\'s data streams and the Firebase link against it, and it gates the whole google half: no propertyId, no google operations. Interactive setup selects/creates the property and writes it back here.',
429
+ description: "GA4 property id (digits, as the Admin API returns it). The manager's analytics service reconciles the brand's data streams and the Firebase link against it, and it gates the whole google half: no propertyId, no google operations. Interactive setup selects/creates the property and writes it back here.",
436
430
  },
437
431
  {
438
432
  path: 'analytics.providers.google.accountId',
439
433
  type: 'string',
440
434
  required: false,
441
- description: "GA account id the property lives under (digits) — the analytics property picker and the console bookmarks read it. A company-managed brand inherits the COMPANY layer's value through the merge chain; a brand-level value always wins.",
435
+ description: "GA account id the property lives under (digits), read by the analytics property picker and the console bookmarks. A company-managed brand inherits the COMPANY layer's value through the merge chain; a brand-level value always wins.",
442
436
  },
443
437
  {
444
438
  path: 'analytics.providers.meta.id',
@@ -450,7 +444,7 @@ const SHARED_SCHEMA = [
450
444
  path: 'analytics.providers.meta.accountId',
451
445
  type: 'string',
452
446
  required: false,
453
- description: "Meta AD ACCOUNT id ('act_…' or the bare digits) the pixel is created on and found by name against. A non-secret platform id, so it lives here beside google.accountId — the token that reaches the account stays in .env (META_ACCESS_TOKEN).",
447
+ description: "Meta AD ACCOUNT id ('act_…' or the bare digits) the pixel is created on and found by name against. A non-secret platform id, so it lives here beside google.accountId; the token that reaches the account stays in .env (META_ACCESS_TOKEN).",
454
448
  },
455
449
  {
456
450
  path: 'analytics.providers.tiktok.id',
@@ -468,7 +462,7 @@ const SHARED_SCHEMA = [
468
462
  path: 'analytics.providers.tiktok.appId',
469
463
  type: 'string',
470
464
  required: false,
471
- description: "TikTok DEVELOPER APP id the access-token mint authorizes through (#448) — public config, so the manager can walk the portal without asking twice; setup writes it back here (#635). The app secret is pasted once and never saved, and the token lands in .env (TIKTOK_ACCESS_TOKEN).",
465
+ description: 'TikTok DEVELOPER APP id the access-token mint authorizes through. Public config, so the manager can walk the portal without asking twice; setup writes it back here. The app secret is pasted once and never saved, and the token lands in .env (TIKTOK_ACCESS_TOKEN).',
472
466
  },
473
467
 
474
468
  // ── advertising ──────────────────────────────────────────────────────────
@@ -476,7 +470,7 @@ const SHARED_SCHEMA = [
476
470
  path: 'advertising.providers.adsense.client',
477
471
  type: 'string',
478
472
  required: false,
479
- description: 'AdSense publisher client id (ca-pub-…). The ONE adsense switch (#527), presence-driven: set it and the manager service manages the account, the site renders units from it and ads.txt carries the record; leave it out and none of that happens. No second gate — a managed-but-ad-free brand is deliberately inexpressible.',
473
+ description: 'AdSense publisher client id (ca-pub-…). The ONE adsense switch, presence-driven: set it and the manager service manages the account, the site renders units from it and ads.txt carries the record; leave it out and none of that happens. No second gate exists, so a managed-but-ad-free brand is deliberately inexpressible.',
480
474
  },
481
475
  {
482
476
  path: 'advertising.providers.adsense.displaySlot',
@@ -512,13 +506,13 @@ const SHARED_SCHEMA = [
512
506
  path: 'advertising.fallback',
513
507
  type: 'string|boolean',
514
508
  required: false,
515
- description: "The role a provider miss falls through to — 'inhouse' renders the house unit, false (or absent) ends the ladder at the built-in promo. Role-level, beside `providers` (docs/web/ads-system.md).",
509
+ description: "The role a provider miss falls through to: 'inhouse' renders the house unit, false (or absent) ends the ladder at the built-in promo. Role-level, beside `providers` (docs/web/ads-system.md).",
516
510
  },
517
511
  {
518
512
  path: 'advertising.tags',
519
513
  type: 'array',
520
514
  required: false,
521
- description: "This brand's contextual tags (['music', 'audio-tools']) — the targeting match input a house unit sends with its serve request unless the include passes its own. No user tracking.",
515
+ description: "This brand's contextual tags (['music', 'audio-tools']), the targeting match input a house unit sends with its serve request unless the include passes its own. No user tracking.",
522
516
  },
523
517
 
524
518
  // ── company (ONE typed key, the rest RESOLVED) ───────────────────────────
@@ -545,7 +539,7 @@ const SHARED_SCHEMA = [
545
539
  // would be noise, and the fact belongs to the brands that actually share
546
540
  // somebody else's provider account.
547
541
  materialize: false,
548
- description: "Whether the manage walk may repoint this company's provider ACCOUNT webhooks (#677). `false` says the SendGrid Event Webhook and the Beehiiv webhook belong to someone else (a shared account whose one account-level webhook points at their production), so the campaigns and newsletter services leave them alone. The successor to the retired top-level `parent: false`.",
542
+ description: "Whether the manage walk may repoint this company's provider ACCOUNT webhooks. `false` says the SendGrid Event Webhook and the Beehiiv webhook belong to someone else (a shared account whose one account-level webhook points at their production), so the campaigns and newsletter services leave them alone.",
549
543
  },
550
544
 
551
545
  // ── features (the catalog every product prices) ──────────────────────────
@@ -553,7 +547,7 @@ const SHARED_SCHEMA = [
553
547
  path: 'features',
554
548
  type: 'object',
555
549
  required: false,
556
- description: "The feature catalog (#647): every feature DEFINED once, keyed by id, in the order every surface renders it. An entry carries name, icon (the full Font Awesome class string, #929), definition — and, when the feature is METERED, a `usage` block ({ pace: 'daily' | false, mirror: ['<doc kind>'] }); an entry without one is a perk. Products name only the VALUE (payment.products[].features). Key order is row order on the pricing page and the account's usage bars.",
550
+ description: "The feature catalog: every feature DEFINED once, keyed by id, in the order every surface renders it. An entry carries name, icon (the full Font Awesome class string) and definition, plus, when the feature is METERED, a `usage` block ({ pace: 'daily' | false, mirror: ['<doc kind>'] }); an entry without one is a perk. Products name only the VALUE (payment.products[].features). Key order is row order on the pricing page and the account's usage bars.",
557
551
  },
558
552
 
559
553
  // ── payment (role: billing; provider-discriminated) ──────────────────────
@@ -562,7 +556,7 @@ const SHARED_SCHEMA = [
562
556
  type: 'string',
563
557
  required: false,
564
558
  default: 'USD',
565
- description: "The ISO 4217 code every price in payment.products is quoted in (#850). One currency per brand, named wherever a price is shown or recorded: the pricing page's JSON-LD, the checkout, and the backend's order history (GET /user/orders). Defaults to 'USD', the fallback its readers carried before the fact had a home. The successor to the retired targets.web.currency.",
559
+ description: "The ISO 4217 code every price in payment.products is quoted in. One currency per brand, named wherever a price is shown or recorded: the pricing page's JSON-LD, the checkout, and the backend's order history (GET /user/orders). Defaults to 'USD'.",
566
560
  },
567
561
  {
568
562
  path: 'payment.providers.stripe.publishableKey',
@@ -588,20 +582,20 @@ const SHARED_SCHEMA = [
588
582
  type: 'boolean',
589
583
  required: false,
590
584
  default: false,
591
- description: "Coinbase Commerce (crypto) checkout, one-time purchases only. Its ONLY credential is a secret (COINBASE_COMMERCE_API_KEY), so there is no public datum to gate on — this is the switch, default OFF, because an accidental ON shows a Crypto button no key can complete.",
585
+ description: 'Coinbase Commerce (crypto) checkout, one-time purchases only. Its ONLY credential is a secret (COINBASE_COMMERCE_API_KEY), so there is no public datum to gate on: this is the switch, default OFF, because an accidental ON shows a Crypto button no key can complete.',
592
586
  },
593
587
  {
594
588
  path: 'payment.products',
595
589
  type: 'array',
596
590
  required: false,
597
- description: 'Product catalog (@omega.js/backend-shaped: id, name, type, prices, per-provider IDs) — referenceable from every target. The ONLY pricing-page source (C2); optional presentation fields: tagline, popular, enterprise (the talk-to-us tier: its own full-width row, never a card), hidden (still created on every provider and purchasable by id — QA tiers, grandfathered plans — but never rendered on the pricing page), url, features { <catalog id>: value } — a number on a counted feature (the MONTHLY limit, -1 unlimited), true/false/a string on a perk (#647).',
591
+ description: 'Product catalog (@omega.js/backend-shaped: id, name, type, prices, per-provider IDs), referenceable from every target and the ONLY pricing-page source. Optional presentation fields: tagline, popular, enterprise (the talk-to-us tier: its own full-width row, never a card), hidden (still created on every provider and purchasable by id, as QA tiers and grandfathered plans are, but never rendered on the pricing page), url, and features { <catalog id>: value }, a number on a counted feature (the MONTHLY limit, -1 unlimited) or true/false/a string on a perk.',
598
592
  },
599
593
  {
600
594
  path: 'payment.winback.enabled',
601
595
  type: 'boolean',
602
596
  required: false,
603
597
  default: true,
604
- description: 'The cancel-flow save offer (#268), shown before the cancellation questionnaire. Defaults ON — false is the whole off switch, and the cancel flow goes straight to the questionnaire.',
598
+ description: 'The cancel-flow save offer, shown before the cancellation questionnaire. Defaults ON; false is the whole off switch, and the cancel flow goes straight to the questionnaire.',
605
599
  },
606
600
  {
607
601
  path: 'payment.winback.percent',
@@ -640,7 +634,7 @@ const SHARED_SCHEMA = [
640
634
  path: 'monitoring.providers.sentry.org',
641
635
  type: 'string',
642
636
  required: false,
643
- description: 'Sentry organization slug (public). Auto-detected when the auth token sees exactly one org — the monitoring service writes it back here.',
637
+ description: 'Sentry organization slug (public). Auto-detected when the auth token sees exactly one org; the monitoring service writes it back here.',
644
638
  },
645
639
  {
646
640
  path: 'monitoring.providers.sentry.dsn',
@@ -677,7 +671,7 @@ const SHARED_SCHEMA = [
677
671
  required: false,
678
672
  min: 0,
679
673
  max: 1,
680
- description: 'Browser only: fraction of SESSIONS recorded as a replay, 0..1. Defaults to 0 — replay costs bandwidth and captures the DOM, so it is strictly opt-in; any value above 0 loads the replay integration.',
674
+ description: 'Browser only: fraction of SESSIONS recorded as a replay, 0..1. Defaults to 0, because replay costs bandwidth and captures the DOM, so it is strictly opt-in; any value above 0 loads the replay integration.',
681
675
  },
682
676
  {
683
677
  path: 'monitoring.providers.sentry.replaysOnErrorSampleRate',
@@ -703,7 +697,7 @@ const SHARED_SCHEMA = [
703
697
  path: 'monitoring.providers.sentry.bundlePatterns',
704
698
  type: 'array',
705
699
  required: false,
706
- description: "Browser only: the URL fragments that identify OUR bundles — a browser event reports only when a stack frame matches one. Defaults to ['/assets/js/'], where both @omega.js/web and @omega.js/extension serve every framework bundle.",
700
+ description: "Browser only: the URL fragments that identify OUR bundles; a browser event reports only when a stack frame matches one. Defaults to ['/assets/js/'], where both @omega.js/web and @omega.js/extension serve every framework bundle.",
707
701
  },
708
702
 
709
703
  // ── connections ──────────────────────────────────────────────────────────
@@ -757,7 +751,7 @@ const SHARED_SCHEMA = [
757
751
  description: 'Connect your Kick channel',
758
752
  },
759
753
  },
760
- description: "Per-provider user-connection settings, keyed by provider name (`connections: { twitch: {…} }`) — public values only, never client secrets (those are the CONNECTIONS_<PROVIDER>_CLIENT_ID/_SECRET env pair). The set of providers is OPEN (a brand ships its own as `src/connections/<name>.js`), so the section stays free-form; the keys every entry may carry are `enabled` (the packaged providers default to false — set it true to offer one; a brand's own provider is on unless this is false), `scope` (an array that wins over the provider module's default), `name` and `logo` (what the account page's card draws — the CONFIG is the only card list, #793: `logo` is the name of a mark @omega.js/web ships in core/logos/brandmarks/original, rendered inline, or a full URL rendered as an img), and `description` (the line under the card's title). The five packaged providers carry all three by default, so enabling one is one line. packages/backend/docs/connections.md",
754
+ description: "Per-provider user-connection settings, keyed by provider name (`connections: { twitch: {…} }`), public values only, never client secrets (those are the CONNECTIONS_<PROVIDER>_CLIENT_ID/_SECRET env pair). The set of providers is OPEN (a brand ships its own as `src/connections/<name>.js`), so the section stays free-form; the keys every entry may carry are `enabled` (the packaged providers default to false, so set it true to offer one; a brand's own provider is on unless this is false), `scope` (an array that wins over the provider module's default), `name` and `logo` (what the account page's card draws; the CONFIG is the only card list, and `logo` is the name of a mark @omega.js/web ships in core/logos/brandmarks/original, rendered inline, or a full URL rendered as an img), and `description` (the line under the card's title). The five packaged providers carry all three by default, so enabling one is one line. docs/backend/connections.md",
761
755
  },
762
756
 
763
757
  // ── repo (where the brand hosts its source; #883) ───────────────────────
@@ -769,7 +763,7 @@ const SHARED_SCHEMA = [
769
763
  path: 'repo',
770
764
  type: 'object',
771
765
  required: false,
772
- description: 'Where the brand hosts its source. Presence enables the repo service; the block is provider + org and nothing else, since every repo name derives from `<brand.id>-<role>` (#883).',
766
+ description: 'Where the brand hosts its source. Presence enables the repo service; the block is provider + org and nothing else, since every repo name derives from `<brand.id>-<role>`.',
773
767
  },
774
768
  {
775
769
  path: 'repo.provider',
@@ -796,19 +790,19 @@ const SHARED_SCHEMA = [
796
790
  path: 'domain.providers',
797
791
  type: 'object',
798
792
  required: false,
799
- description: "The brand's REGISTRAR, named as the one KEY under it ({ namecheap: {} }) — presence picks it, and no entry means nothing chosen, so the domain service skips. The provider set is open; the entry may be empty.",
793
+ description: "The brand's REGISTRAR, named as the one KEY under it ({ namecheap: {} }). Presence picks it, and no entry means nothing chosen, so the domain service skips. The provider set is open; the entry may be empty.",
800
794
  },
801
795
  {
802
796
  path: 'domain.providers.namecheap',
803
797
  type: 'object',
804
798
  required: false,
805
- description: 'Namecheap as the registrar — the one provider whose nameservers the domain service points at the Cloudflare zone via API (NAMECHEAP_USERNAME + NAMECHEAP_API_KEY in .env). An empty object is the whole declaration; every other registrar gets manual instructions.',
799
+ description: 'Namecheap as the registrar, the one provider whose nameservers the domain service points at the Cloudflare zone via API (NAMECHEAP_USERNAME + NAMECHEAP_API_KEY in .env). An empty object is the whole declaration; every other registrar gets manual instructions.',
806
800
  },
807
801
  {
808
802
  path: 'domain.email.providers',
809
803
  type: 'object',
810
804
  required: false,
811
- description: "The brand's MAILBOX provider, named as the one KEY under it ({ cloudflare: {} } | { squarespace: {} } | { privateemail: {} }) — presence picks it; no entry leaves the edge service's email-routing operations off.",
805
+ description: "The brand's MAILBOX provider, named as the one KEY under it ({ cloudflare: {} } | { squarespace: {} } | { privateemail: {} }). Presence picks it; no entry leaves the edge service's email-routing operations off.",
812
806
  },
813
807
  {
814
808
  path: 'domain.email.forwarding',
@@ -823,11 +817,11 @@ const SHARED_SCHEMA = [
823
817
  type: 'boolean',
824
818
  required: false,
825
819
  default: true,
826
- description: 'false = the edge service skips entirely — no zone, DNS, settings, rules, speed-test or worker reconciliation for this brand.',
820
+ description: 'false = the edge service skips entirely: no zone, DNS, settings, rules, speed-test or worker reconciliation for this brand.',
827
821
  },
828
822
  {
829
823
  path: 'edge.providers.cloudflare',
830
- type: 'object',
824
+ type: 'object', open: true,
831
825
  required: false,
832
826
  description: 'Cloudflare zone reconciliation (@omega.js/manager cloudflare service): dns, settings, rules, cacheRules, speedTest, workers. Tokens live in .env.',
833
827
  },
@@ -841,7 +835,7 @@ const SHARED_SCHEMA = [
841
835
  path: 'edge.providers.cloudflare.rules.redirect',
842
836
  type: 'array',
843
837
  required: false,
844
- description: "The zone's dynamic redirect rules, ORDERED — the ONE home for a TEMPLATED redirect, i.e. one whose destination is computed from the request path (#466): [{ name, expression, statusCode, preserveQueryString, targetUrl, enabled }]. `expression` and `targetUrl` ({ value } for a fixed URL, { expression } for a computed one) are Cloudflare's own filter language, because only the edge can answer a URL the build cannot enumerate — DashQR's printed `/c/<id>` codes redirect to `/code?id=<id>` with `targetUrl.expression: concat(\"https://\", http.host, \"/code?id=\", substring(http.request.uri.path, 3))`. The @omega.js/manager edge service reconciles them by `name`. A redirect whose URLs CAN be enumerated is a redirect PAGE instead (docs/web/index.md), never config.",
838
+ description: "The zone's dynamic redirect rules, ORDERED, and the ONE home for a TEMPLATED redirect, i.e. one whose destination is computed from the request path: [{ name, expression, statusCode, preserveQueryString, targetUrl, enabled }]. `expression` and `targetUrl` ({ value } for a fixed URL, { expression } for a computed one) are Cloudflare's own filter language, because only the edge can answer a URL the build cannot enumerate: DashQR's printed `/c/<id>` codes redirect to `/code?id=<id>` with `targetUrl.expression: concat(\"https://\", http.host, \"/code?id=\", substring(http.request.uri.path, 3))`. The @omega.js/manager edge service reconciles them by `name`. A redirect whose URLs CAN be enumerated is a redirect PAGE instead (docs/web/index.md), never config.",
845
839
  },
846
840
 
847
841
  // ── captcha (role: bot defense; provider-discriminated) ──────────────────
@@ -849,13 +843,13 @@ const SHARED_SCHEMA = [
849
843
  path: 'captcha.providers.recaptcha.project',
850
844
  type: 'string',
851
845
  required: false,
852
- description: "The brand's OWN GCP project hosting the classic reCAPTCHA key — used only for the console deep-link in guidance. Site/secret keys stay in .env.",
846
+ description: "The brand's OWN GCP project hosting the classic reCAPTCHA key, used only for the console deep-link in guidance. Site/secret keys stay in .env.",
853
847
  },
854
848
  {
855
849
  path: 'captcha.providers.recaptcha.siteKey',
856
850
  type: 'string',
857
851
  required: false,
858
- description: 'Classic reCAPTCHA site key rendered client-side. Public by design — the secret half stays in .env (RECAPTCHA_SECRET_KEY).',
852
+ description: 'Classic reCAPTCHA site key rendered client-side. Public by design; the secret half stays in .env (RECAPTCHA_SECRET_KEY).',
859
853
  },
860
854
  {
861
855
  path: 'captcha.providers.recaptcha.domainsConfirmed',
@@ -876,7 +870,7 @@ const SHARED_SCHEMA = [
876
870
  path: 'forms.providers.slapform.formId',
877
871
  type: 'string',
878
872
  required: false,
879
- description: 'Slapform form id — the contact page posts to https://api.slapform.com/{formId}. Interactive manager runs land it here.',
873
+ description: 'Slapform form id; the contact page posts to https://api.slapform.com/{formId}. Interactive manager runs land it here.',
880
874
  },
881
875
  {
882
876
  path: 'forms.providers.slapform.templateFormId',
@@ -896,7 +890,7 @@ const SHARED_SCHEMA = [
896
890
  type: 'object',
897
891
  required: false,
898
892
  default: { id: 'grandmaster', name: 'Grandmaster' },
899
- description: 'Tier granted to the form-owner account ({ id, name }) — Slapform-operator only.',
893
+ description: 'Tier granted to the form-owner account ({ id, name }), for Slapform operators only.',
900
894
  },
901
895
 
902
896
  // ── inbound (role: inbound conversations; per-channel providers) ─────────
@@ -917,7 +911,7 @@ const SHARED_SCHEMA = [
917
911
  path: 'inbound.chat.providers.chatsy.accountId',
918
912
  type: 'string',
919
913
  required: false,
920
- description: "Chatsy owner-account uid the agent belongs to — the account whose subscription the chat service reconciles to `plan`. The service resolves it from the agent itself; set it only when the agent's owner is provisioned elsewhere.",
914
+ description: "Chatsy owner-account uid the agent belongs to, the account whose subscription the chat service reconciles to `plan`. The service resolves it from the agent itself; set it only when the agent's owner is provisioned elsewhere.",
921
915
  },
922
916
  {
923
917
  path: 'inbound.chat.providers.chatsy.templateAgentId',
@@ -937,7 +931,7 @@ const SHARED_SCHEMA = [
937
931
  type: 'object',
938
932
  required: false,
939
933
  default: { id: 'max', name: 'Max' },
940
- description: 'Tier granted to the agent-owner account ({ id, name }) — Chatsy-operator only.',
934
+ description: 'Tier granted to the agent-owner account ({ id, name }), for Chatsy operators only.',
941
935
  },
942
936
  {
943
937
  path: 'inbound.chat.providers.chatsy.sponsorshipsUrl',
@@ -982,7 +976,7 @@ const SHARED_SCHEMA = [
982
976
  type: 'object',
983
977
  required: false,
984
978
  default: { id: 'max', name: 'Max' },
985
- description: 'Tier granted to the agent-owner account ({ id, name }) — Replyify-operator only.',
979
+ description: 'Tier granted to the agent-owner account ({ id, name }), for Replyify operators only.',
986
980
  },
987
981
  {
988
982
  path: 'inbound.email.providers.replyify.discount',
@@ -997,7 +991,7 @@ const SHARED_SCHEMA = [
997
991
  type: 'boolean',
998
992
  required: false,
999
993
  default: true,
1000
- description: 'false = the search service skips entirely — the property is never verified and no sitemap is submitted (submitSitemap only turns the sitemap half off).',
994
+ description: 'false = the search service skips entirely: the property is never verified and no sitemap is submitted (submitSitemap only turns the sitemap half off).',
1001
995
  },
1002
996
  {
1003
997
  path: 'search.providers.searchConsole.submitSitemap',
@@ -1025,13 +1019,13 @@ const SHARED_SCHEMA = [
1025
1019
  path: 'certificates.enabled',
1026
1020
  type: 'boolean',
1027
1021
  required: false,
1028
- description: 'false = the certificates service skips entirely — no bundle ids, no signing certificates, no provisioning profiles for this brand. Absent reads as ON for brands with a desktop/mobile target (the service still skips one with neither).',
1022
+ description: 'false = the certificates service skips entirely: no bundle ids, no signing certificates, no provisioning profiles for this brand. Absent reads as ON for brands with a desktop/mobile target (the service still skips one with neither).',
1029
1023
  },
1030
1024
  {
1031
1025
  path: 'certificates.providers.apple.bundleIdPrefix',
1032
1026
  type: 'string',
1033
1027
  required: false,
1034
- description: "Reverse-DNS prefix the brand's bundle id is composed from ('com.mycompany' + brand.id → com.mycompany.my.brand). The brand's own answer — the onboard wizard seeds it and setup asks for it (#635); unset, the Apple operations have no id to reconcile.",
1028
+ description: "Reverse-DNS prefix the brand's bundle id is composed from ('com.mycompany' + brand.id → com.mycompany.my.brand). The brand's own answer: the onboard wizard seeds it and setup asks for it; unset, the Apple operations have no id to reconcile.",
1035
1029
  },
1036
1030
  {
1037
1031
  path: 'certificates.providers.apple.capabilities',
@@ -1049,7 +1043,7 @@ const SHARED_SCHEMA = [
1049
1043
  path: 'certificates.providers.apple.certificates',
1050
1044
  type: 'array',
1051
1045
  required: false,
1052
- description: "Certificate types managed for the Apple Developer account ([{ type, manual }]) — `manual: true` marks the ones Apple's API cannot create, which the Account Holder downloads from the portal. Unset uses the framework set.",
1046
+ description: "Certificate types managed for the Apple Developer account ([{ type, manual }]); `manual: true` marks the ones Apple's API cannot create, which the Account Holder downloads from the portal. Unset uses the framework set.",
1053
1047
  },
1054
1048
 
1055
1049
  // ── publishing (role: ship credentials; no provider) ─────────────────
@@ -1057,7 +1051,7 @@ const SHARED_SCHEMA = [
1057
1051
  path: 'publishing.enabled',
1058
1052
  type: 'boolean',
1059
1053
  required: false,
1060
- description: "The manage walk's ship-credential gate ([#867](https://github.com/Omega-JS-Stack/omega/issues/867)): the `publishing` service asks for every developer key and listing id the brand's declared formats need (`targets.<name>.platforms.<platform>.formats`). `false` is the permanent opt-out the gate's Disable lands; absence means ask, and no default is materialized. Dropping ONE store is the declaration's job (`platforms.<store>.formats.store: false`), never this switch.",
1054
+ description: "The manage walk's ship-credential gate: the `publishing` service asks for every developer key and listing id the brand's declared formats need (`targets.<name>.platforms.<platform>.formats`). `false` is the permanent opt-out the gate's Disable lands; absence means ask, and no default is materialized. Dropping ONE store is the declaration's job (`platforms.<store>.formats.store: false`), never this switch.",
1061
1055
  },
1062
1056
 
1063
1057
  // ── devlog (role: commit-digest publishing; provider-discriminated) ──────
@@ -1071,7 +1065,7 @@ const SHARED_SCHEMA = [
1071
1065
  path: 'devlog.enabled',
1072
1066
  type: 'boolean',
1073
1067
  required: false,
1074
- description: 'true PUBLISHES AI-written commit-digest posts to the brand\'s live website. Case 3 (docs/shared/config.md): the literal true is the only ON, absence is off, and no default is materialized — publishing is never implicit.',
1068
+ description: "true PUBLISHES AI-written commit-digest posts to the brand's live website. Case 3 (docs/shared/config.md): the literal true is the only ON, absence is off, and no default is materialized, so publishing is never implicit.",
1075
1069
  },
1076
1070
 
1077
1071
  // ── seo ──────────────────────────────────────────────────────────────────
@@ -1079,7 +1073,7 @@ const SHARED_SCHEMA = [
1079
1073
  path: 'seo',
1080
1074
  type: 'object',
1081
1075
  required: false,
1082
- description: 'Parasite-SEO content: seo.github.content is the @omega.js/manager seo service\'s repos, and big content blocks may live in the config/seo.json5 sidecar. The indexing switch is NOT here: it is `targets.web.meta.index`, the same name a page writes (#564).',
1076
+ description: "Parasite-SEO content: seo.github.content is the @omega.js/manager seo service's repos, and big content blocks may live in the config/seo.json5 sidecar. The indexing switch is NOT here: it is `targets.web.meta.index`, the same name a page writes.",
1083
1077
  },
1084
1078
 
1085
1079
  // ── account ──────────────────────────────────────────────────────────────
@@ -1087,7 +1081,7 @@ const SHARED_SCHEMA = [
1087
1081
  path: 'account',
1088
1082
  type: 'object',
1089
1083
  required: false,
1090
- description: "Managed Firebase Auth accounts (@omega.js/manager account service): enabled + admins ([{ email, account, marketing }]; '{domain}' templates to the brand domain). Owner-defined — typically set once in the COMPANY omega.json5 (arrays replace, so the company list wins whole). Passwords NEVER live here: per-account OMEGA_ACCOUNT_PASSWORD__* env vars, the config/hooks/account/password.js hook, or the ACCOUNT_PASSWORD_SEED derivation.",
1084
+ description: "Managed Firebase Auth accounts (@omega.js/manager account service): enabled + admins ([{ email, account, marketing }]; '{domain}' templates to the brand domain). Owner-defined, typically set once in the COMPANY omega.json5 (arrays replace, so the company list wins whole). Passwords NEVER live here: per-account OMEGA_ACCOUNT_PASSWORD__* env vars, the config/hooks/account/password.js hook, or the ACCOUNT_PASSWORD_SEED derivation.",
1091
1085
  },
1092
1086
 
1093
1087
  // ── reviews ──────────────────────────────────────────────────────────────
@@ -1143,7 +1137,7 @@ const SHARED_SCHEMA = [
1143
1137
  path: 'marketing.campaigns.providers.sendgrid.groups.account',
1144
1138
  type: 'integer',
1145
1139
  required: false,
1146
- description: "SendGrid unsubscribe (ASM) group id for account-action email (deletion, data requests) — also the fallback for any send naming no group. Written back by the campaigns service, which provisions the group by name; ASM ids are per SendGrid ACCOUNT, so this is config and never code. Unset makes @omega.js/backend fail the send loudly.",
1140
+ description: 'SendGrid unsubscribe (ASM) group id for account-action email (deletion, data requests), and the fallback for any send naming no group. Written back by the campaigns service, which provisions the group by name; ASM ids are per SendGrid ACCOUNT, so this is config and never code. Unset makes @omega.js/backend fail the send loudly.',
1147
1141
  },
1148
1142
  {
1149
1143
  path: 'marketing.campaigns.providers.sendgrid.groups.marketing',
@@ -1186,14 +1180,14 @@ const SHARED_SCHEMA = [
1186
1180
  path: 'marketing.newsletter.content',
1187
1181
  type: 'object|array',
1188
1182
  required: false,
1189
- description: 'Newsletter-PIPELINE config (sources, categories, tone, template, theme, sponsorships) — a single object or an array whose first entry the generator uses. Role-level on purpose: it configures @omega.js/backend\'s generator, not Beehiiv.',
1183
+ description: "Newsletter-PIPELINE config (sources, categories, tone, template, theme, sponsorships), a single object or an array whose first entry the generator uses. Role-level on purpose: it configures @omega.js/backend's generator, not Beehiiv.",
1190
1184
  },
1191
1185
  {
1192
1186
  path: 'marketing.prune.enabled',
1193
1187
  type: 'boolean',
1194
1188
  required: false,
1195
1189
  default: true,
1196
- description: 'Monthly cold-contact prune across both providers. ON by default (Ian 2026-08-22) and materialized into every brand config — false is the per-brand off switch.',
1190
+ description: 'Monthly cold-contact prune across both providers. ON by default and materialized into every brand config; false is the per-brand off switch.',
1197
1191
  },
1198
1192
 
1199
1193
  // ── blog ─────────────────────────────────────────────────────────────────
@@ -1221,14 +1215,14 @@ const SHARED_SCHEMA = [
1221
1215
  path: 'directory',
1222
1216
  type: 'object',
1223
1217
  required: false,
1224
- description: "Directory participation (@omega.js/manager directory service): { enabled } opts in to pushing this brand's entry into the parent project's `brands` collection. Absent or false never pushes. Needs `company.id` to name a parent (#677) and DIRECTORY_SERVICE_ACCOUNT in the brand .env.",
1218
+ description: "Directory participation (@omega.js/manager directory service): { enabled } opts in to pushing this brand's entry into the parent project's `brands` collection. Absent or false never pushes. Needs `company.id` to name a parent and DIRECTORY_SERVICE_ACCOUNT in the brand .env.",
1225
1219
  },
1226
1220
  {
1227
1221
  path: 'directory.enabled',
1228
1222
  type: 'boolean',
1229
1223
  required: false,
1230
1224
  default: false,
1231
- description: 'true opts the brand into the directory push. Default false — participation is never implicit.',
1225
+ description: 'true opts the brand into the directory push. Default false, so participation is never implicit.',
1232
1226
  },
1233
1227
 
1234
1228
  // ── sponsorships ─────────────────────────────────────────────────────────
@@ -1236,7 +1230,7 @@ const SHARED_SCHEMA = [
1236
1230
  path: 'sponsorships',
1237
1231
  type: 'object',
1238
1232
  required: false,
1239
- description: "The brand's sponsorship terms — the first directory BLOCK (#246), and the section the server service also publishes. Public terms only: { acceptable, unacceptable, prices }.",
1233
+ description: "The brand's sponsorship terms, the first directory BLOCK and the section the server service also publishes. Public terms only: { acceptable, unacceptable, prices }.",
1240
1234
  },
1241
1235
  {
1242
1236
  path: 'sponsorships.acceptable',
@@ -1254,7 +1248,7 @@ const SHARED_SCHEMA = [
1254
1248
  path: 'sponsorships.prices',
1255
1249
  type: 'object',
1256
1250
  required: false,
1257
- description: "Price in USD per placement type — placement key to number ({ 'guest-post': 70, 'link-insertion': 50 }). A placement with no price is not for sale.",
1251
+ description: "Price in USD per placement type, placement key to number ({ 'guest-post': 70, 'link-insertion': 50 }). A placement with no price is not for sale.",
1258
1252
  },
1259
1253
 
1260
1254
  // ── ports (dev-only) ─────────────────────────────────────────────────────
@@ -1262,7 +1256,7 @@ const SHARED_SCHEMA = [
1262
1256
  path: 'ports',
1263
1257
  type: 'object',
1264
1258
  required: false,
1265
- description: 'Explicit dev-port pins (N7). Unset ports auto-allocate (classic defaults, bump-if-taken); a pinned port never bumps — busy pin is a hard error. Keys: auth, functions, firestore, database, hosting, storage, pubsub, ui, website, livereload, cdp. Dev/emulator only — production never reads this.',
1259
+ description: 'Explicit dev-port pins. Unset ports auto-allocate (classic defaults, bump-if-taken); a pinned port never bumps, and a busy pin is a hard error. Keys: auth, functions, firestore, database, hosting, storage, pubsub, ui, website, livereload, cdp. Dev/emulator only; production never reads this.',
1266
1260
  },
1267
1261
 
1268
1262
  // ── theme ────────────────────────────────────────────────────────────────
@@ -1270,7 +1264,7 @@ const SHARED_SCHEMA = [
1270
1264
  path: 'theme.id',
1271
1265
  type: 'string',
1272
1266
  required: false,
1273
- description: "Theme id (seeded 'classy' at onboarding). Project-owned — the manager never overwrites it.",
1267
+ description: "Theme id (seeded 'classy' at onboarding). Project-owned: the manager never overwrites it.",
1274
1268
  },
1275
1269
  {
1276
1270
  path: 'theme.appearance',
@@ -1285,7 +1279,7 @@ const SHARED_SCHEMA = [
1285
1279
  path: 'ai.enabled',
1286
1280
  type: 'boolean',
1287
1281
  required: false,
1288
- description: "The manager's AI-provider setup gate ([#639](https://github.com/Omega-JS-Stack/omega/issues/639)): the `ai` service asks once for OPENAI_API_KEY / ANTHROPIC_API_KEY. `false` is the permanent opt-out the gate's Disable lands; absence means ask. No default is materialized — a brand that never answers is never re-shaped.",
1282
+ description: "The manager's AI-provider setup gate: the `ai` service asks once for OPENAI_API_KEY / ANTHROPIC_API_KEY. `false` is the permanent opt-out the gate's Disable lands; absence means ask. No default is materialized, so a brand that never answers is never re-shaped.",
1289
1283
  },
1290
1284
 
1291
1285
  // ── translation ──────────────────────────────────────────────────────────
@@ -1313,7 +1307,7 @@ const SHARED_SCHEMA = [
1313
1307
  path: 'translation.providers',
1314
1308
  type: 'object',
1315
1309
  required: false,
1316
- description: "AI translation provider, keyed by name — presence picks the engine ({ claude: {} } or { chatgpt: {} }). 'claude' (the default when the block is absent) rides the local Claude Code install — no API key; 'chatgpt' uses the OpenAI API via OPENAI_API_KEY in env.",
1310
+ description: "AI translation provider, keyed by name; presence picks the engine ({ claude: {} } or { chatgpt: {} }). 'claude' (the default when the block is absent) rides the local Claude Code install with no API key; 'chatgpt' uses the OpenAI API via OPENAI_API_KEY in env.",
1317
1311
  },
1318
1312
  {
1319
1313
  path: 'translation.model',
@@ -1333,65 +1327,15 @@ const SHARED_SCHEMA = [
1333
1327
  description: "Web only: the page routes to translate, as globs with `!` negation, read in order like a .gitignore: the LAST pattern that matches a route decides it, and a route no pattern matches is not translated. A folder pattern covers the folder itself (`!blog/**` excludes `/blog` and everything under it). The default ['**', '!blog/**'] translates the whole site except the blog, which is where the words (and the cost) pile up. A brand list REPLACES the default outright. A page overrides it for itself with `translation.include: true`/`false` in its own frontmatter, the same key name one level down (docs/web/frontmatter.md). The framework's own default pages carry their own exclusion and are never in this list's hands.",
1334
1328
  },
1335
1329
 
1336
- // ── client (the @omega.js/client runtime blob) ───────────────────────────
1337
- // The blob is a settings bag the client normalizes against its own defaults,
1338
- // so it is deliberately NOT enumerated key by key here. `consent` is the
1339
- // exception (#383): it decides whether a visitor is tracked at all, which is
1340
- // a legal surface, not a preference — a typo that silently disabled the
1341
- // banner would ship a site with no consent gate and no error.
1342
- //
1343
- // The three below are the second exception (#650 — found adopting omega in a
1344
- // consumer): keys a BRAND authors. They work (they reach the client payload
1345
- // and change what the site does), so every validate run told the brand that
1346
- // turned one off it might be a typo. The rest of the blob stays the client's.
1347
- {
1348
- path: 'client.consent.enabled',
1349
- type: 'boolean',
1350
- required: false,
1351
- default: true,
1352
- description: 'The consent banner + the provider-script gate (default true). false ships NO banner — legal only for a site that loads no analytics/marketing provider at all.',
1353
- },
1354
- {
1355
- path: 'client.consent.config.position',
1356
- type: 'string',
1357
- required: false,
1358
- enum: ['bottom-left', 'bottom-right', 'bottom'],
1359
- description: "Where the panel sits. 'bottom' is the centered full-width form.",
1360
- },
1361
- {
1362
- path: 'client.consent.config.content',
1363
- type: 'object',
1364
- required: false,
1365
- description: "Banner copy: message, panelIntro, accept, customize, acceptAll, acceptNone (a literal `{terms}`/`{cookies}` links the terms/cookie-policy page). Category labels are framework copy — a brand renames the buttons, not the categories.",
1366
- },
1367
- {
1368
- path: 'client.auth.config.policy',
1369
- type: 'string',
1370
- required: false,
1371
- enum: ['authenticated', 'unauthenticated', 'disabled'],
1372
- description: "Who a page is FOR: 'authenticated' redirects a signed-out visitor to the signin route, 'unauthenticated' redirects a signed-in one away, 'disabled' skips the auth module entirely (a vert iframe). Absent = no policy, which is the site-wide answer — the auth/admin layouts set theirs in page frontmatter, so a brand only sets this to blanket a whole site.",
1373
- },
1374
- {
1375
- path: 'client.exitPopup.enabled',
1376
- type: 'boolean',
1377
- required: false,
1378
- default: true,
1379
- description: 'The exit-intent offer popup (default true). false ships no popup at all; its copy, timeout and avatars live under client.exitPopup.config.',
1380
- },
1381
- {
1382
- path: 'client.serviceWorker.enabled',
1383
- type: 'boolean',
1384
- required: false,
1385
- default: true,
1386
- description: 'Registers the site service worker (default true) — the offline/refresh lane and the push-notification registration ride it. false unregisters any worker the visitor already has.',
1387
- },
1330
+ // ── client (the @omega.js/client runtime blob): its own file ────────────
1331
+ ...CLIENT_RULES,
1388
1332
 
1389
1333
  // ── targets ──────────────────────────────────────────────────────────────
1390
1334
  {
1391
1335
  path: 'targets',
1392
1336
  type: 'object',
1393
1337
  required: false,
1394
- description: "Every key is a target NAME, which is the folder `targets/<name>`, the `--target=<name>` word and the derived-repo suffix (#886). Key presence = target enabled; the value is an object of target-scoped config (any shared key inside overrides it) and MUST declare `type`: web/backend/desktop/extension/mobile, or `custom` (#603) for a target the manager drives entirely through its own package.json scripts. Anything else is an error.",
1338
+ description: 'Every key is a target NAME, which is the folder `targets/<name>`, the `--target=<name>` word and the derived-repo suffix. Key presence = target enabled; the value is an object of target-scoped config (any shared key inside overrides it) and MUST declare `type`: web/backend/desktop/extension/mobile, or `custom` for a target the manager drives entirely through its own package.json scripts. Anything else is an error.',
1395
1339
  },
1396
1340
  ];
1397
1341
 
@@ -1405,7 +1349,7 @@ const TARGET_SCHEMAS = {
1405
1349
  path: 'hosting',
1406
1350
  type: 'object',
1407
1351
  required: false,
1408
- description: 'Where this web target is SERVED from (#883). Web targets only: a non-web target carrying it is a validation error.',
1352
+ description: 'Where this web target is SERVED from. Web targets only: a non-web target carrying it is a validation error.',
1409
1353
  },
1410
1354
  {
1411
1355
  path: 'hosting.provider',
@@ -1428,7 +1372,7 @@ const TARGET_SCHEMAS = {
1428
1372
  type: 'boolean',
1429
1373
  required: false,
1430
1374
  default: true,
1431
- description: "false takes the WHOLE site out of search: every page emits noindex and sitemap.xml, llms.txt and pages.json list nothing. It is the site-wide DEFAULT of the one flag every signal reads (#564), so a page's own `meta.index: true` still exempts it. Case 2 (docs/shared/config.md): default ON, and the literal false is the only OFF.",
1375
+ description: "false takes the WHOLE site out of search: every page emits noindex and sitemap.xml, llms.txt and pages.json list nothing. It is the site-wide DEFAULT of the one flag every signal reads, so a page's own `meta.index: true` still exempts it. Case 2 (docs/shared/config.md): default ON, and the literal false is the only OFF.",
1432
1376
  },
1433
1377
  {
1434
1378
  path: 'imagemin',
@@ -1440,13 +1384,13 @@ const TARGET_SCHEMAS = {
1440
1384
  path: 'dev.limitCollections',
1441
1385
  type: 'object',
1442
1386
  required: false,
1443
- description: "Dev-only collection sampling (#190): collection name → max documents ({ posts: 50 }), plus `randomize: true` for a random sample instead of the first N. Collection names are @omega.js/web's (posts, alternatives, team, updates) plus the brand's own `collections`, and the engine hard-fails an unknown one. Development builds only — production never samples.",
1387
+ description: "Dev-only collection sampling: collection name → max documents ({ posts: 50 }), plus `randomize: true` for a random sample instead of the first N. Collection names are @omega.js/web's (posts, alternatives, team, updates) plus the brand's own `collections`, and the engine hard-fails an unknown one. Development builds only; production never samples.",
1444
1388
  },
1445
1389
  {
1446
1390
  path: 'collections',
1447
1391
  type: 'object',
1448
1392
  required: false,
1449
- description: "The brand's own content collections (#207): collection name → { field, size, title, description, permalink }. Documents live in `_<name>/`, and @omega.js/web generates the paginated listing page plus one page per category of `field` (the dotted frontmatter path the categories group on, e.g. 'doc.category'). A built-in collection name (posts, alternatives, team, updates) is an error.",
1393
+ description: "The brand's own content collections: collection name → { field, size, title, description, permalink }. Documents live in `_<name>/`, and @omega.js/web generates the paginated listing page plus one page per category of `field` (the dotted frontmatter path the categories group on, e.g. 'doc.category'). A built-in collection name (posts, alternatives, team, updates) is an error.",
1450
1394
  },
1451
1395
  {
1452
1396
  path: 'purgecss',
@@ -1458,7 +1402,7 @@ const TARGET_SCHEMAS = {
1458
1402
  path: 'purgecss.safelist',
1459
1403
  type: 'object|array',
1460
1404
  required: false,
1461
- description: "Selectors the content scan cannot see, merged OVER the framework's own safelist (#250). The object form takes PurgeCSS's lanes (standard, deep, greedy, keyframes); a bare array is PurgeCSS's shorthand for `standard`.",
1405
+ description: "Selectors the content scan cannot see, merged OVER the framework's own safelist. The object form takes PurgeCSS's lanes (standard, deep, greedy, keyframes); a bare array is PurgeCSS's shorthand for `standard`.",
1462
1406
  },
1463
1407
  {
1464
1408
  path: 'purgecss.safelist.standard',
@@ -1476,7 +1420,7 @@ const TARGET_SCHEMAS = {
1476
1420
  path: 'purgecss.safelist.greedy',
1477
1421
  type: 'array',
1478
1422
  required: false,
1479
- description: 'Patterns (strings, compiled to RegExp) keeping every selector that contains a match — the lane for a runtime-stamped namespace.',
1423
+ description: 'Patterns (strings, compiled to RegExp) keeping every selector that contains a match, the lane for a runtime-stamped namespace.',
1480
1424
  },
1481
1425
  {
1482
1426
  path: 'purgecss.safelist.keyframes',
@@ -1486,8 +1430,9 @@ const TARGET_SCHEMAS = {
1486
1430
  },
1487
1431
  ],
1488
1432
 
1489
- // Seeded from the sandbox brand's real @omega.js/backend config (backend-manager-config.json).
1433
+ // Seeded from the sandbox brand's real @omega.js/backend config.
1490
1434
  backend: [
1435
+ ...OVERRIDE_RULES.backend,
1491
1436
  // reviews, marketing, blog, dataRequest moved to SHARED_SCHEMA (#277); a
1492
1437
  // targets.backend block still overrides them through the merge chain.
1493
1438
  {
@@ -1495,7 +1440,7 @@ const TARGET_SCHEMAS = {
1495
1440
  type: 'string',
1496
1441
  required: false,
1497
1442
  enum: BACKEND_PROJECT_TYPES,
1498
- description: "How this backend RUNS and deploys (#584). 'firebase' (default) exports Cloud Functions; 'custom' runs the same app as an Express server on `PORT` for a container host (Render & co) — no Functions deploy, no emulator lane, and the target's own `deploy` script is the publish lane. Everything else — the auth middleware, the routes, the schemas, every helper — is identical in both modes.",
1443
+ description: "How this backend RUNS and deploys. 'firebase' (default) exports Cloud Functions; 'custom' runs the same app as an Express server on `PORT` for a container host (Render & co), with no Functions deploy, no emulator lane, and the target's own `deploy` script as the publish lane. Everything else (the auth middleware, the routes, the schemas, every helper) is identical in both modes.",
1499
1444
  },
1500
1445
  {
1501
1446
  path: 'auth.signup.maxPerIpPerDay',
@@ -1506,9 +1451,9 @@ const TARGET_SCHEMAS = {
1506
1451
  },
1507
1452
  ],
1508
1453
 
1509
- // Seeded from EM's proven src/config/schema.js. EM's per-OS `targets` key
1510
- // is renamed `platforms` here (avoids targets.desktop.targets).
1454
+ // The per-OS key is `platforms` (avoids targets.desktop.targets).
1511
1455
  desktop: [
1456
+ ...OVERRIDE_RULES.desktop,
1512
1457
  {
1513
1458
  path: 'app.category',
1514
1459
  type: 'string',
@@ -1524,7 +1469,7 @@ const TARGET_SCHEMAS = {
1524
1469
  path: 'app.appId',
1525
1470
  type: 'string',
1526
1471
  required: false,
1527
- description: "The bundle identifier the app is signed and registered under. Unset, it derives as `certificates.providers.apple.bundleIdPrefix` plus `brand.id` with the dashes as dots ([#909](https://github.com/Omega-JS-Stack/omega/issues/909)), the very id the certificates service registers; a brand with no prefix falls back to the reverse-domain of `brand.url`, then `app.<brand.id>`. Set it only to stay on an id already-shipped builds carry.",
1472
+ description: 'The bundle identifier the app is signed and registered under. Unset, it derives as `certificates.providers.apple.bundleIdPrefix` plus `brand.id` with the dashes as dots, the very id the certificates service registers; a brand with no prefix falls back to the reverse-domain of `brand.url`, then `app.<brand.id>`. Set it only to stay on an id already-shipped builds carry.',
1528
1473
  },
1529
1474
  {
1530
1475
  path: 'app.productName',
@@ -1567,7 +1512,7 @@ const TARGET_SCHEMAS = {
1567
1512
  path: 'platforms',
1568
1513
  type: 'object',
1569
1514
  required: false,
1570
- description: "What this desktop target SHIPS, plus each platform's install knobs (#867): `platforms.<mac|windows|linux>.formats.<dmg|nsis|deb|appimage|snap>`. Presence = enabled; drop a default with `false`. The install knobs beside `formats` (arch, the NSIS flags, mac entitlements) map onto the same electron-builder concepts, which @omega.js/desktop owns the target list of.",
1515
+ description: "What this desktop target SHIPS, plus each platform's install knobs: `platforms.<mac|windows|linux>.formats.<dmg|nsis|deb|appimage|snap>`. Presence = enabled; drop a default with `false`. The install knobs beside `formats` (arch, the NSIS flags, mac entitlements) map onto the same electron-builder concepts, which @omega.js/desktop owns the target list of.",
1571
1516
  },
1572
1517
  {
1573
1518
  path: 'platforms.mac',
@@ -1703,7 +1648,7 @@ const TARGET_SCHEMAS = {
1703
1648
  path: 'autoUpdate.autoDownload',
1704
1649
  type: 'boolean',
1705
1650
  required: false,
1706
- description: 'Whether a found update downloads on its own. Unset reads as true; false leaves the download to `manager.autoUpdater` being asked for it.',
1651
+ description: 'Whether a found update downloads on its own. Unset reads as true; false leaves the download to `omega.autoUpdater` being asked for it.',
1707
1652
  },
1708
1653
  {
1709
1654
  path: 'autoUpdate.startupDelayMs',
@@ -1749,7 +1694,7 @@ const TARGET_SCHEMAS = {
1749
1694
  path: 'releases',
1750
1695
  type: 'object',
1751
1696
  required: false,
1752
- description: "Presence opts the brand into the release surface (the site's download links, desktop publishing). WHICH repo is not configurable: it is the brand's one public `<brand.id>-releases` (#883).",
1697
+ description: "Presence opts the brand into the release surface (the site's download links, desktop publishing). WHICH repo is not configurable: it is the brand's one public `<brand.id>-releases`.",
1753
1698
  },
1754
1699
  {
1755
1700
  path: 'restartManager.enabled',
@@ -1789,14 +1734,14 @@ const TARGET_SCHEMAS = {
1789
1734
  path: 'remoteScripts.enabled',
1790
1735
  type: 'boolean',
1791
1736
  required: false,
1792
- description: 'Emergency remote code execution in the main process — OPT-IN: only `true` arms the lane, anything else leaves it inert.',
1737
+ description: 'Emergency remote code execution in the main process, OPT-IN: only `true` arms the lane, anything else leaves it inert.',
1793
1738
  },
1794
1739
  {
1795
1740
  path: 'remoteScripts.url',
1796
1741
  type: 'string',
1797
1742
  required: false,
1798
1743
  match: /^https?:\/\//,
1799
- description: 'Remote-script source override (default `${brand.url}/data/scripts/main.js`). https-only at runtime — a cleartext URL is refused (localhost excepted for dev).',
1744
+ description: 'Remote-script source override (default `${brand.url}/data/scripts/main.js`). https-only at runtime: a cleartext URL is refused (localhost excepted for dev).',
1800
1745
  },
1801
1746
  {
1802
1747
  path: 'restartManager.feed.url',
@@ -1818,7 +1763,7 @@ const TARGET_SCHEMAS = {
1818
1763
  path: 'platforms',
1819
1764
  type: 'object',
1820
1765
  required: false,
1821
- description: 'What this extension target SHIPS (#867): `platforms.<chrome|firefox|edge>.formats.<zip|store>`. Presence = enabled; drop a default with `false`. The zip is attached to the release, the store is published to, and @omega.js/config\'s platforms.js says which credentials each store cannot ship without.',
1766
+ description: "What this extension target SHIPS: `platforms.<chrome|firefox|edge>.formats.<zip|store>`. Presence = enabled; drop a default with `false`. The zip is attached to the release, the store is published to, and @omega.js/config's platforms.js says which credentials each store cannot ship without.",
1822
1767
  },
1823
1768
  {
1824
1769
  path: 'platforms.chrome',
@@ -1842,7 +1787,7 @@ const TARGET_SCHEMAS = {
1842
1787
  path: 'platforms.chrome.formats.store',
1843
1788
  type: 'object|boolean',
1844
1789
  required: false,
1845
- description: 'The Chrome Web Store publish. Needs the CHROME_* API credentials plus `listings.chrome.id` (the walk asks for both, #893).',
1790
+ description: 'The Chrome Web Store publish. Needs the CHROME_* API credentials plus `listings.chrome.id` (the walk asks for both).',
1846
1791
  },
1847
1792
  {
1848
1793
  path: 'platforms.firefox',
@@ -1897,7 +1842,7 @@ const TARGET_SCHEMAS = {
1897
1842
  path: 'platforms.edge.formats.store',
1898
1843
  type: 'object|boolean',
1899
1844
  required: false,
1900
- description: 'The Edge Add-ons publish. Needs EDGE_CLIENT_ID + EDGE_API_KEY plus `listings.edge.id` (the Partner Center product id, #893).',
1845
+ description: 'The Edge Add-ons publish. Needs EDGE_CLIENT_ID + EDGE_API_KEY plus `listings.edge.id` (the Partner Center product id).',
1901
1846
  },
1902
1847
  {
1903
1848
  path: 'categories',
@@ -1905,20 +1850,20 @@ const TARGET_SCHEMAS = {
1905
1850
  required: false,
1906
1851
  itemEnum: AMO_CATEGORIES,
1907
1852
  default: ['alerts-updates'],
1908
- description: "The AMO categories a FIRST Firefox publish lists the add-on under (#884): addons.mozilla.org requires them on a new listing, and the Firefox lane writes them into the `--amo-metadata` file it hands `web-ext sign`. Slugs come from AMO's own set; the default is `alerts-updates`, the most generic one that is not `other`. Chrome and Edge pick their category in their own dashboards.",
1853
+ description: "The AMO categories a FIRST Firefox publish lists the add-on under: addons.mozilla.org requires them on a new listing, and the Firefox lane writes them into the `--amo-metadata` file it hands `web-ext sign`. Slugs come from AMO's own set; the default is `alerts-updates`, the most generic one that is not `other`. Chrome and Edge pick their category in their own dashboards.",
1909
1854
  },
1910
1855
  {
1911
1856
  path: 'listings.chrome.id',
1912
1857
  type: 'string',
1913
1858
  required: false,
1914
- description: "The Chrome Web Store ITEM ID the publish uploads to, the 32-letter id in the listing URL (#893). Public by design, so it lives here and not in .env; the manage walk asks for it.",
1859
+ description: 'The Chrome Web Store ITEM ID the publish uploads to, the 32-letter id in the listing URL. Public by design, so it lives here and not in .env; the manage walk asks for it.',
1915
1860
  },
1916
1861
  {
1917
1862
  path: 'listings.chrome.url',
1918
1863
  type: 'string',
1919
1864
  required: false,
1920
1865
  match: /^https?:\/\//,
1921
- description: "Chrome Web Store listing URL. Feeds site.targets.extension.listings.chrome — the /extension page's button and its /extension/chrome shortlink.",
1866
+ description: "Chrome Web Store listing URL. Feeds site.targets.extension.listings.chrome, the /extension page's button and its /extension/chrome shortlink.",
1922
1867
  },
1923
1868
  {
1924
1869
  path: 'listings.chrome.state',
@@ -1930,7 +1875,7 @@ const TARGET_SCHEMAS = {
1930
1875
  path: 'listings.firefox.id',
1931
1876
  type: 'string',
1932
1877
  required: false,
1933
- description: "The addons.mozilla.org ADD-ON ID, which IS the manifest's browser_specific_settings.gecko.id (AMO uses it as the add-on guid, #893). Declared here it is authoritative: the firefox package writes it into the manifest. A brand that declares none gets the derived id pinned here by the extension's local scaffold, never by a publish.",
1878
+ description: "The addons.mozilla.org ADD-ON ID, which IS the manifest's browser_specific_settings.gecko.id (AMO uses it as the add-on guid). Declared here it is authoritative: the firefox package writes it into the manifest. A brand that declares none gets the derived id pinned here by the extension's local scaffold, never by a publish.",
1934
1879
  },
1935
1880
  {
1936
1881
  path: 'listings.firefox.url',
@@ -1949,7 +1894,7 @@ const TARGET_SCHEMAS = {
1949
1894
  path: 'listings.edge.id',
1950
1895
  type: 'string',
1951
1896
  required: false,
1952
- description: "The Microsoft Edge Partner Center PRODUCT ID (a GUID) the publish uploads to (#893). Public by design, so it lives here and not in .env; the manage walk asks for it.",
1897
+ description: 'The Microsoft Edge Partner Center PRODUCT ID (a GUID) the publish uploads to. Public by design, so it lives here and not in .env; the manage walk asks for it.',
1953
1898
  },
1954
1899
  {
1955
1900
  path: 'listings.edge.url',
@@ -2010,10 +1955,9 @@ const TARGET_SCHEMAS = {
2010
1955
  };
2011
1956
 
2012
1957
  // Declared top-level keys that are NOT config sections: `targets` is scoping
2013
- // machinery, and `url` is this target's RESOLVED public url (#588): a value
2014
- // the loader derives, carried to templates as the `site.url` build fact, never
2015
- // a namespace a page's `config:` block overrides.
2016
- const NON_SECTION_KEYS = ['targets', 'url', 'type'];
1958
+ // machinery, `url` the target's RESOLVED public url (the `site.url` build fact),
1959
+ // `type` an entry's framework, and `enabled` the brand's on/off switch.
1960
+ const NON_SECTION_KEYS = ['targets', 'url', 'type', 'enabled'];
2017
1961
 
2018
1962
  /** The top-level sections a rule array declares (machinery keys excluded). */
2019
1963
  const sectionsOf = (rules) => [...new Set(rules.map((rule) => rule.path.split('.')[0]))].filter((section) => !NON_SECTION_KEYS.includes(section));