@omega.js/desktop 0.53.0 → 0.54.1
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.
- package/README.md +38 -38
- package/dist/cli-run.js +4 -1
- package/dist/cli.js +2 -2
- package/dist/commands/cdp/client.js +1 -1
- package/dist/commands/cdp.js +1 -1
- package/dist/commands/clean.js +2 -3
- package/dist/commands/dev.js +25 -0
- package/dist/commands/lib/ensure-target.js +12 -17
- package/dist/commands/lib/migrate.js +17 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/release.js +1 -1
- package/dist/commands/test.js +4 -4
- package/dist/commands/update.js +5 -4
- package/dist/defaults/.github/workflows/build.yml +18 -18
- package/dist/defaults/_.gitignore +0 -2
- package/dist/defaults/_mas/README.md +3 -3
- package/dist/defaults/config/certs/README.md +1 -1
- package/dist/defaults/config/omega.json5 +36 -36
- package/dist/defaults/docs/README.md +3 -3
- package/dist/defaults/gulpfile.js +1 -1
- package/dist/defaults/hooks/build/post.js +1 -1
- package/dist/defaults/hooks/build/pre.js +1 -1
- package/dist/defaults/hooks/notarize/post.js +2 -2
- package/dist/defaults/hooks/release/post.js +1 -1
- package/dist/defaults/hooks/release/pre.js +1 -1
- package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
- package/dist/defaults/src/integrations/context-menu/index.js +11 -11
- package/dist/defaults/src/integrations/menu/index.js +5 -5
- package/dist/defaults/src/integrations/tray/index.js +9 -9
- package/dist/defaults/src/main.js +2 -2
- package/dist/defaults/src/preload.js +1 -1
- package/dist/defaults/test/README.md +3 -3
- package/dist/defaults/test/_init.js +1 -1
- package/dist/gulp/tasks/audit.js +5 -8
- package/dist/lib/restart-manager/index.js +1 -1
- package/dist/lib/restart-manager/install.js +1 -1
- package/dist/lib/restart-manager/protocol.js +1 -1
- package/dist/main.js +4 -3
- package/dist/preload.js +1 -1
- package/dist/test/suites/build/audit.test.js +20 -7
- package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
- package/dist/test/suites/build/cli.test.js +28 -0
- package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
- package/dist/test/suites/build/defaults-scaffold.test.js +19 -5
- package/dist/test/suites/build/deploy-direct.test.js +7 -5
- package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
- package/dist/test/suites/build/deploy-hook.test.js +4 -2
- package/dist/test/suites/build/dev-verb.test.js +67 -0
- package/dist/test/suites/build/ensure-target.test.js +11 -3
- package/dist/test/suites/build/merge-line-files.test.js +6 -6
- package/dist/test/suites/build/migrate.test.js +29 -0
- package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
- package/dist/test/suites/build/runner-env-write.test.js +73 -0
- package/dist/test/suites/build/runner.test.js +9 -8
- package/dist/test/suites/build/setup-scripts.test.js +27 -0
- package/dist/test/suites/build/validate-config.test.js +13 -2
- package/dist/test/suites/build/verb-logs.test.js +20 -0
- package/dist/test/suites/renderer/window-desktop-surface.test.js +1 -1
- package/dist/utils/build-pipeline.js +4 -4
- package/dist/utils/runner-env.js +13 -28
- package/dist/vendor/config/company.js +46 -14
- package/dist/vendor/config/defaults.js +30 -7
- package/dist/vendor/config/edit.js +25 -3
- package/dist/vendor/config/env-delivery.js +1 -1
- package/dist/vendor/config/env-schema.js +3 -6
- package/dist/vendor/config/env.js +34 -22
- package/dist/vendor/config/index.js +13 -17
- package/dist/vendor/config/load.js +15 -7
- package/dist/vendor/config/repo.js +10 -27
- package/dist/vendor/config/schema-client.js +64 -0
- package/dist/vendor/config/schema-cloud.js +38 -0
- package/dist/vendor/config/schema-manager.js +118 -0
- package/dist/vendor/config/schema-overrides.js +68 -0
- package/dist/vendor/config/schema.js +99 -152
- package/dist/vendor/config/validate.js +97 -77
- package/dist/vendor/devkit/agents-md.js +233 -0
- package/dist/vendor/devkit/attach-log-file.js +15 -1
- package/dist/vendor/devkit/ci-workflows.js +30 -30
- package/dist/vendor/devkit/cli-router.js +13 -7
- package/dist/vendor/devkit/defaults-engine.js +9 -43
- package/dist/vendor/devkit/deploy-snapshot.js +44 -9
- package/dist/vendor/devkit/env-lines.js +183 -0
- package/dist/vendor/devkit/local.js +62 -10
- package/dist/vendor/devkit/lockfile.js +32 -13
- package/dist/vendor/devkit/logger.js +7 -2
- package/dist/vendor/devkit/merge-line-files.js +219 -176
- package/dist/vendor/devkit/omega-bin.js +208 -111
- package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
- package/dist/vendor/devkit/preludes/index.js +1 -0
- package/dist/vendor/devkit/target-picker.js +45 -0
- package/dist/vendor/devkit/test/dashed-files.js +37 -0
- package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
- package/dist/vendor/devkit/update.js +15 -15
- package/dist/vendor/devkit/verb-scripts.js +40 -0
- package/dist/vendor/devkit/verbs.js +170 -0
- package/package.json +18 -24
- package/dist/commands/install.js +0 -37
- package/dist/defaults/AGENTS.md +0 -119
- package/dist/defaults/CLAUDE.md +0 -1
- package/dist/vendor/config/env-retired.js +0 -137
- package/dist/vendor/config/retired-keys.js +0 -635
- package/docs/analytics.md +0 -140
- package/docs/app-state.md +0 -92
- package/docs/audit.md +0 -69
- package/docs/auth.md +0 -284
- package/docs/auto-updater.md +0 -243
- package/docs/boot-sequence.md +0 -44
- package/docs/build-system.md +0 -169
- package/docs/cdp-debugging.md +0 -169
- package/docs/common-mistakes.md +0 -21
- package/docs/config-schema.md +0 -120
- package/docs/context-menu.md +0 -112
- package/docs/context.md +0 -81
- package/docs/css.md +0 -84
- package/docs/deep-link.md +0 -186
- package/docs/environment-detection.md +0 -112
- package/docs/fontawesome.md +0 -109
- package/docs/hooks.md +0 -89
- package/docs/icons.md +0 -79
- package/docs/index.md +0 -328
- package/docs/installer-options.md +0 -165
- package/docs/ipc.md +0 -61
- package/docs/lib-modules.md +0 -53
- package/docs/logging.md +0 -227
- package/docs/menu.md +0 -160
- package/docs/releasing.md +0 -239
- package/docs/remote-config.md +0 -118
- package/docs/remote-scripts.md +0 -144
- package/docs/restart-manager.md +0 -144
- package/docs/runner.md +0 -290
- package/docs/sentry.md +0 -97
- package/docs/shared/agent-docs.md +0 -89
- package/docs/shared/analytics.md +0 -612
- package/docs/shared/brands.md +0 -57
- package/docs/shared/breaking-changes.md +0 -917
- package/docs/shared/config.md +0 -1948
- package/docs/shared/deploys.md +0 -341
- package/docs/shared/icons.md +0 -219
- package/docs/shared/local-dev.md +0 -167
- package/docs/shared/logging.md +0 -205
- package/docs/shared/monitoring.md +0 -167
- package/docs/shared/publishing.md +0 -187
- package/docs/shared/rulings.md +0 -34
- package/docs/shared/testing.md +0 -147
- package/docs/shared/theming.md +0 -629
- package/docs/shared/translation.md +0 -342
- package/docs/shared/updates.md +0 -61
- package/docs/signing.md +0 -293
- package/docs/startup.md +0 -142
- package/docs/storage.md +0 -59
- package/docs/templating.md +0 -101
- package/docs/test-boot-layer.md +0 -157
- package/docs/test-framework.md +0 -362
- package/docs/themes.md +0 -149
- package/docs/tooltips.md +0 -99
- package/docs/tray.md +0 -164
- package/docs/usage.md +0 -58
- package/docs/verts.md +0 -62
- package/docs/windows.md +0 -149
package/docs/shared/icons.md
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
# Icons — one Font Awesome mechanism, every surface
|
|
2
|
-
|
|
3
|
-
Authoring is plain Font Awesome markup, everywhere, with **zero setup** and
|
|
4
|
-
nothing to learn ([#619](https://github.com/Omega-JS-Stack/omega/issues/619)):
|
|
5
|
-
|
|
6
|
-
```html
|
|
7
|
-
<i class="fa-solid fa-rocket"></i>
|
|
8
|
-
<i class="fa-brands fa-github"></i>
|
|
9
|
-
<i class="fa-sharp fa-light fa-bolt"></i> <!-- Pro, when the brand supplies it -->
|
|
10
|
-
<i class="omega-flag omega-flag-us"></i> <!-- the flags namespace -->
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
```js
|
|
14
|
-
el.className = 'fa-solid fa-play'; // set OR CHANGED via JS, any time —
|
|
15
|
-
el.className = 'fa-solid fa-stop'; // the icon re-renders in place
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
There is no icon TAG, no `prerender_icons` list, no icon font, no CDN and no
|
|
19
|
-
sprite system. SVGs land inline, sized `1em`/`currentColor` (they scale with
|
|
20
|
-
font-size and inherit text color), `overflow="visible"` (FA 7 glyphs may
|
|
21
|
-
overdraw their viewBox).
|
|
22
|
-
|
|
23
|
-
**Emojis are text**, not icons: type the character. Nothing to build.
|
|
24
|
-
|
|
25
|
-
**A DATA key that carries an icon carries the same string**: every chrome
|
|
26
|
-
`icon` (nav, sidebar, topbar, page header, account dropdown, account section
|
|
27
|
-
header, footer) is the full class string (`icon: 'fa-brands fa-github'`), never
|
|
28
|
-
a bare name a template wraps
|
|
29
|
-
([#903](https://github.com/Omega-JS-Stack/omega/issues/903)). One shape to
|
|
30
|
-
author, and every family the brand's set carries is reachable from data.
|
|
31
|
-
|
|
32
|
-
The same string is what a SECTION arg, a page-layout arg and the feature
|
|
33
|
-
catalog's `icon` carry
|
|
34
|
-
([#929](https://github.com/Omega-JS-Stack/omega/issues/929)): `{% section
|
|
35
|
-
"marketing/stats" %}` takes `icon: 'fa-brands fa-figma'`, and every template
|
|
36
|
-
and runtime builder emits the value verbatim, adding only its own size and
|
|
37
|
-
spacing classes. One key, one shape, wherever it lives. The two keys that name
|
|
38
|
-
a PLATFORM rather than an icon (the footer's `socials` block and a team
|
|
39
|
-
member's link `id`) stay platform keys: a social profile is always a brand
|
|
40
|
-
mark, so the family is derived at the one site that knows it and a brand never
|
|
41
|
-
types a class for a platform it only named.
|
|
42
|
-
|
|
43
|
-
## The two halves
|
|
44
|
-
|
|
45
|
-
| Half | When | What happens |
|
|
46
|
-
|------|------|--------------|
|
|
47
|
-
| **Build inlining** | every icon present in the RENDERED HTML | The SVG is inlined into the `<i>`. Tiny payload, no flash, no runtime fetch, purge-safe. |
|
|
48
|
-
| **Runtime upgrading** | every icon JS creates afterwards, and every class change | A MutationObserver fetches that ONE icon from the site's own set. Zero cost until an icon is actually requested. |
|
|
49
|
-
|
|
50
|
-
Both halves emit exactly the same markup — the `<i>` carries
|
|
51
|
-
`data-omega-fa="<style>/<name>"` and holds the SVG — so nothing can render one
|
|
52
|
-
icon two ways, and the build's stamp is what tells the watcher to leave the
|
|
53
|
-
element alone.
|
|
54
|
-
|
|
55
|
-
## The pieces
|
|
56
|
-
|
|
57
|
-
| Piece | Home | What it owns |
|
|
58
|
-
|-------|------|--------------|
|
|
59
|
-
| `icon-core` | `@omega.js/client/modules/icon-core.js` | Pure semantics: name/style validation, `parseIconClasses` (FA's family × weight class model, plus the `omega-flag-*` namespace), candidate lookup order (style dir → brands fallback), SVG root attributes, alias mapping, the package preference order (`PACKAGES`), the emitted dir name (`ICONS_DIR` — `icons`) |
|
|
60
|
-
| `icon-renderer` | `@omega.js/client/modules/icon-renderer.js` | The ONE browser auto-render: scan + MutationObserver (insertions AND class changes), render/re-render/clear, caching. Transport-injected — callers pass one `resolve(name, style) → Promise<svg\|null>` |
|
|
61
|
-
| Build-side reads | `@omega.js/devkit/icons` | `resolveFontAwesomeRoots()` (the root chain), `createIconLoader()` (read one icon out of it, aliases and brands fallback included), `emitIcons()` (ship the merged set into a build output), `ICONS_DIR` (icon-core's, re-exported so the build side has one import) |
|
|
62
|
-
| Web's build pass | `@omega.js/web` `src/inline-icons.js` | The post-render transform over every rendered page (registered in `src/engine.js` beside the cache-breaker) |
|
|
63
|
-
| Web's runtime | `@omega.js/web` `runtime/icons.js` | The transport + the watcher, started by `runtime/boot.js` on EVERY page, main bundle or not |
|
|
64
|
-
|
|
65
|
-
The emitted set is `assets/icons/<style>/<name>.svg`, with the country flags
|
|
66
|
-
one namespace over at `assets/icons/flags/<country>.svg`. It carries every
|
|
67
|
-
style the brand's chain supplies: the free floor plus whatever Pro set the
|
|
68
|
-
brand brought. Hosting deploys diff by hash and browsers fetch only the icons
|
|
69
|
-
a page actually asks for.
|
|
70
|
-
|
|
71
|
-
## Per-target transport (the only non-shared line)
|
|
72
|
-
|
|
73
|
-
| Target | Resolver | Wired in |
|
|
74
|
-
|--------|----------|----------|
|
|
75
|
-
| Web pages | `fetch('/assets/icons/<style>/<name>.svg')` from the site's own origin — `omega dev` emits the set at boot too, so runtime icons resolve in dev | `runtime/icons.js`, started by `runtime/boot.js` `initialize()` |
|
|
76
|
-
| Desktop renderers | IPC `desktop:fontawesome:get` → main's icon server (fs, works packaged/offline) | `renderer.js` `_wireFontAwesome` |
|
|
77
|
-
| Extension pages | `fetch(chrome.runtime.getURL('assets/icons/…'))` — the packaged set (gulp `fontawesome` task emits it to `dist/assets/icons` at every brand build), fully offline | `src/lib/icons.js` (self-starting side-effect import) in popup/options/sidepanel/page |
|
|
78
|
-
| Extension content scripts | NOT auto-wired on purpose — watching a HOST page's DOM would collide with sites using FA themselves. Injected UI imports `createIconRenderer` and `scan()`s its own container; `assets/icons/*` is in `web_accessible_resources` for exactly this | manual, per injected surface |
|
|
79
|
-
|
|
80
|
-
**Build-time inlining is WEB's** — it is the only target that renders HTML at
|
|
81
|
-
build. Desktop and extension pages ship their markup as authored and let the
|
|
82
|
-
shared watcher fill it on first paint, out of the same emitted set.
|
|
83
|
-
|
|
84
|
-
Web's `src/language-flags.js` additionally writes language-named copies into
|
|
85
|
-
their own namespace (`assets/icons/flags/lang/en.svg` = the us flag — language
|
|
86
|
-
and country codes collide, so `ar` is Arabic there and Argentina one level up),
|
|
87
|
-
so the client-side footer language switcher fetches a flag by the row's own
|
|
88
|
-
hreflang code with no map in the browser; a language the set has no flag for
|
|
89
|
-
drops its `<img>` rather than showing a broken glyph.
|
|
90
|
-
|
|
91
|
-
## Missing icons
|
|
92
|
-
|
|
93
|
-
A name the icon set has no file for leaves the element **empty** — a missing
|
|
94
|
-
icon is a content problem, never a crash and never a wrong-style fallback —
|
|
95
|
-
and says so, loudly, where a human will see it:
|
|
96
|
-
|
|
97
|
-
- **At build**, web's pass marks the element `data-omega-icon-missing="<style>/<name>"`
|
|
98
|
-
and warns once per name. The dev-only browser audit
|
|
99
|
-
(`core/js/core/dev-icon-audit.js`) turns each marker into a console error.
|
|
100
|
-
- **At runtime**, a 404 is a `console.error` naming the icon **in development**
|
|
101
|
-
and a silent skip in production, where a missing icon must never be worse
|
|
102
|
-
than a missing icon.
|
|
103
|
-
|
|
104
|
-
## Supplying Font Awesome Pro (brand-owned, never redistributed)
|
|
105
|
-
|
|
106
|
-
Pro is NO omega package's dependency — publishing a package that vendors Pro
|
|
107
|
-
SVGs violates the FA license. A brand that owns Pro brings its own copy; every
|
|
108
|
-
surface picks it up automatically. Two routes:
|
|
109
|
-
|
|
110
|
-
1. **npm** — authenticate the `@fortawesome` scope with your FA token
|
|
111
|
-
(fontawesome.com → Account → API Tokens), machine-global, never
|
|
112
|
-
committed:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
npm config set "@fortawesome:registry" "https://npm.fontawesome.com/"
|
|
116
|
-
npm config set "//npm.fontawesome.com/:_authToken" "YOUR-TOKEN"
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Then install `@fortawesome/fontawesome-pro` (monorepo root for dev; a
|
|
120
|
-
brand desktop app declares it as a **prod dependency** so it ships in
|
|
121
|
-
the packaged asar).
|
|
122
|
-
|
|
123
|
-
2. **Download dir (no token)** — set `OMEGA_FONTAWESOME_ROOT` to a dir
|
|
124
|
-
containing `svgs/` (+ optionally `metadata/`): a fontawesome.com "Pro
|
|
125
|
-
for Web" download, or any style-shaped SVG tree. Lives in the brand
|
|
126
|
-
`.env` (the config env cascade delivers it to manager runs, web
|
|
127
|
-
builds, and desktop dev) or the shell profile for machine-global.
|
|
128
|
-
|
|
129
|
-
Both can coexist — the chain is `[env dir, pro npm, free]`; missing files
|
|
130
|
-
fall through per icon, so a solid-only download plus the npm package plus
|
|
131
|
-
free compose seamlessly.
|
|
132
|
-
|
|
133
|
-
## Styles
|
|
134
|
-
|
|
135
|
-
Free floor: `solid` (default), `regular`, `brands`. Pro adds `light`,
|
|
136
|
-
`thin`, and the `duotone` / `sharp` / `sharp-duotone` families, which
|
|
137
|
-
compose with weights exactly like FA markup: `fa-sharp fa-light fa-play`
|
|
138
|
-
→ `sharp-light/play` (bare `fa-duotone` → the `duotone` dir =
|
|
139
|
-
duotone-solid). Style validation is by path-safe shape, not a whitelist —
|
|
140
|
-
future FA families work with zero framework changes.
|
|
141
|
-
|
|
142
|
-
A name the requested style has no file for falls back to `brands/`, so
|
|
143
|
-
`<i class="fa-solid fa-github">` resolves without the author knowing which
|
|
144
|
-
side of the set the mark lives on.
|
|
145
|
-
|
|
146
|
-
## The sheet (one home, every surface)
|
|
147
|
-
|
|
148
|
-
Icon PRESENTATION is one sheet:
|
|
149
|
-
`packages/web/core/css/core/_custom-font-awesome.scss`. It owns the box, the
|
|
150
|
-
size scale and the animation utilities, and it is deliberately self-contained
|
|
151
|
-
(plain css, its own `$fa-sizes` map, its own keyframes) so the other targets
|
|
152
|
-
take it verbatim instead of hand-writing a second copy that drifts.
|
|
153
|
-
|
|
154
|
-
Desktop and extension VENDOR it at prepare through their package.json
|
|
155
|
-
`omega.vendorAssets`, the same channel that carries the `--omega-*` tokens and
|
|
156
|
-
the shell mechanics: it lands at `dist/assets/css/core/_fontawesome.scss` and
|
|
157
|
-
the `omega-desktop` / `omega-extension` entries `@use` it. Nothing to import in
|
|
158
|
-
a consumer project, and no second sheet to keep in sync (#183).
|
|
159
|
-
|
|
160
|
-
## Animation
|
|
161
|
-
|
|
162
|
-
`.fa-spin` turns an icon one full rotation a second, linear, forever;
|
|
163
|
-
`.fa-bounce` hops it; `.fa-beat` swells it. All three are Font Awesome's own
|
|
164
|
-
class names, so the muscle memory carries over, and each rides either half of
|
|
165
|
-
the mechanism:
|
|
166
|
-
|
|
167
|
-
```html
|
|
168
|
-
<i class="fa-solid fa-spinner fa-spin"></i>
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
The keyframes are ours, not FA's: FA's own are built on a stack of
|
|
172
|
-
custom-property knobs the sheet does not ship, so each utility gets a minimal
|
|
173
|
-
implementation under FA's name.
|
|
174
|
-
|
|
175
|
-
Under `prefers-reduced-motion: reduce` every one of them is dropped and the
|
|
176
|
-
icon parks, the same deal every looping effect in the motion library
|
|
177
|
-
makes.
|
|
178
|
-
|
|
179
|
-
The rendered icon `<i>` is a square `1em` box with the glyph centered in
|
|
180
|
-
it, so a spin (or any other transform) turns about the glyph's own center
|
|
181
|
-
instead of a point down on the text baseline. Nothing to hand-fix, and no
|
|
182
|
-
class to remember: the box keys on the `data-omega-fa` stamp the renderers
|
|
183
|
-
leave themselves.
|
|
184
|
-
|
|
185
|
-
The 12 size classes (`fa-2xs` … `fa-6xl`) are the sheet's `$fa-sizes` map, and
|
|
186
|
-
`icon-core`'s modifier list carries the same roster, so a size class never
|
|
187
|
-
reads as an icon name.
|
|
188
|
-
|
|
189
|
-
## The rule for packaged markup
|
|
190
|
-
|
|
191
|
-
**Framework markup names FREE icons only** (#3). Pro is brand-owned and never a
|
|
192
|
-
package dependency, so a Pro-only name in a packaged theme, core layout, or
|
|
193
|
-
default page renders nothing in every consumer that does not own Pro — and the
|
|
194
|
-
consumer cannot fix it (the chain has no consumer-local icon dir). Stock chrome
|
|
195
|
-
must build with zero missing-icon markers; a brand's OWN pages are free to use
|
|
196
|
-
whatever its set resolves.
|
|
197
|
-
|
|
198
|
-
## Testing
|
|
199
|
-
|
|
200
|
-
- `packages/client/test/icon-core.test.js` — parsing/validation, the flags
|
|
201
|
-
namespace, plus the 12-size roster the modifier list must carry.
|
|
202
|
-
- `packages/client/test/icon-renderer.test.js` — the shared watcher on a real
|
|
203
|
-
class list: insert, re-class, clear, flags.
|
|
204
|
-
- `packages/devkit/test/{fontawesome-roots,icons}.test.js` — chain resolution,
|
|
205
|
-
the build-side loader, and the emission merge.
|
|
206
|
-
- `packages/web/test/icons-inline.test.js` — the build pass (native markup,
|
|
207
|
-
modifiers, brands fallback, aliases, flags, misses), one real mini-site
|
|
208
|
-
build, and the grep guard that the retired mechanisms are gone.
|
|
209
|
-
- `packages/web/test/icons-runtime.test.js` — the transport and the watcher:
|
|
210
|
-
a JS-created icon upgrading, a flag upgrading, dev loudness vs production
|
|
211
|
-
silence, and zero fetches for what the build already inlined.
|
|
212
|
-
- `packages/desktop/src/test/suites/{main,renderer}/fontawesome.test.js` —
|
|
213
|
-
the real-DOM proof of the SHARED renderer + main's root chain and IPC
|
|
214
|
-
sanitization.
|
|
215
|
-
- `packages/web/test/icons.test.js` — the compiled icon sheet: the square
|
|
216
|
-
box, `.fa-spin` / `.fa-bounce` / `.fa-beat`, their reduced-motion branch,
|
|
217
|
-
and the size roster derived from `$fa-sizes`.
|
|
218
|
-
- `packages/{desktop,extension}/src/test/suites/build/icon-sheet.test.js`:
|
|
219
|
-
the vendored copy lands, and the entry compiles the box + the park.
|
package/docs/shared/local-dev.md
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
# Local dev loop (master plan §8)
|
|
2
|
-
|
|
3
|
-
How to work on the frameworks and see edits live in consumers — one repo, one window, one command. The mechanics live in **`@omega.js/devkit/local`** (`packages/devkit/src/local.js`), the single home for monorepo resolution, brand/app detection, file:-linking, and the watch lock.
|
|
4
|
-
|
|
5
|
-
## Root watch: `npm start`
|
|
6
|
-
|
|
7
|
-
`npm start` at the monorepo root runs `scripts/watch-all.js`, which starts every package's `prepare:watch` (src→dist rebuild-on-change) **concurrently**, with per-package output prefixes:
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
Watching 4 packages (src→dist): backend, client, desktop, extension
|
|
11
|
-
[client ] [02:25:05] 'prepare-package': Ready for changes!
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
- **Discovery, not hardcoding**: any `packages/*` with a `prepare:watch` script is watched. Packages without one (`account`, `analytics`, `config`, `devkit`, `mcp-router`, `monitoring`, `template-kit`) serve `src/` directly — file:-linked consumers see those edits live with no watch at all.
|
|
15
|
-
- **Vendor propagation**: a per-package `prepare:watch` only sees its OWN src, but the vendorable shared packages (`devkit`, `config`, `account`, `template-kit`, `analytics`, `monitoring` — `VENDORABLE_PACKAGES` in `packages/devkit/tools/vendor.js` is the list) also live as copies inside every framework's `dist/vendor/*`. The orchestrator watches those srcs too (`startVendorPropagation`) and re-runs `npm run prepare` in every watchable package on change — debounced, coalescing, one failure never stops the pass. Without it, a devkit edit strands dist-running frameworks on stale vendored code until a manual rebuild (the cp184 `http://localhost:5002` no-redirect bug). Running processes still need a restart to load the fresh dist, same as any src edit.
|
|
16
|
-
- **Single-instance**: the orchestrator takes `.omega/dev-watch.lock` (pid inside). A second `npm start` — or an `omega dev --local` session — sees the live lock and exits cleanly instead of double-watching. Stale locks (dead pid) are swept automatically.
|
|
17
|
-
- **Shutdown**: SIGINT/SIGTERM kills every watch child and releases the lock. A watch child dying on its own is announced loudly; the others stay up.
|
|
18
|
-
- **Its log**: the whole watcher — its own lines and every child watch's prefixed output — tees to `.temp/logs/watch-all.log`, truncated per launch (attached AFTER the lock, so a losing second instance never wipes the live one's log). "Is it alive, did it respawn, what did it rebuild" is a `grep`, never a restart ([logging.md](logging.md)).
|
|
19
|
-
|
|
20
|
-
## Brand-root one command: `omega dev` (web + backend together)
|
|
21
|
-
|
|
22
|
-
At a **brand root** (the dir with `config/omega.json5`), every framework's `omega` bin dispatches to `@omega.js/manager` — whose `dev` command boots the whole local stack at once (`npm start` is the packaged form, `npm run dev` its alias — the manage cycle is `npm run manage`, i.e. `omega manage`; a bare `omega` prints help and runs nothing, [#229](https://github.com/Omega-JS-Stack/omega/issues/229)):
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
omega dev # website dev server + backend FULL emulator suite
|
|
26
|
-
omega dev --target=web # one leg
|
|
27
|
-
omega dev --target=web,backend # explicit set
|
|
28
|
-
omega dev --all # every target with a dev leg (desktop/extension opt-in)
|
|
29
|
-
omega dev --full # boot on the WHOLE manage walk, not just the boot lane
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
- **Boot opens with ONE freshness sweep, before anything spawns** ([#340](https://github.com/Omega-JS-Stack/omega/issues/340)): `omega dev` checks every selected lane's framework closure itself (devkit's `freshnessSweep`, deps-first, one check per package dir), so a heal — `npm run prepare`, which PURGES dist before recopying — happens once, with no sibling lane mid-`require('../dist/…')` through it. Each leg still checks itself at CLI boot; swept first, that check is a no-op. A stale MONOREPO-linked dist stops the boot here instead of taking a leg down later.
|
|
33
|
-
- **Boot opens with a manage cycle** ([#44](https://github.com/Omega-JS-Stack/omega/issues/44)): before any leg spawns, `omega dev` reconciles the brand — its boot lane, below — so brand-level sources reach the targets instead of sitting stale (see the redistribution contract below). Errors in that cycle stop the boot loudly; nothing serves on top of a broken brand.
|
|
34
|
-
- **The boot walks the LOCAL lane, in about a second** ([#228](https://github.com/Omega-JS-Stack/omega/issues/228)): `workspace` (structure + config health), `assets` (derived logo/icon variants) and `disperse` (signing artifacts) — the file work the dev legs actually consume. It is the DELIVERY lane, not a boot-only one ([#678](https://github.com/Omega-JS-Stack/omega/issues/678)): a brand-root `omega deploy` runs the same `BOOT_SERVICES` walk before it fans out, so brand inputs reach the targets on both triggers from one list. Cloud reconciliation and the rebuild lane (cloud, campaigns, payment, update, account, …) cost a minute-plus and reach no dev leg, so they belong to `npm run manage` — or `omega dev --full`, which boots on the whole walk. A service is manage-lane unless `BOOT_SERVICES` names it, so nothing new can slow a boot by accident. The boot still prints the preflight walkthrough for the FULL setup (it is local and instant), then one line pointing at `npm run manage`.
|
|
35
|
-
- **The boot cycle never waits on a human** ([#228](https://github.com/Omega-JS-Stack/omega/issues/228)): it runs non-interactive, so any step that needs a person — a console confirm, a consent flow, a secret paste — steps aside into the run summary's ⚑ pending list, each item naming its `npm run manage -- --service=<name>` rerun. The dev legs boot regardless of what is pending; only real errors (broken config, an unloadable brand) stop the boot. A never-setup brand and a fully setup one behave identically here — the difference shows up as a longer pending list, not as a stalled terminal. A dead Google grant is pending too, not an error: the service warns into the same list and the walk carries on. One consequence on a `--full` boot: its web build uses the committed translation cache only (`--cached-only`, the same determinism law the pipeline and deploys follow), so new strings translate on the next interactive manage or build.
|
|
36
|
-
- **Default set = `web` + `backend`** — the local web loop. GUI/watcher targets (desktop opens an Electron window; extension runs a build watcher) never boot unless named via `--target=`/`--all` ([#780](https://github.com/Omega-JS-Stack/omega/issues/780)). The default also adapts: a web-only brand boots just web, no warning.
|
|
37
|
-
- **Legs**: web → the target's `npm start` (`omega dev`, :4000); backend → `npm run emulator` (auth/firestore/functions/database/hosting + seeded personas). Backend boots first so its port map is published before web reads it, and the order is optional either way ([#346](https://github.com/Omega-JS-Stack/omega/issues/346)): the web dev server rewrites each page's port map into the response as it serves it, so a backend that publishes later reaches the browser on the next request instead of never.
|
|
38
|
-
- **Sibling web ports offset deterministically** ([#886](https://github.com/Omega-JS-Stack/omega/issues/886)): each web target wants the classic base + its position among the brand's `web`-type targets (targets/web → :4000, targets/admin → :4001), so they dev side-by-side from their own target dirs; the N7 allocator still bumps if the offset port happens to be taken, and pins (`--port` / config `ports.website`) win verbatim. A brand with one web target stays on :4000 exactly as before.
|
|
39
|
-
- **Per-target Node**: each leg spawns under its target's own `.nvmrc` major (web and backend pin different ones).
|
|
40
|
-
- **A leg targets the `https` port, never `hosting`** ([#795](https://github.com/Omega-JS-Stack/omega/issues/795)): the ports file publishes both, and only `https` is the public origin under the local certificate; `hosting` is the internal plain-http port the mkcert proxy forwards to, so a consumer process aimed there is talking to the proxy's back door instead of the address every other surface uses. Every leg `omega dev` spawns carries `NODE_EXTRA_CA_CERTS=<mkcert -CAROOT>/rootCA.pem` (a shell-set value wins verbatim; a host without mkcert gets no key at all), so a brand's own Node process VERIFIES that certificate rather than dying on it. A leg that never speaks TLS ignores the variable, and since this is verification and not a bypass, Node prints no warning. The emulator's and `omega serve`'s own children take the same variable in place of the old `NODE_TLS_REJECT_UNAUTHORIZED=0`, whose boot warning is gone with it; that bypass survives in exactly one place, a host whose mkcert root vanished under certificates already on disk.
|
|
41
|
-
- **One terminal covers FRAMEWORK edits too** ([#587](https://github.com/Omega-JS-Stack/omega/issues/587)): when the brand's `@omega.js/*` deps RESOLVE into a monorepo checkout, the boot starts that monorepo's watch as a session child: no flag, no second terminal, so editing a framework's `src/` rebuilds its `dist/` while the stack runs. Resolution answers the question, never the manifest (a `file:` spec can be stale, and workspace hoisting puts the copy at the brand root): `resolveLinkedMonorepo(brandRoot)`. Lock-aware: a watch already running (root `npm start`, or another brand's session) is REUSED, never doubled, session-scoped, so it dies with the stack, and its output rides the `[watch]` tag into `logs/dev.log`. A registry-installed brand has nothing to watch and says so in one line. It starts AFTER the freshness sweep on purpose: the sweep's verdict depends on whether a watch holds the lock ([#281](https://github.com/Omega-JS-Stack/omega/issues/281)/[#398](https://github.com/Omega-JS-Stack/omega/issues/398)), so starting one first would change the answer it just gave. A FRESH watch's initial prepare rewrites every package's `dist/` as it runs, so the boot WAITS for that pass to land ([#670](https://github.com/Omega-JS-Stack/omega/issues/670)): the helper's `ready` promise resolves once every watched package has printed its `Ready for changes!` line (or the moment the watch child exits without reporting, or after 120s with one warning naming the stragglers), and only then do the legs boot, so nothing loads a CLI out from under the rewrite. **An ALREADY-RUNNING watch gates the same way** ([#622](https://github.com/Omega-JS-Stack/omega/issues/622)): the branch a linked brand normally takes, since the root `npm start` is usually already up: with no child stdout to read, the boot tails that watch's own `.temp/logs/watch-all.log` (believed only when its pid header IS the lock owner's) through the same tracker, and also waits out a vendor-propagation pass holding `.omega/vendor-propagation.lock`. A settled watch answers off the file with no wait at all; a watch that dies mid-wait settles it at once. Same helper as the web target's `--local` prelude: one implementation, two callers.
|
|
42
|
-
- **Every seeded persona is a FULL account** ([#327](https://github.com/Omega-JS-Stack/omega/issues/327)): identical in shape AND in substance to a real user — a name, a place, a phone, a company, a birthday, and the device and IP it signed up from — so a persona renders like a customer on every surface that shows a person. The profile is DERIVED from the persona key (`packages/backend/src/test/test-accounts.js` `seededProfile`), so it is the same on every boot and a new persona is born complete; anything a persona names for itself wins. Paid personas carry the provider record a real purchase leaves (provider, resource, order, term start, the event that last wrote it), which is what payment-gated surfaces — the billing card's save offer — read before they show anything. Established personas also carry the two lists an account accumulates ([#343](https://github.com/Omega-JS-Stack/omega/issues/343)): the REFERRALS an affiliate link earned (`{ uid, timestamp }`, exactly as a signup appends them, and naming other personas — so whether a referral converted is the referred account's own subscription state) — carried by the DEDICATED `referrer` persona and nothing else ([#363](https://github.com/Omega-JS-Stack/omega/issues/363)), because a persona demonstrates exactly its own scenario — and the ACTIVE SESSIONS of the two or three devices they are signed in on (Realtime Database records under `sessions/app`, seeded on boot and restored by "Reset to seed", because they live outside the user doc). Demo-safe by construction: documentation-range IPs, fictional 555 numbers, invented companies.
|
|
43
|
-
- **The dev palette** (the DEV pull-tab the web framework renders in development): one-click sign-in as the seeded personas, including the four billing-journey accounts ([#215](https://github.com/Omega-JS-Stack/omega/issues/215)), and a "Reset to seed" button on any signed-in test account, a development-only backend route rebuilds the account from its canonical seed shape. The switcher's roster is the SEED's own ([#400](https://github.com/Omega-JS-Stack/omega/issues/400)): the backend labels each human-facing persona (`palette: '<Label>'` in `packages/backend/src/test/test-accounts.js`) and serves that list, in declaration order, at `GET /omega/test/roster`; the palette fetches it on build and keeps no copy, so a persona seeded today is switchable today and the machinery an automated suite drives never appears. The WHOLE seed is read, both halves ([#712](https://github.com/Omega-JS-Stack/omega/issues/712)): a project's own personas from its `test/_init.js` carry the same label and are offered after the framework's, so a consumer persona is switchable the moment it is seeded. The address the palette signs in on is the brand's HOST, and so is the one the seeder creates ([#708](https://github.com/Omega-JS-Stack/omega/issues/708)): ONE derivation, `@omega.js/config`'s `resolvedBrandHost` (a target's own `url`, else `brand.url`), because a brand answering support mail on the apex while the site sits on a subdomain used to seed accounts the palette could never sign in as. There is no fallback list: with no emulator up the dropdown holds its placeholder alone and the "Signed in as" line carries the reason on a second line, under whoever you are signed in as (that readout is composed, so an auth state settling after the failure lands beside it rather than on top of it). A page opened while the emulator is still booting does not stay stuck there ([#402](https://github.com/Omega-JS-Stack/omega/issues/402)): a spinner and a "Backend starting…" line sit at the top of the panel and the roster is fetched again every two seconds, forever, until it answers; the attempt that lands fills the dropdown, preselects the signed-in persona, and clears both the indicator and the explanation, so no reload is needed. A built-in **Storage** section ([#390](https://github.com/Omega-JS-Stack/omega/issues/390)) covers the client blob: a target dropdown ("All" first, then every top-level key, re-derived from the LIVE blob each time the panel opens) with Log and Clear buttons acting on the selected target: Log prints the parsed value into the console, Clear removes it, and nothing asks you to confirm. Source: `packages/web/core/js/core/dev-palette.js`. Pages contribute their own controls via `registerDevSection` (`core/js/core/dev-sections.js`, [#234](https://github.com/Omega-JS-Stack/omega/issues/234)): sections render on open and merge by id: the checkout page's controls (product, frequency, pre-delay, trial, card provider, reCAPTCHA, the decline toggle) live in the palette's Checkout section, and every one of them applies the same way: Apply & reload navigates with its param set, the decline arm included (`_dev_decline`, which stays armed until you apply it away); the page's old gear dropdown is gone.
|
|
44
|
-
- **The palette is the ONLY dev-testing surface** (Ian's ruling, [#342](https://github.com/Omega-JS-Stack/omega/issues/342), extending [#329](https://github.com/Omega-JS-Stack/omega/issues/329)): no client-side test personas as code objects, no helper hung on `window`, no dev switch you can only reach by already knowing its name — hidden functions get forgotten in six months. Alongside Checkout the panel now carries **Tools** (log opening tags, toggle theme), **Icons** (re-run the missing-icon audit), **Download** (open the onboarding walkthrough for a platform), **Extension** (fire a browser's install click) and **OAuth** (rehearse a returning provider redirect: returning user, new user, credential conflict). The account page carries NO control of its own: its referrals and sessions lists were the last client-side fixtures (`?_dev_prefill`), and the personas carry both now ([#343](https://github.com/Omega-JS-Stack/omega/issues/343)) — switching persona IS the affordance. Every one of them is a `@dev-only` block, so a production build ships neither the control nor what it unlocks. The rule outlives the sweep as a guard: `packages/web/test/dev-hooks-guard.test.js` fails on a new `_dev_*` hook or `window.<helper>` in client code, on a listed hook no palette section actually offers, and on any hook read outside a `@dev-only` block.
|
|
45
|
-
- **Google/OAuth signin uses the redirect flow here, same as production** ([#156](https://github.com/Omega-JS-Stack/omega/issues/156)): the auth emulator returns its credential through storage on the origin it is served from, so `omega dev` proxies the emulator under the SITE origin and the return leg survives the browser's storage partitioning. Details in [docs/web/index.md](../web/index.md).
|
|
46
|
-
- Output is line-prefixed per target (`[backend] …`, `[web] …`); one Ctrl-C stops everything; a leg dying alone is announced and its siblings stay up.
|
|
47
|
-
- **No production build runs under a live dev server** ([#617](https://github.com/Omega-JS-Stack/omega/issues/617)): `omega dev` and `omega build` write the SAME `dist/`, and a build landing under a running website dev server replaces the pages it keeps serving — production-stamped, `dev: null`, no re-render until a restart. So `omega build` in a website target REFUSES in one line while that target's dev run holds it, and so does anything that runs it (`omega test`'s project lane, `omega audit`, the manage cycle's rebuild lane, and `omega deploy`'s local/direct lanes — mid-session, scope manage: `npm run manage -- --service=<name>`). Stop the leg first. The dev run is known by the ports file it already publishes and retracts on shutdown, so there is no second lock — and a crash leftover (dead pid) is ignored, never a wedged build. Web only for now: the same guard reaches another framework the day the mechanism is shared, never as a fourth copy.
|
|
48
|
-
|
|
49
|
-
## What refreshes when — the redistribution contract
|
|
50
|
-
|
|
51
|
-
Brand-level sources are not read by the targets directly; most of them are **redistributed** by the manage cycle, and target watchers only watch their own dir. So:
|
|
52
|
-
|
|
53
|
-
| You edited | What moves it | When it lands |
|
|
54
|
-
|---|---|---|
|
|
55
|
-
| `assets/logo/*.svg`, `assets/templates/*.psd` | assets service → derived variants in the gitignored `.omega/assets/`, mtime-diffed (only stale outputs regenerate) | a manage cycle: restart `omega dev`, or run `npm run manage` in the brand |
|
|
56
|
-
| `.omega/assets/*` (the derived set) | the web build's static channel copies them into the site as `assets/images/brand/*` + `assets/images/favicon`, and mirrors the shipped `favicon.ico` to the site root so the browser's `/favicon.ico` probe resolves (`packages/web/src/static-assets.js`) | the web build's `static` phase — the dev server copies at BOOT, no watcher, so a restart picks them up |
|
|
57
|
-
| signing certs | disperse service → each target's gitignored certs dir | the delivery lane: a boot, a `npm run manage`, or a brand-root `omega deploy` |
|
|
58
|
-
| brand `.env` | nothing copies it — every verb composes its target's runtime env (the backend's staged `dist/.env`) from the cascade, filtered by the env schema ([#678](https://github.com/Omega-JS-Stack/omega/issues/678)) | the next build; **the dev watchers reload the env cascade on change** — the backend restages `dist/.env`, and the web + extension dev lanes reload it into `process.env` for the next rebuild ([#681](https://github.com/Omega-JS-Stack/omega/issues/681)). A **NEW** key and an **EDITED** value both land on that rebuild, and a key dropped from the file is dropped from the process — the reload drops what a file layer owns and re-reads the cascade ([#724](https://github.com/Omega-JS-Stack/omega/issues/724)); a **shell**-set value still wins over every file. One limit worth knowing: **desktop is pre-wired only** (its dev pipeline builds once, and the electron child snapshots env at spawn, so nothing there consumes a reload yet) |
|
|
59
|
-
| `config/omega.json5` (brand or local) | nothing — targets read it directly, there is no mirror to disperse | the reading process's own reload (a running backend restages on it; a dev-server restart is always enough) |
|
|
60
|
-
| a target's own `src/` | that target's watcher | live |
|
|
61
|
-
|
|
62
|
-
The short version: **anything under the brand root that isn't inside a target needs the manage cycle**, and `omega dev` now runs one at boot ([#44](https://github.com/Omega-JS-Stack/omega/issues/44)) — so the loop for a brandmark edit is *edit the svg → restart `omega dev`*. Mid-session, `npm run manage` (or `npx omega manage --service=assets`) does the same without stopping the stack, then restart the web leg to pick up the copies.
|
|
63
|
-
|
|
64
|
-
## One-command consumer sessions: `omega dev --local`
|
|
65
|
-
|
|
66
|
-
In a brand's website target, `omega dev --local` runs the full local-mode prelude before the normal dev server:
|
|
67
|
-
|
|
68
|
-
1. **Resolve the monorepo** — `OMEGA_MONOREPO` env override → self-location (works whenever the running framework is linked from the monorepo, or the consumer lives inside it) → `~/Developer/Repositories/Omega/omega`.
|
|
69
|
-
2. **Find the brand root** — walk up from cwd to the first directory with both a `package.json` and `targets/<name>/package.json` (the Omega monorepo itself never counts); standalone projects resolve to themselves.
|
|
70
|
-
3. **Link brand-wide** — for every project (brand root + `targets/*`), every `@omega.js/*` dependency in the ONE authored manifest (src/dist pillar — a backend's `functions/` is staged output and carries no authored manifest) is flipped to a `file:` spec and installed. **Idempotent**: deps already resolving to the monorepo copy are skipped (resolution walks up node_modules, so npm-workspace hoisting is handled), and an already-correct committed `file:` spec is never rewritten. **Transactional**: an install failure restores every flipped manifest to its pre-link spec.
|
|
71
|
-
4. **Start the monorepo watch** — spawned as a session-scoped child (dies with the dev server; the lock prevents doubles). Watch output streams into the dev log under a `[watch]` prefix. Same helper the brand root uses (below): `startMonorepoWatch()` owns the session scoping, each caller keeps its own Ctrl-C policy.
|
|
72
|
-
|
|
73
|
-
Then the standard `omega dev` loop runs. Result: edit any framework's `src/` and the consumer picks it up live — no N windows, no N × `mgr i local`.
|
|
74
|
-
|
|
75
|
-
**Upstream-first (Ian 2026-07-27).** The live link is not just a convenience — it is how framework holes get found and fixed. Building a real consumer app regularly exposes gaps in OMEGA; when a defect or missing piece would hit EVERY consumer (a broken core style, a wrong default, a missing option), fix it in the framework right here through the link, not in the consumer project — a consumer-side patch has to be rediscovered and repeated in the next project. The test: would the next consumer need the same change? Then it belongs upstream. Brand-specific looks, content, and one-off behavior stay in the brand. The same rule ships to brand sessions in the brand guide (`docs/manager/brand.md`).
|
|
76
|
-
|
|
77
|
-
**Permission first (Ian 2026-07-27).** Upstream-first says WHERE a fix belongs, never that a consumer session may make it unprompted. A session working in a consumer project that finds a framework-level hole SURFACES the proposed framework change — what is broken, what it would change, why every consumer needs it — and WAITS for Ian's go before touching the monorepo (or files it as an issue when Ian is not in the loop). No consumer session edits the framework silently as a side effect of its own task.
|
|
78
|
-
|
|
79
|
-
## Per-target linking: `mgr i local`
|
|
80
|
-
|
|
81
|
-
**Linking is one-time and durable (Ian 2026-07-28) — never re-run it per change or per session.** The `file:` specs resolve to real symlinks into the monorepo, so once a brand is linked, every framework edit is live the moment its `dist/` rebuilds (the monorepo watch or any prepare). The web dev server watches the resolved `@omega.js/client` dist and rebuilds the site bundle on ANY change there — including a prepare's wholesale delete-and-recreate, which the watch survives by re-arming onto the new directory ([#378](https://github.com/Omega-JS-Stack/omega/issues/378)) — so client changes reach a RUNNING site with no restart. Framework PROCESS code (the dev server itself, a backend leg) still loads at boot: those edits need the normal stack restart. Re-running `i local` is a HEAL for a link something overwrote (a registry install, a fresh clone) — reruns all-skip and cost nothing, but they are never part of the edit loop.
|
|
82
|
-
|
|
83
|
-
Unchanged contract for consumers, now monorepo-backed: `mgr i local` (web, desktop, extension — web gained its install command in cp194) and `mgr i local` / `mgr install --local` (backend) call the same `linkLocalPackages()`. Backend links its target-root manifest like every other target (`functions/` is staged output; the CLI normalizes a `functions/` cwd up to the target root). `mgr i live/prod` still installs from the registry and is untouched.
|
|
84
|
-
|
|
85
|
-
**Linking is brand-tree-wide by construction (cp194):** npm resolves the WHOLE workspace tree on any install anchored in a brand monorepo, so linking one target while a sibling still carries an unpublished registry spec (`@omega.js/backend: *`) 404s before anything links — only reachable in a brand OUTSIDE the omega monorepo, the real consumer topology. `linkLocalPackages()` therefore flips every target's `@omega.js/*` specs to `file:` first (dev/prod placement preserved; specs computed from REAL paths so symlinked/aliased dirs can't dangle), then runs ONE `npm install` for the tree. One call from any target links the whole brand; reruns all-skip.
|
|
86
|
-
|
|
87
|
-
**Both flips regenerate the brand's OWN lockfile** ([#938](https://github.com/Omega-JS-Stack/omega/issues/938)): after the tree install, `omega i local` and `omega i live` run devkit's one lock helper, `regenerateLockfile({ root })` (`npm install --package-lock-only --ignore-scripts --no-audit --no-fund --prefix <brand root>`, the same helper the deploy's pack step calls). The reason is nesting: every in-repo brand is itself a workspace of the omega monorepo (`brands/*`), so a plain `npm install` in the brand climbs to the monorepo root and writes THAT lockfile, leaving the brand's own describing whatever it described before; `--prefix` makes npm treat the brand as the install root. Before it runs, the helper first prunes every lock entry for a folder no longer on disk (a renamed target's old workspace entry, everything nested under it, and every link pointing into it), because npm keeps honoring what such an entry declares; then it drops every `@omega.js/*` lock entry the deploy's lockfile gate would refuse (a `link: true` entry and its link target, a path-resolved entry, a version outside the spec), because npm alone KEEPS a locked link whose checkout already sits at the published version, and every other pin stays as it was. `omega i live` also heals when there is nothing to flip: specs already on registry versions under a lock that still disagrees get the lock regenerated alone, since that verb is the fix the gate names ([deploys.md](deploys.md)). A failed install restores every flipped manifest; a failed regeneration runs after a successful install, so it leaves the manifests and the installed tree as they agree and only puts the lock back byte for byte, failing loudly as a lock regeneration.
|
|
88
|
-
|
|
89
|
-
**The brand root itself is part of the tree (cp195):** onboard scaffolds `@omega.js/manager` into the brand root's devDependencies — the omega-bin dispatcher resolves brand-level verbs (`omega dev`, manage, the scaffolded `start`/`manage` scripts) FROM the brand root, and without the declaration nothing installs the manager outside the monorepo (inside it, workspace hoisting masked the gap). `linkLocalPackages()` links it like any target dep (`discoverTargets` already includes the brand root).
|
|
90
|
-
|
|
91
|
-
**Outside brands may COMMIT relative `file:` specs — the real brand did in the local era (cp229):** `../omega-omega` declared every `@omega.js/*` dep as `file:../../../omega/packages/<name>` (brand root: `file:../omega/packages/manager`), the same pattern the in-repo brands still use at their own depth. Clone the two repos side by side and a plain `npm install` links the whole tree with zero linker involvement; `linkLocalPackages()` all-skips because the specs already resolve into the monorepo. Since the first publish (0.50.0) the real brand pins the exact registry version instead; the in-repo brands stay on `file:` specs by ruling (2026-09-10).
|
|
92
|
-
|
|
93
|
-
**A linked brand reaches CI as TARBALLS, not as a checkout** ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)): a runner has nothing at the path `file:` specs name, so `omega deploy` packs every linked package into the brand's `omega_modules/` and pushes that snapshot to the brand repo, and the runner installs it with a plain `npm ci`. Nothing is asked of the consumer or of the CI tool, and nothing framework-aware runs on the box. The lane, the pack step and the two hops: [deploys.md](deploys.md).
|
|
94
|
-
|
|
95
|
-
**`npx omega` is only ever run inside an installed target** ([#881](https://github.com/Omega-JS-Stack/omega/issues/881)): with no local bin npx fetches a stranger's package named `omega` from the registry, and the plugin's npx hook refuses that in agent shells.
|
|
96
|
-
|
|
97
|
-
## Vendoring vs runtime deps (what ships where)
|
|
98
|
-
|
|
99
|
-
Two different mechanisms keep consumers working:
|
|
100
|
-
|
|
101
|
-
| Kind | Packages | Mechanism |
|
|
102
|
-
|------|----------|-----------|
|
|
103
|
-
| Private shared internals | `devkit`, `config`, `account`, `template-kit`, `analytics`, `monitoring` (devDependencies of the frameworks) | Vendored into `dist/vendor/<pkg>` at prepare time by `@omega.js/devkit/vendor`; requires rewritten to relative paths. Never published. |
|
|
104
|
-
| Published runtime deps | `@omega.js/client` (dependency of web + backend + desktop + extension), `@omega.js/backend` and `@omega.js/mcp-router` (dependencies of manager) | Normal npm dependency — **never vendored** (a vendored copy would pin a stale snapshot and duplicate the shared client singleton). Requires stay as package requires. |
|
|
105
|
-
|
|
106
|
-
The vendor tool derives the split from the host's package.json: anything in `dependencies`/`peerDependencies`/`optionalDependencies` is published-runtime and skipped; devDependency workspace packages get vendored. All six DIST-BUILDING publishables (backend, client, desktop, extension, manager, web) run the vendor after-hook; the seventh, `@omega.js/mcp-router`, ships its `src/` as-is and vendors nothing. Two mirrored gates prove self-containment: CI's pack-smoke and the local `npm run release:check` — both pack, scratch-install with tarball `overrides` for the published runtime deps, resolve, and grep the whole shipped tree for raw private `@omega.js/*` refs — the alternation is DERIVED from `VENDORABLE_PACKAGES`, never written out ([#713](https://github.com/Omega-JS-Stack/omega/issues/713): the hand-written four sat two names behind the list, so a raw `analytics`/`monitoring` require could ship undetected). The publish runbook lives in [docs/shared/publishing.md](publishing.md).
|
|
107
|
-
|
|
108
|
-
## API surface (`@omega.js/devkit/local`)
|
|
109
|
-
|
|
110
|
-
| Export | Purpose |
|
|
111
|
-
|--------|---------|
|
|
112
|
-
| `resolveMonorepoRoot()` | env → self-location walk-up → conventional path; throws with guidance if none |
|
|
113
|
-
| `findBrandRoot(dir)` / `discoverTargets(root)` | brand-root walk-up / brand root + `targets/*` list |
|
|
114
|
-
| `frameworkPackagesOf(targetDir)` | `@omega.js/*` deps incl. `functions/package.json`, with dev/prod placement + owning dir |
|
|
115
|
-
| `linkLocalPackages({ dir, monorepoRoot, logger, dryRun })` | idempotent brand-tree file:-link (spec flip + one install + the brand's own lockfile regenerated, transactional: a failed install restores every manifest); returns `[{ name, dir, target, action: link\|skip\|missing }]` across the tree |
|
|
116
|
-
| `restoreRegistrySpecs({ dir, logger, dryRun, range })` | the publish-day INVERSE (`omega i live` in every framework): every `file:` spec flips to the EXACT `<linked version>` (read from the file: target, so no monorepo needed; no caret: the family is lockstep, [#794](https://github.com/Omega-JS-Stack/omega/issues/794)) or the explicit `range`, used verbatim; then one registry install and the brand's own lockfile regenerated (alone, when nothing flipped but the lock disagrees), same transactional restore on failure |
|
|
117
|
-
| `resolveLinkedMonorepo(brandRoot)` | the monorepo a brand's `@omega.js/*` deps actually RESOLVE into, or null for a registry install |
|
|
118
|
-
| `startMonorepoWatch({ monorepoRoot, logger })` | lock-aware, SESSION-SCOPED spawn of the root watch (dies with the caller); `{ alreadyRunning, pid, child, ready }` — `ready` resolves with `'ready'`, `'exit'` or `'timeout'` on BOTH branches (#670, #622) |
|
|
119
|
-
| `awaitRunningWatchReady({ monorepoRoot, pid, logger })` | the same verdict for a watch this process did not start: its tee'd log + the propagation marker, 120s bound |
|
|
120
|
-
| `vendorPropagationActive(monorepoRoot)` | is a propagation pass purging framework dists right now (stale marker = no) |
|
|
121
|
-
| `createWatchReadyTracker()` | the pure line parser behind `ready`: `push(line)`, `isReady()`, `pending()` |
|
|
122
|
-
| `startVendorPropagation({ packagesDir, packages, dependents, runPrepare?, log?, debounceMs? })` | watch vendorable srcs → re-prepare dependents (debounced, coalescing); `{ watched, poke, close }` — `poke` is the fs-free test seam; a running pass holds `.omega/vendor-propagation.lock` |
|
|
123
|
-
| `acquireWatchLock` / `releaseWatchLock` / `readLiveWatchPid` | the `.omega/dev-watch.lock` single-instance protocol (owned by watch-all) |
|
|
124
|
-
|
|
125
|
-
## Freshness guard (cp247, hardened in [#195](https://github.com/Omega-JS-Stack/omega/issues/195))
|
|
126
|
-
|
|
127
|
-
Every framework CLI boot (web/backend/desktop/extension/manager — `freshnessBoot` in devkit `local.js`, wired in each `cli-run`) checks whether a locally-linked framework's `dist/` is stale. What happens next depends on who OWNS that dist ([#281](https://github.com/Omega-JS-Stack/omega/issues/281), narrowed by [#398](https://github.com/Omega-JS-Stack/omega/issues/398)): a link into this monorepo **whose watch is running** is read-only and the boot stops loudly (below), while every other local checkout — a plain link, and a monorepo link with the watch DOWN — heals in place, a loud auto `npm run prepare` in the package, after which the invocation RE-EXECS once (`OMEGA_FRESH_REEXEC` loop guard) so no verb ever runs stale framework code.
|
|
128
|
-
|
|
129
|
-
**Linked monorepo packages are READ-ONLY to consumer builds while their watch runs** ([#281](https://github.com/Omega-JS-Stack/omega/issues/281), Ian's call 2026-08-17). A consumer that resolves an `@omega.js/*` dependency to a link into this monorepo never prepares that package out from under the process that owns the build: no dist purge, no cache refetch (backend's prepare refetches `.cache/email/disposable-domains.json` over the network), not even a heal lock inside the package. That checkout is SHARED, by a fleet of agents, several brands and the monorepo's own processes, and a prepare deletes the `dist/` a sibling process is mid-`require` on, with nothing in `git status` to show for it. **Dist freshness is owned by the monorepo watch** (root `npm start`) whenever that watch exists.
|
|
130
|
-
|
|
131
|
-
**With the watch DOWN, the boot heals the link itself** ([#398](https://github.com/Omega-JS-Stack/omega/issues/398), Ian's call 2026-08-20). The stop is right only while something else owns the rebuild; with no watch running it is pure friction — the guard already knows the watch is down (the `.omega/dev-watch.lock` pid probe it warns from), so a stale linked dist takes the ordinary heal: one `npm run prepare` in place under the package's heal lock, `rebuilt` by `self`, then the boot's re-exec. The hazard #281 named is narrowed, not dismissed: the heal lock still serializes the CLIs booting on that package, and the largest concurrent writer is by definition not running. The once-per-process watch-down warning STAYS on this path — a stale linked dist is healed once at boot, and a framework edit made during the session still needs `npm start` for live rebuilds. **And a heal that FAILS is a loud stop, not a shrug**: the outcome is `heal-failed`, and the boot exits 1 naming the package, the staleness, the prepare's exit status and the `npm run prepare` to run by hand in that checkout. A plain checkout keeps the old warn-and-continue (its dist is the developer's own, and nothing shared hangs on this boot), but a monorepo link has no watch coming to land the build, so continuing would serve code nobody built — the silent fallback #281 forbids. `omega dev`'s hoisted sweep returns those entries as `healFailed` and stops the fan-out before any leg spawns, exactly as it does for `staleLinked`.
|
|
132
|
-
|
|
133
|
-
**The INSTALL path is guarded at the prepare script** ([#350](https://github.com/Omega-JS-Stack/omega/issues/350)). The gate is required by a RELATIVE path (`../devkit/tools/prepare-guard`) and borrows nothing from the rest of devkit, because a linked brand's install reifies its `file:` links, and so runs this prepare, before there is any `node_modules/@omega.js/devkit` link to resolve through; and a checkout with no root `node_modules` at all skips its prepare outright, there being nothing to build with ([#868](https://github.com/Omega-JS-Stack/omega/issues/868)). npm runs a `file:`-linked dependency's `prepare` INSIDE the linked checkout, so `omega i local`, `omega dev --local` and any plain `npm install` in a linked brand rebuilt a monorepo package in place through the one door a CLI guard cannot see: npm invokes prepare, devkit never gets a say. Every dist-building package's prepare IS devkit's gate: `node -e "require('../devkit/tools/prepare-guard').run()"` (`packages/devkit/tools/prepare-guard.js`), which runs the decision and then prepare-package itself. When npm's install root (`npm_config_local_prefix`, `INIT_CWD` as the fallback) lies OUTSIDE the monorepo that contains the package, the build is skipped (prepare-package is never even loaded, exit 0, so the consumer's install completes normally) with one stderr line naming the package, the install that reached in, and the watch that owns the dist. It fires for every TREE-TOUCHING command from outside: `install`, `ci`, `update`, `rebuild`, `dedupe`, the five npm reports in `npm_command` that re-run a linked package's prepare (npm 11.12.1), and for nothing else: the monorepo's own root install, workspace installs (`-w packages/web`), an install typed inside a package or an in-repo app, `npm run prepare`, and the `npm pack`/`npm publish` prepare lane all build exactly as before, so a published tarball still carries a freshly built dist. npm buffers lifecycle output, so the notice surfaces under `--foreground-scripts`.
|
|
134
|
-
|
|
135
|
-
**The gate keeps itself wired, even when the prepare fails** ([#870](https://github.com/Omega-JS-Stack/omega/issues/870)). prepare-package OWNS `scripts.prepare`: every full build rewrites the manifest with its own canonical one-liner, which would silently drop the gate on the monorepo's next prepare. Two things put it back, both through the one `ensureGuarded()`/`rewire()` pair in `prepare-guard.js`, so no lane carries a copy of the gated string:
|
|
136
|
-
|
|
137
|
-
1. **`rewire()` is the FIRST `preparePackage.hooks.after` command of every package**, ahead of the vendor hook, because the manifest write is the last thing prepare-package does before its hooks. It restores the gated line in prepare-package's own formatting (idempotent and narrow: it writes only when the manifest holds prepare-package's exact string, and never touches a prepare it does not own), and running first is what covers the `prepare:watch` lane, which never goes through `scripts.prepare` at all. Ordering used to be the other way, and a vendor hook that failed (twice: a devkit source string that looked like a package-internal require) stopped the chain before the rewire entry and left the manifest UNGUARDED.
|
|
138
|
-
2. **`run()` calls `ensureGuarded()` in a `finally`**, so the `prepare` lane ends gated whether the build succeeded, a hook exited nonzero or prepare-package itself threw, whatever the hook order says. The failure stays loud: the rejection is returned, so the script still exits nonzero.
|
|
139
|
-
|
|
140
|
-
**And the freshness heal refuses to leave a manifest its prepare stripped**: after its `npm run prepare` the heal calls that same `ensureGuarded()` on the package it prepared (the guard loaded from the devkit beside it), which covers the one case a `finally` cannot reach, a prepare killed outright. Pinned by devkit `test/prepare-guard.test.js` (27 tests: the decision table, the notice, real-npm/real-prepare-package fixture runs including the red baselines, a failing after hook that still ends gated, the heal on a stripped manifest, and a sweep asserting every dist-building `package.json` carries the gate with `rewire()` first).
|
|
141
|
-
|
|
142
|
-
The consequence is deliberate: with that watch running, a linked dist that is missing, unbuilt or stale FAILS the consumer build loudly, before the verb runs, naming the package, the evidence, the checkout it will not touch, and the watch already on the job:
|
|
143
|
-
|
|
144
|
-
```
|
|
145
|
-
omega: @omega.js/backend is linked into the omega monorepo and its dist is not built (dist/ is missing).
|
|
146
|
-
omega: linked packages are read-only to consumer builds, so this build will not rebuild /Users/ian/…/omega/packages/backend.
|
|
147
|
-
omega: the monorepo watch (`npm start` in /Users/ian/…/omega) is running but has not landed that build yet: give it a moment, then re-run this command.
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
There is no silent fallback and no staging copy: the build runs on a dist the watch built, or it stops and waits for it. `OMEGA_SKIP_FRESHNESS` remains the one opt-out, for harnesses that manage their own builds.
|
|
151
|
-
|
|
152
|
-
**The boot checks the host AND its `@omega.js/*` runtime deps** ([#198](https://github.com/Omega-JS-Stack/omega/issues/198)): each `cli-run` names its host package, and `freshnessCheckList` walks that host's `dependencies` (never devDependencies) through local checkouts — cycle-safe, each dep resolved from ITS depender so the walk follows the real node_modules chain — producing a DEPS-FIRST, host-last check order (web → `client`, web; manager → `client`, `backend`, manager). The host alone was never enough: web's bundle carries `@omega.js/client`'s dist verbatim, so a boot that healed web and stopped there still served the browser a stale client. A rebuild ANYWHERE in that list re-execs the invocation once; a dep that resolves into `node_modules` is classified `registry` and its own deps are not walked. **Every heal re-execs, whoever built it**: the outcome is `rebuilt` with a `by: self|watch|peer` discriminator (this boot's own prepare, the monorepo watch landing its copy inside the grace, or a peer CLI that won the heal lock) — a process that booted from the pre-heal dist is just as stale when someone else fixed it. Registry installs (realpath inside node_modules) and non-buildable dirs skip instantly; `OMEGA_SKIP_FRESHNESS` is the opt-out seam. A monorepo-linked entry that comes back stale under a live watch ends the walk where it stands: the boot prints the read-only stop above and exits 1, deps included, so a stale `client` never reaches its dependent's build. Pinned by devkit `test/local-freshness.test.js` (49 tests incl. linked-consumer integration proofs).
|
|
153
|
-
|
|
154
|
-
**Detection is per-file evidence, never a whole-tree mtime compare** (`distStaleReason`, 1–36ms per package). Every file under `src/` must exist at its mapped `dist/` path with an mtime at least as new — a MISSING dist file is stale whatever the timestamps say — and every dist file must have a src counterpart, or a leftover from a deleted source reads stale. The exceptions are the paths prepare itself generates: `dist/vendor/**` plus every destination the host declares in `omega.vendorAssets` (that is how desktop/extension carry a `dist/assets` tree the web package owns) — and nothing else, because vendor-docs writes the package ROOT `docs/` and prepare-package rewrites the ROOT `package.json`, so a `dist/docs/**` or `dist/package.json` is a leftover like any other. One subtree is exempt from the orphan question alone ([#352](https://github.com/Omega-JS-Stack/omega/issues/352)): `dist/test/fixtures/**`, where a package's own self-tests seed runtime state (backend writes its fixture project's `firestore.rules` and `service-account.json` there) that shadows no consumer require and outlives a crashed run — a file there WITH a src counterpart still answers the mtime question. The embedded `dist/vendor/<name>` copies are still compared against each private package's `src/` — they only refresh on a full prepare. The declared `omega.vendorAssets` destinations get the same positive question ([#199](https://github.com/Omega-JS-Stack/omega/issues/199)): each is compared against its source package's `from` tree (web's `core/`/`themes/` for desktop/extension), monorepo-only, so the exemption from the orphan scan is no longer a free pass: a boot acts on a stale vendored copy — heal or stop — instead of serving it. The old newest-mtime heuristic read "fresh" whenever ANYTHING touched dist after a src edit, and the vendor after-hook does exactly that: on 2026-08-05 `packages/web/dist` served a stale `commands/setup.js` and was missing new files for a day with both automatic layers silent.
|
|
155
|
-
|
|
156
|
-
**A live watch buys a bounded grace, not blind trust.** Stale with the root-watch lock held → recheck for ~2s so an in-flight watcher copy can land (dim note, `rebuilt` by `watch` if it does — still a heal, so the boot re-execs onto it), then the verdict: a monorepo link stops the invocation, any other local checkout heals here. **A dead watcher is loud**: a monorepo-linked boot with no live lock prints one stderr line naming the fix (`npm start` in the monorepo) — the boot's own heal ([#398](https://github.com/Omega-JS-Stack/omega/issues/398)) repairs a STALE dist once, at boot, and nothing propagates a src edit made afterwards, so the warning still carries the whole story (it fires on a fresh dist too, so it states that standing deal rather than claiming a heal that may not have happened). Both surfaced from the freshness path, so all five framework CLIs get them for free.
|
|
157
|
-
|
|
158
|
-
**Heals are locked per package** (a local checkout outside this monorepo, a monorepo link with the watch down, and the watch's own prepares): a mkdir-as-mutex at `<package>/.omega/heal.lock` with the owner pid inside (the `withStateLock` idiom from `deploy-record.js`; pid liveness replaces its mtime age because a real prepare holds the lock for minutes). The loser waits, then RE-CHECKS freshness — the winner's build makes it a no-op, so two or ten CLIs booting on the same stale link produce one build. Abandoned locks (owner gone — an EPERM pid is LIVE, never stolen) are stolen; a wait past the deadline proceeds unlocked rather than failing a boot, and lock bookkeeping that cannot be written (read-only fs, full disk) degrades to unlocked instead of throwing out of a heal. **The vendor-propagation watcher takes the same lock** around its own `npm run prepare` spawn (`startVendorPropagation`, held for the child's whole life, awaited async so the watcher keeps serving its other watches) — that spawn is devkit's own, so it locks; only prepare-package's INTERNAL src→dist copying stays unlocked third-party territory, and it converges on the watcher's next change event.
|
|
159
|
-
|
|
160
|
-
## Boot order: freshness, preludes, verb ([#890](https://github.com/Omega-JS-Stack/omega/issues/890))
|
|
161
|
-
|
|
162
|
-
Every framework CLI boot (web/backend/desktop/extension/manager) runs exactly two steps before the verb, in this order, with the identical call on all five:
|
|
163
|
-
|
|
164
|
-
1. **the freshness guard** above: a stale locally-linked `dist/` heals (or stops the boot loudly) and the invocation re-execs, so no verb ever runs stale framework code;
|
|
165
|
-
2. **the boot preludes** (`@omega.js/devkit/preludes`, `runPreludes({ verb: process.argv[2], targetDir: process.cwd() })`): the cheap, non-interactive checks the whole CLI surface has to settle first, each declaring the verbs it targets (`'all'`, one, or a list) and each a no-op on the ordinary. The list, the contract and the preludes that ship are in [devkit/index.md](../devkit/index.md#boot-preludes-890).
|
|
166
|
-
|
|
167
|
-
The order is the point: the preludes run in the process that will run the verb, which only the freshness step can guarantee is the current framework code. First shipped prelude: the `origin` heal, so a brand whose repo moved is pointed at the address GitHub's redirect answers on the next verb, whichever verb that is.
|