@adaas/are-html 0.0.23 → 0.0.25

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 (81) hide show
  1. package/dist/browser/index.d.mts +18 -2
  2. package/dist/browser/index.mjs +35 -10
  3. package/dist/browser/index.mjs.map +1 -1
  4. package/dist/node/directives/AreDirectiveIf.directive.d.mts +17 -1
  5. package/dist/node/directives/AreDirectiveIf.directive.d.ts +17 -1
  6. package/dist/node/directives/AreDirectiveIf.directive.js +29 -6
  7. package/dist/node/directives/AreDirectiveIf.directive.js.map +1 -1
  8. package/dist/node/directives/AreDirectiveIf.directive.mjs +29 -6
  9. package/dist/node/directives/AreDirectiveIf.directive.mjs.map +1 -1
  10. package/dist/node/engine/AreHTML.compiler.d.mts +3 -1
  11. package/dist/node/engine/AreHTML.compiler.d.ts +3 -1
  12. package/dist/node/engine/AreHTML.compiler.js +7 -4
  13. package/dist/node/engine/AreHTML.compiler.js.map +1 -1
  14. package/dist/node/engine/AreHTML.compiler.mjs +7 -4
  15. package/dist/node/engine/AreHTML.compiler.mjs.map +1 -1
  16. package/examples/for-perf/dist/index.html +1 -1
  17. package/examples/for-perf/dist/{mqj1mpf2-z4aokv.js → mqp8i2py-vltsx0.js} +2488 -2373
  18. package/examples/lazy-loading/README.md +76 -0
  19. package/examples/lazy-loading/concept.ts +55 -0
  20. package/examples/lazy-loading/containers/UI.container.ts +215 -0
  21. package/examples/lazy-loading/dist/app.js +3803 -0
  22. package/examples/{for-perf/dist/mqj1mpff-4fr7mw.js → lazy-loading/dist/chunks/chunk-6K72IBO4.js} +2688 -5897
  23. package/examples/lazy-loading/dist/index.html +36 -0
  24. package/examples/lazy-loading/dist/lazy/about-page.js +59 -0
  25. package/examples/lazy-loading/dist/lazy/reports-page.js +65 -0
  26. package/examples/lazy-loading/dist/lazy/settings-page.js +54 -0
  27. package/examples/lazy-loading/public/index.html +36 -0
  28. package/examples/lazy-loading/src/components/AppShell.component.ts +44 -0
  29. package/examples/lazy-loading/src/components/HomePage.component.ts +59 -0
  30. package/examples/lazy-loading/src/components/LazyOutlet.component.ts +108 -0
  31. package/examples/lazy-loading/src/components/NavBar.component.ts +98 -0
  32. package/examples/lazy-loading/src/concept.ts +116 -0
  33. package/examples/lazy-loading/src/lazy/AboutPage.component.ts +54 -0
  34. package/examples/lazy-loading/src/lazy/ReportsPage.component.ts +56 -0
  35. package/examples/lazy-loading/src/lazy/SettingsPage.component.ts +45 -0
  36. package/examples/lazy-loading/src/runtime/ComponentManifest.fragment.ts +61 -0
  37. package/examples/lazy-loading/src/runtime/LazyComponentResolver.fragment.ts +77 -0
  38. package/examples/os-desktop/README.md +91 -0
  39. package/examples/os-desktop/concept.ts +54 -0
  40. package/examples/os-desktop/containers/OS.container.ts +198 -0
  41. package/examples/os-desktop/containers/apps/AppBackend.ts +29 -0
  42. package/examples/os-desktop/containers/apps/GanttApp.backend.ts +56 -0
  43. package/examples/os-desktop/containers/apps/MarketingApp.backend.ts +68 -0
  44. package/examples/os-desktop/dist/app.js +4410 -0
  45. package/examples/os-desktop/dist/apps/gantt/app.js +271 -0
  46. package/examples/os-desktop/dist/apps/marketing/app.js +346 -0
  47. package/examples/os-desktop/dist/chunks/chunk-6K72IBO4.js +12455 -0
  48. package/examples/os-desktop/dist/chunks/chunk-EIIGUL6N.js +30 -0
  49. package/examples/os-desktop/dist/chunks/chunk-WOH7L5UR.js +30 -0
  50. package/examples/os-desktop/dist/index.html +33 -0
  51. package/examples/os-desktop/public/index.html +33 -0
  52. package/examples/os-desktop/src/apps/gantt/GanttApp.component.ts +41 -0
  53. package/examples/os-desktop/src/apps/gantt/GanttChart.component.ts +126 -0
  54. package/examples/os-desktop/src/apps/gantt/GanttStore.ts +47 -0
  55. package/examples/os-desktop/src/apps/gantt/GanttToolbar.component.ts +73 -0
  56. package/examples/os-desktop/src/apps/gantt/index.ts +13 -0
  57. package/examples/os-desktop/src/apps/marketing/MarketingApp.component.ts +53 -0
  58. package/examples/os-desktop/src/apps/marketing/MarketingStore.ts +34 -0
  59. package/examples/os-desktop/src/apps/marketing/PostEditor.component.ts +153 -0
  60. package/examples/os-desktop/src/apps/marketing/PostPreview.component.ts +110 -0
  61. package/examples/os-desktop/src/apps/marketing/index.ts +16 -0
  62. package/examples/os-desktop/src/concept.ts +126 -0
  63. package/examples/os-desktop/src/os/AppStage.component.ts +112 -0
  64. package/examples/os-desktop/src/os/AppWindow.component.ts +102 -0
  65. package/examples/os-desktop/src/os/Desktop.component.ts +106 -0
  66. package/examples/os-desktop/src/os/Dock.component.ts +174 -0
  67. package/examples/os-desktop/src/os/Hud.component.ts +83 -0
  68. package/examples/os-desktop/src/os/Launchpad.component.ts +191 -0
  69. package/examples/os-desktop/src/os/MenuBar.component.ts +156 -0
  70. package/examples/os-desktop/src/runtime/AppComponentResolver.fragment.ts +121 -0
  71. package/examples/os-desktop/src/runtime/AppRegistry.fragment.ts +104 -0
  72. package/examples/os-desktop/src/signals/MouseState.signal.ts +34 -0
  73. package/examples/os-desktop/src/signals/OSRoute.signal.ts +37 -0
  74. package/examples/os-desktop/src/signals/SelectionState.signal.ts +34 -0
  75. package/examples/signal-routing/dist/index.html +1 -1
  76. package/examples/signal-routing/dist/{mqiwo23h-bhcolu.js → mqp8hgce-4d6rh0.js} +2911 -2708
  77. package/package.json +11 -7
  78. package/src/directives/AreDirectiveIf.directive.ts +33 -4
  79. package/src/engine/AreHTML.compiler.ts +12 -2
  80. package/tests/PropPropagation.test.ts +181 -0
  81. package/tests/jest.setup.ts +11 -0
@@ -0,0 +1,116 @@
1
+ import { A_Concept, A_Context } from "@adaas/a-concept";
2
+ import { A_Config, ConfigReader } from "@adaas/a-utils/a-config";
3
+ import { A_Logger, A_LOGGER_ENV_KEYS } from "@adaas/a-utils/a-logger";
4
+ import { A_SignalBus, A_SignalState } from "@adaas/a-utils/a-signal";
5
+ import { A_Polyfill } from "@adaas/a-utils/a-polyfill";
6
+ import {
7
+ AreContainer,
8
+ AreInit,
9
+ AreSignalsContext,
10
+ } from "@adaas/are";
11
+ import {
12
+ AreRoot,
13
+ AreHTMLEngine,
14
+ AreHTMLEngineContext,
15
+ AreDirectiveIf,
16
+ AreDirectiveFor,
17
+ AreDirectiveShow,
18
+ AreRouteWatcher,
19
+ } from "src";
20
+
21
+ // ── Eager components (shipped in this bundle) ───────────────────────────────
22
+ import { AppShell } from "./components/AppShell.component";
23
+ import { NavBar } from "./components/NavBar.component";
24
+ import { HomePage } from "./components/HomePage.component";
25
+ import { LazyOutlet } from "./components/LazyOutlet.component";
26
+
27
+ // ── Runtime ─────────────────────────────────────────────────────────────────
28
+ import { ComponentManifest, ComponentManifestEntry } from "./runtime/ComponentManifest.fragment";
29
+ import { LazyComponentResolver } from "./runtime/LazyComponentResolver.fragment";
30
+
31
+ // AreRoute must come from are-html (not @adaas/are) so state.has() matches the
32
+ // class NavBar emits — otherwise the bus silently drops every route signal.
33
+ import { AreRoute as AreRouteSignal } from "src/signals/AreRoute.signal";
34
+
35
+
36
+ (async () => {
37
+ try {
38
+ // 1. Discover available components from the backend BEFORE bootstrapping.
39
+ // The set of routes/components is server-driven; lazy ones carry a URL
40
+ // the LazyOutlet will dynamically import on first visit.
41
+ const entries: ComponentManifestEntry[] = await fetch('/api/components')
42
+ .then(res => res.json())
43
+ .catch(() => []);
44
+
45
+ const manifest = new ComponentManifest({ entries });
46
+
47
+ // The engine consults this resolver whenever a node's tag does not match
48
+ // a registered component: it imports the lazy bundle from the manifest
49
+ // and the engine registers the returned class globally.
50
+ const componentResolver = new LazyComponentResolver({ manifest });
51
+
52
+ // Empty signals context: the outer <are-root id="app"> has no routing
53
+ // config, so it renders its body (<app-shell>) and ignores route signals.
54
+ // LazyOutlet does its own routing independently.
55
+ const signalsContext = new AreSignalsContext({});
56
+
57
+ const container = new AreContainer({
58
+ name: 'ARE Lazy Loading',
59
+ components: [
60
+ // ── Eager UI ─────────────────────────────────────────────
61
+ AppShell,
62
+ NavBar,
63
+ HomePage,
64
+ LazyOutlet,
65
+ // ── Directives ───────────────────────────────────────────
66
+ AreDirectiveIf,
67
+ AreDirectiveFor,
68
+ AreDirectiveShow,
69
+ // ── Engine ───────────────────────────────────────────────
70
+ A_SignalBus,
71
+ AreRoot,
72
+ AreRouteWatcher,
73
+ ConfigReader,
74
+ AreHTMLEngine,
75
+ A_Logger,
76
+ ],
77
+ entities: [
78
+ AreInit,
79
+ AreRouteSignal,
80
+ ],
81
+ fragments: [
82
+ // Both AreInit AND AreRouteSignal must be in this state, or
83
+ // state.has() returns false and the bus drops route signals.
84
+ new A_SignalState([AreInit, AreRouteSignal]),
85
+ signalsContext,
86
+ new AreHTMLEngineContext({ container: document }),
87
+ manifest,
88
+ componentResolver,
89
+ new A_Config({
90
+ defaults: {
91
+ [A_LOGGER_ENV_KEYS.LOG_LEVEL]: 'info',
92
+ },
93
+ }),
94
+ ],
95
+ });
96
+
97
+ const concept = new A_Concept({
98
+ name: 'adaas-are-example-lazy-loading',
99
+ fragments: [
100
+ new A_Config({
101
+ variables: ['CONFIG_VERBOSE', 'DEV_MODE'] as const,
102
+ defaults: { CONFIG_VERBOSE: true, DEV_MODE: true },
103
+ }),
104
+ ],
105
+ components: [A_Logger, ConfigReader, A_Polyfill],
106
+ containers: [container],
107
+ });
108
+
109
+ await concept.load();
110
+ await concept.start();
111
+
112
+ } catch (error) {
113
+ const logger = A_Context.root.resolve<A_Logger>(A_Logger)!;
114
+ logger.error(error);
115
+ }
116
+ })();
@@ -0,0 +1,54 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { Are, AreNode } from "@adaas/are";
3
+ import { AreHTMLNode } from "src";
4
+
5
+
6
+ /**
7
+ * About page — LAZY. Built into its own bundle (`/lazy/about-page.js`) and NOT
8
+ * registered at bootstrap. It is fetched + registered the first time the
9
+ * `/about` route is visited.
10
+ *
11
+ * Authored exactly like an eager component — the only difference is it lives in
12
+ * `src/lazy/` (its own esbuild entry point) and is `export default`ed so the
13
+ * loader can grab the class without knowing its name.
14
+ */
15
+ export class AboutPage extends Are {
16
+
17
+ @Are.Template
18
+ template(@A_Inject(A_Caller) node: AreNode) {
19
+ node.setContent(`
20
+ <section class="page">
21
+ <span class="eyebrow">Lazy · fetched on demand</span>
22
+ <h1 class="page-title">About this example</h1>
23
+ <p class="page-lead">
24
+ You just triggered a dynamic <code>import('/lazy/about-page.js')</code>.
25
+ The class was registered into the running scope and rendered into the
26
+ outlet — no page reload, and it wasn't in the initial bundle.
27
+ </p>
28
+ <div class="grid">
29
+ <div class="card"><h3>Code-split</h3><p>The app and every lazy bundle share framework chunks, so this class <code>extends</code> the same <code>Are</code> runtime.</p></div>
30
+ <div class="card"><h3>Backend-driven</h3><p>The server's <code>/api/components</code> manifest decided this page exists and where to fetch it.</p></div>
31
+ <div class="card"><h3>Cached</h3><p>Revisit /about — no second network request. The manifest marks it loaded.</p></div>
32
+ </div>
33
+ </section>
34
+ `);
35
+ }
36
+
37
+ @Are.Styles
38
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
39
+ node.setStyles(`
40
+ .page { padding: 48px 56px; max-width: 860px; }
41
+ .eyebrow { display: inline-block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 4px 10px; border-radius: 999px; margin-bottom: 20px; color: #fcd34d; background: rgba(252, 211, 77, 0.12); }
42
+ .page-title { font-size: 32px; font-weight: 800; color: #f4f4f5; margin-bottom: 14px; letter-spacing: -0.02em; }
43
+ .page-lead { font-size: 16px; color: #a1a1aa; line-height: 1.7; margin-bottom: 28px; }
44
+ code { background: #27272a; padding: 2px 6px; border-radius: 4px; color: #a78bfa; font-size: 13px; }
45
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
46
+ .card { background: #1c1c1f; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
47
+ .card h3 { font-size: 15px; font-weight: 600; color: #f4f4f5; margin-bottom: 8px; }
48
+ .card p { font-size: 13px; color: #71717a; line-height: 1.6; }
49
+ .card code { font-size: 12px; }
50
+ `);
51
+ }
52
+ }
53
+
54
+ export default AboutPage;
@@ -0,0 +1,56 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { Are, AreNode } from "@adaas/are";
3
+ import { AreHTMLNode } from "src";
4
+
5
+
6
+ /**
7
+ * Reports page — LAZY (`/lazy/reports-page.js`). A heavier page (charts/tables)
8
+ * is a classic candidate for code-splitting: there's no reason to pay for it on
9
+ * first paint if most users never open it.
10
+ */
11
+ export class ReportsPage extends Are {
12
+
13
+ @Are.Template
14
+ template(@A_Inject(A_Caller) node: AreNode) {
15
+ node.setContent(`
16
+ <section class="page">
17
+ <span class="eyebrow">Lazy · fetched on demand</span>
18
+ <h1 class="page-title">Reports</h1>
19
+ <p class="page-lead">Heavy, rarely-visited views are ideal lazy-load candidates — they stay out of the critical path.</p>
20
+ <div class="stats">
21
+ <div class="stat"><div class="stat-value">1,284</div><div class="stat-label">Components served</div></div>
22
+ <div class="stat"><div class="stat-value">96 ms</div><div class="stat-label">Avg load time</div></div>
23
+ <div class="stat"><div class="stat-value">3</div><div class="stat-label">Lazy bundles</div></div>
24
+ <div class="stat"><div class="stat-value">100%</div><div class="stat-label">Cache hit on revisit</div></div>
25
+ </div>
26
+ <div class="bars">
27
+ <div class="bar" style="height: 38%"></div>
28
+ <div class="bar" style="height: 62%"></div>
29
+ <div class="bar" style="height: 48%"></div>
30
+ <div class="bar" style="height: 81%"></div>
31
+ <div class="bar" style="height: 55%"></div>
32
+ <div class="bar" style="height: 72%"></div>
33
+ <div class="bar" style="height: 90%"></div>
34
+ </div>
35
+ </section>
36
+ `);
37
+ }
38
+
39
+ @Are.Styles
40
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
41
+ node.setStyles(`
42
+ .page { padding: 48px 56px; max-width: 860px; }
43
+ .eyebrow { display: inline-block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 4px 10px; border-radius: 999px; margin-bottom: 20px; color: #fcd34d; background: rgba(252, 211, 77, 0.12); }
44
+ .page-title { font-size: 32px; font-weight: 800; color: #f4f4f5; margin-bottom: 14px; letter-spacing: -0.02em; }
45
+ .page-lead { font-size: 16px; color: #a1a1aa; line-height: 1.7; margin-bottom: 28px; }
46
+ .stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 16px; margin-bottom: 32px; }
47
+ .stat { background: #1c1c1f; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
48
+ .stat-value { font-size: 26px; font-weight: 800; color: #a78bfa; }
49
+ .stat-label { font-size: 12px; color: #71717a; margin-top: 6px; }
50
+ .bars { display: flex; align-items: flex-end; gap: 12px; height: 180px; padding: 20px; background: #1c1c1f; border: 1px solid #27272a; border-radius: 12px; }
51
+ .bar { flex: 1; background: linear-gradient(180deg, #a78bfa, #7c3aed); border-radius: 6px 6px 0 0; }
52
+ `);
53
+ }
54
+ }
55
+
56
+ export default ReportsPage;
@@ -0,0 +1,45 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { Are, AreNode } from "@adaas/are";
3
+ import { AreHTMLNode } from "src";
4
+
5
+
6
+ /**
7
+ * Settings page — LAZY (`/lazy/settings-page.js`). Demonstrates that a lazy
8
+ * component can carry its own interactive markup and styles just like any
9
+ * eager one.
10
+ */
11
+ export class SettingsPage extends Are {
12
+
13
+ @Are.Template
14
+ template(@A_Inject(A_Caller) node: AreNode) {
15
+ node.setContent(`
16
+ <section class="page">
17
+ <span class="eyebrow">Lazy · fetched on demand</span>
18
+ <h1 class="page-title">Settings</h1>
19
+ <p class="page-lead">A second independently-served bundle. It shares the framework runtime with the app via code-split chunks.</p>
20
+ <div class="settings">
21
+ <label class="row"><span>Dark theme</span><input type="checkbox" checked /></label>
22
+ <label class="row"><span>Compact density</span><input type="checkbox" /></label>
23
+ <label class="row"><span>Telemetry</span><input type="checkbox" /></label>
24
+ <label class="row"><span>Auto-update components</span><input type="checkbox" checked /></label>
25
+ </div>
26
+ </section>
27
+ `);
28
+ }
29
+
30
+ @Are.Styles
31
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
32
+ node.setStyles(`
33
+ .page { padding: 48px 56px; max-width: 640px; }
34
+ .eyebrow { display: inline-block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 4px 10px; border-radius: 999px; margin-bottom: 20px; color: #fcd34d; background: rgba(252, 211, 77, 0.12); }
35
+ .page-title { font-size: 32px; font-weight: 800; color: #f4f4f5; margin-bottom: 14px; letter-spacing: -0.02em; }
36
+ .page-lead { font-size: 16px; color: #a1a1aa; line-height: 1.7; margin-bottom: 28px; }
37
+ .settings { display: flex; flex-direction: column; gap: 2px; border: 1px solid #27272a; border-radius: 12px; overflow: hidden; }
38
+ .row { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; background: #1c1c1f; font-size: 14px; color: #e4e4e7; }
39
+ .row + .row { border-top: 1px solid #27272a; }
40
+ .row input { width: 18px; height: 18px; accent-color: #a78bfa; }
41
+ `);
42
+ }
43
+ }
44
+
45
+ export default SettingsPage;
@@ -0,0 +1,61 @@
1
+ import { A_Fragment } from "@adaas/a-concept";
2
+ import { A_Frame } from "@adaas/a-frame/core";
3
+
4
+
5
+ export type ComponentManifestEntry = {
6
+ /** Route that activates this component. */
7
+ route: string;
8
+ /** Custom-element tag (kebab-case of the class name). */
9
+ tag: string;
10
+ /** Class name (PascalCase). */
11
+ name: string;
12
+ /** Public URL to dynamically import (null when bundled eagerly). */
13
+ url: string | null;
14
+ /** Whether the component must be fetched on demand. */
15
+ lazy: boolean;
16
+ };
17
+
18
+
19
+ /**
20
+ * App-level fragment that holds the component manifest fetched from the backend
21
+ * (`GET /api/components`) plus a record of which lazy components have already
22
+ * been loaded + registered into the scope.
23
+ *
24
+ * The browser app receives this from the server at bootstrap, so the *set of
25
+ * available components is backend-driven* — the frontend discovers them at
26
+ * runtime rather than hard-coding a component list.
27
+ */
28
+ @A_Frame.Define({
29
+ namespace: 'a-are-html-example',
30
+ description: 'Holds the backend-served component manifest and tracks which lazy components have been dynamically imported and registered.'
31
+ })
32
+ export class ComponentManifest extends A_Fragment {
33
+
34
+ protected _entries: ComponentManifestEntry[] = [];
35
+ protected _loaded: Set<string> = new Set();
36
+
37
+ constructor(data: { entries: ComponentManifestEntry[] }) {
38
+ super({ name: 'ComponentManifest' });
39
+ this._entries = data.entries ?? [];
40
+ }
41
+
42
+ /** All known components (eager + lazy). */
43
+ list(): ComponentManifestEntry[] {
44
+ return this._entries;
45
+ }
46
+
47
+ /** Resolve a route to its component descriptor (exact match). */
48
+ match(route: string): ComponentManifestEntry | undefined {
49
+ return this._entries.find(entry => entry.route === route);
50
+ }
51
+
52
+ /** Has the lazy bundle for this tag already been imported + registered? */
53
+ isLoaded(tag: string): boolean {
54
+ return this._loaded.has(tag);
55
+ }
56
+
57
+ /** Mark a lazy component as loaded so we never re-import it. */
58
+ markLoaded(tag: string): void {
59
+ this._loaded.add(tag);
60
+ }
61
+ }
@@ -0,0 +1,77 @@
1
+ import { A_TYPES__Ctor } from "@adaas/a-concept";
2
+ import { A_Frame } from "@adaas/a-frame/core";
3
+ import { Are, AreComponentResolver } from "@adaas/are";
4
+ import { ComponentManifest } from "./ComponentManifest.fragment";
5
+
6
+
7
+ /**
8
+ * App-side {@link AreComponentResolver} — the lazy-loading hook the engine
9
+ * consults whenever a node's tag does NOT resolve to a registered component.
10
+ *
11
+ * This is where the "fetch a component from the backend on demand" logic now
12
+ * lives. The engine (AreLoader) calls {@link resolve} with the unresolved tag;
13
+ * if the manifest says that tag is a lazy component, we dynamically `import()`
14
+ * its bundle and hand the class back. The engine then registers that class
15
+ * GLOBALLY (root scope) so every node can resolve it from here on — we no
16
+ * longer touch any scope ourselves.
17
+ *
18
+ * Compared to the previous hand-rolled `LazyOutlet`, the fetch/import/dedupe
19
+ * logic is fully decoupled from rendering: the outlet just sets a `<tag>` and
20
+ * renders; the engine + this resolver handle "where does that class come from".
21
+ */
22
+ @A_Frame.Define({
23
+ namespace: 'a-are-html-example',
24
+ description: 'Resolves unregistered component tags by dynamically importing their backend-served bundle, per the component manifest. Plugged into the engine via AreComponentResolver; returned classes are registered globally by the engine.'
25
+ })
26
+ export class LazyComponentResolver extends AreComponentResolver {
27
+
28
+ protected _manifest: ComponentManifest;
29
+
30
+ constructor(data: { manifest: ComponentManifest }) {
31
+ super({ name: 'LazyComponentResolver' });
32
+ this._manifest = data.manifest;
33
+ }
34
+
35
+ /**
36
+ * Resolve a tag to its component class, fetching the bundle on demand.
37
+ *
38
+ * Returns `undefined` when the tag is not a lazy component in the manifest
39
+ * (eager components are already registered; plain elements stay plain), or
40
+ * when it has already been imported (the engine has it registered, so a
41
+ * second resolve is unnecessary).
42
+ */
43
+ async resolve(entity: string): Promise<A_TYPES__Ctor<Are> | undefined> {
44
+ const target = this._manifest.list().find(item => item.tag === entity);
45
+
46
+ if (!target || !target.lazy || !target.url || this._manifest.isLoaded(entity)) {
47
+ return undefined;
48
+ }
49
+
50
+ const Component = await this.importComponent(target.url);
51
+
52
+ if (!Component) {
53
+ return undefined;
54
+ }
55
+
56
+ this._manifest.markLoaded(entity);
57
+
58
+ return Component as A_TYPES__Ctor<Are>;
59
+ }
60
+
61
+ /**
62
+ * Dynamic import of a component bundle served by the backend.
63
+ *
64
+ * Because the app + lazy bundles are code-split against shared framework
65
+ * chunks, the class returned here `extends` the SAME `Are`/DI runtime the
66
+ * host app is running.
67
+ */
68
+ protected async importComponent(url: string): Promise<Function | undefined> {
69
+ const mod: Record<string, unknown> = await import(/* @vite-ignore */ url);
70
+
71
+ if (typeof mod.default === 'function') {
72
+ return mod.default as Function;
73
+ }
74
+
75
+ return Object.values(mod).find(value => typeof value === 'function') as Function | undefined;
76
+ }
77
+ }
@@ -0,0 +1,91 @@
1
+ # ARE · OS Desktop
2
+
3
+ A macOS-style **operating-system desktop** built with ARE, showing how to manage
4
+ **dynamically installable applications** where **each app is its own bundle with
5
+ its own frontend *and* its own backend**, wired together by **signal-based
6
+ routing**.
7
+
8
+ ```
9
+ ┌──────────────────────────────────────────────── menu bar (OSRoute, Selection)
10
+ │ desktop (wallpaper)
11
+ │ ┌────────────── app window (macOS chrome) ──────────────┐
12
+ │ │ Marketing · post-editor | post-preview │
13
+ │ │ Timeline · gantt-toolbar | gantt-chart │
14
+ │ └───────────────────────────────────────────────────────┘
15
+ │ HUD (MouseState, Selection) dock (OSRoute)
16
+ └──────────────────────────────────────────────────────────────────
17
+ ```
18
+
19
+ ## Run
20
+
21
+ ```bash
22
+ npm run example:os-desktop
23
+ # http://localhost:8084
24
+ ```
25
+
26
+ The desktop boots empty. Click the dock's **+ (Launchpad)** button, **Install**
27
+ an app — that fetches the app's bundle and adds it to the dock — then open it.
28
+
29
+ ## What it demonstrates
30
+
31
+ ### 1. Apps are self-contained bundles (FE + BE), installed at runtime
32
+ Each app is a single code-split bundle exporting a **set** of components plus a
33
+ matching backend:
34
+
35
+ | App | Frontend components | Backend endpoint |
36
+ |-------------|----------------------------------------------|-----------------------------------|
37
+ | Marketing | `marketing-app` · `post-editor` · `post-preview` | `GET /apps/marketing/api/hashtags` |
38
+ | Timeline | `gantt-app` · `gantt-toolbar` · `gantt-chart` | `GET /apps/gantt/api/tasks` |
39
+
40
+ The OS kernel (`containers/OS.container.ts`) never imports an app's source. It
41
+ only:
42
+ - advertises descriptors at `GET /api/apps`,
43
+ - builds the shell + every app bundle in **one code-split esbuild pass** (so a
44
+ lazily-`import()`ed app reuses the SAME framework runtime singletons), and
45
+ - routes `/apps/<id>/api/*` to that app's own backend.
46
+
47
+ Adding an app = adding one `AppBackend` to the kernel. Installing it in the UI
48
+ (`AppRegistry.install`) makes it appear in the dock; the
49
+ `AppComponentResolver` then lazily imports its bundle on first render — one
50
+ network request per app, however many components it ships.
51
+
52
+ ### 2. Signal-based routing with several signals
53
+ Three OS-wide signals travel on one bus, and many components react to each one
54
+ **independently** — there is no central controller:
55
+
56
+ | Signal | Produced by | Consumed by |
57
+ |------------------|--------------------------------------|-----------------------------------------------|
58
+ | `OSRoute` | dock / launchpad / window chrome | `AppStage` (swaps surface), `MenuBar` (title), `OsDock` (running dot) |
59
+ | `MouseState` | global `mousemove` (throttled) | `OsHud` (x/y read-out), `GanttChart` (guide line) |
60
+ | `SelectionState` | global `selectionchange` | `OsHud` (length), `MenuBar` (chip), `PostPreview` (hashtag hint) |
61
+
62
+ `AppStage` is a custom **signal outlet**: it maps the current `OSRoute` to one
63
+ surface (`app-window` / `os-launchpad` / empty desktop) and swaps it with the
64
+ engine primitives `clear()` + `render()`, unsubscribing the outgoing subtree
65
+ from the bus first.
66
+
67
+ > Every dispatched signal type is listed in BOTH `entities` and the
68
+ > `A_SignalState([...])` structure in `src/concept.ts` — otherwise the bus
69
+ > silently drops it.
70
+
71
+ ## Layout
72
+
73
+ ```
74
+ os-desktop/
75
+ ├── concept.ts # node entry (kernel)
76
+ ├── public/index.html # <are-root id="os"><os-desktop>
77
+ ├── containers/
78
+ │ ├── OS.container.ts # build + serve + /api/apps + app-API routing
79
+ │ └── apps/
80
+ │ ├── AppBackend.ts # app backend interface
81
+ │ ├── MarketingApp.backend.ts
82
+ │ └── GanttApp.backend.ts
83
+ └── src/
84
+ ├── concept.ts # browser bootstrap (registry + resolver + bus)
85
+ ├── signals/ # OSRoute · MouseState · SelectionState
86
+ ├── runtime/ # AppRegistry + AppComponentResolver
87
+ ├── os/ # shell: desktop, menu bar, dock, hud, stage, window, launchpad
88
+ └── apps/
89
+ ├── marketing/ # marketing bundle (FE) + own store
90
+ └── gantt/ # gantt bundle (FE) + own store
91
+ ```
@@ -0,0 +1,54 @@
1
+ import { A_Concept, A_Context } from "@adaas/a-concept";
2
+ import { OSContainer } from "./containers/OS.container";
3
+ import { A_Logger } from "@adaas/a-utils/a-logger";
4
+ import { A_Polyfill } from "@adaas/a-utils/a-polyfill";
5
+ import { A_Config, ENVConfigReader } from "@adaas/a-utils/a-config";
6
+
7
+
8
+ /**
9
+ * Node-side entry point for the OS Desktop example.
10
+ *
11
+ * The OSContainer is the "kernel": it builds the OS shell bundle plus one
12
+ * code-split bundle per app, serves the static desktop SPA, advertises the
13
+ * installable apps at `/api/apps`, and routes each app's `/apps/<id>/api/*`
14
+ * calls to that app's own backend.
15
+ *
16
+ * In the browser, the desktop boots, you install apps from the Launchpad (each
17
+ * install lazily fetches that app's bundle), open them in macOS-style windows,
18
+ * and watch the OS-wide signals (route / mouse / selection) drive the menu bar,
19
+ * dock and HUD independently.
20
+ */
21
+ (async () => {
22
+ try {
23
+ const Application = new OSContainer({
24
+ name: 'ARE OS Desktop',
25
+ components: [
26
+ A_Polyfill,
27
+ ENVConfigReader,
28
+ A_Logger,
29
+ ],
30
+ fragments: [
31
+ new A_Config({
32
+ defaults: {
33
+ PORT: 8084,
34
+ CONFIG_VERBOSE: true,
35
+ DEV_MODE: true,
36
+ }
37
+ }),
38
+ ]
39
+ });
40
+
41
+ const concept = new A_Concept({
42
+ name: 'adaas-are-example-os-desktop',
43
+ components: [A_Logger, A_Polyfill, ENVConfigReader],
44
+ containers: [Application],
45
+ });
46
+
47
+ await concept.load();
48
+ await concept.start();
49
+
50
+ } catch (error) {
51
+ const logger = A_Context.root.resolve<A_Logger>(A_Logger)!;
52
+ logger.error(error);
53
+ }
54
+ })();