lego-dom 1.0.0 → 1.3.4

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 (74) hide show
  1. package/.legodom +87 -0
  2. package/CHANGELOG.md +87 -3
  3. package/cdn.html +10 -5
  4. package/docs/.vitepress/config.js +23 -7
  5. package/docs/api/config.md +95 -0
  6. package/docs/api/define.md +29 -2
  7. package/docs/api/directives.md +10 -2
  8. package/docs/api/index.md +1 -0
  9. package/docs/contributing/01-welcome.md +2 -0
  10. package/docs/contributing/02-registry.md +37 -3
  11. package/docs/contributing/06-init.md +13 -2
  12. package/docs/contributing/07-observer.md +3 -0
  13. package/docs/contributing/08-snap.md +15 -1
  14. package/docs/contributing/10-studs.md +3 -1
  15. package/docs/contributing/11-scanner.md +13 -0
  16. package/docs/contributing/12-render.md +32 -10
  17. package/docs/contributing/13-directives.md +19 -1
  18. package/docs/contributing/14-events.md +1 -1
  19. package/docs/contributing/15-router.md +49 -1
  20. package/docs/contributing/16-state.md +9 -10
  21. package/docs/contributing/17-legodom.md +1 -8
  22. package/docs/contributing/index.md +23 -4
  23. package/docs/examples/form.md +1 -1
  24. package/docs/examples/index.md +3 -3
  25. package/docs/examples/routing.md +10 -10
  26. package/docs/examples/sfc-showcase.md +1 -1
  27. package/docs/examples/todo-app.md +7 -7
  28. package/docs/guide/cdn-usage.md +44 -18
  29. package/docs/guide/components.md +18 -12
  30. package/docs/guide/directives.md +131 -22
  31. package/docs/guide/directory-structure.md +248 -0
  32. package/docs/guide/faq.md +210 -0
  33. package/docs/guide/getting-started.md +14 -10
  34. package/docs/guide/index.md +1 -1
  35. package/docs/guide/lifecycle.md +32 -0
  36. package/docs/guide/quick-start.md +4 -4
  37. package/docs/guide/reactivity.md +2 -2
  38. package/docs/guide/routing.md +69 -8
  39. package/docs/guide/server-side.md +134 -0
  40. package/docs/guide/sfc.md +96 -13
  41. package/docs/guide/templating.md +62 -57
  42. package/docs/index.md +9 -9
  43. package/docs/router/basic-routing.md +8 -8
  44. package/docs/router/cold-entry.md +2 -2
  45. package/docs/router/history.md +7 -7
  46. package/docs/router/index.md +1 -1
  47. package/docs/router/resolver.md +5 -5
  48. package/docs/router/surgical-swaps.md +5 -5
  49. package/docs/tutorial/01-project-setup.md +152 -0
  50. package/docs/tutorial/02-your-first-component.md +226 -0
  51. package/docs/tutorial/03-adding-routes.md +279 -0
  52. package/docs/tutorial/04-multi-page-app.md +329 -0
  53. package/docs/tutorial/05-state-and-globals.md +285 -0
  54. package/docs/tutorial/index.md +40 -0
  55. package/examples/vite-app/index.html +1 -0
  56. package/examples/vite-app/src/app.js +2 -2
  57. package/examples/vite-app/src/components/side-menu.lego +46 -0
  58. package/examples/vite-app/vite.config.js +2 -1
  59. package/main.js +261 -72
  60. package/main.min.js +7 -0
  61. package/monitoring-plugin.js +111 -0
  62. package/package.json +4 -2
  63. package/parse-lego.js +49 -22
  64. package/tests/error.test.js +74 -0
  65. package/tests/main.test.js +2 -2
  66. package/tests/memory.test.js +68 -0
  67. package/tests/monitoring.test.js +74 -0
  68. package/tests/naming.test.js +74 -0
  69. package/tests/parse-lego.test.js +2 -2
  70. package/tests/security.test.js +67 -0
  71. package/tests/server.test.js +114 -0
  72. package/tests/syntax.test.js +67 -0
  73. package/vite-plugin.js +3 -2
  74. package/docs/guide/contributing.md +0 -32
@@ -2,6 +2,32 @@
2
2
 
3
3
  Directives are special attributes that add reactive behavior to elements.
4
4
 
5
+ ## b-if
6
+
7
+ Conditional rendering (adds/removes from DOM).
8
+
9
+ ### Basic Usage
10
+
11
+ ```html
12
+ <p b-if="isLoggedIn">Welcome back!</p>
13
+ <p b-if="!isLoggedIn">Please log in</p>
14
+ ```
15
+
16
+ ### With Expressions
17
+
18
+ ```html
19
+ <div b-if="count > 0">Count is [[ count ]]</div>
20
+ <div b-if="items.length === 0">No items</div>
21
+ ```
22
+
23
+ ::: tip b-if vs b-show
24
+ `b-if` adds or removes the element from the DOM.
25
+ `b-show` toggles `display: none`.
26
+
27
+ Use `b-if` if the condition rarely changes.
28
+ Use `b-show` if you toggle often.
29
+ :::
30
+
5
31
  ## b-show
6
32
 
7
33
  Conditional rendering using `display: none`.
@@ -16,7 +42,7 @@ Conditional rendering using `display: none`.
16
42
  ### With Expressions
17
43
 
18
44
  ```html
19
- <div b-show="count > 0">Count is {{ count }}</div>
45
+ <div b-show="count > 0">Count is [[ count ]]</div>
20
46
  <div b-show="items.length === 0">No items</div>
21
47
  <div b-show="user && user.role === 'admin'">Admin panel</div>
22
48
  ```
@@ -32,6 +58,73 @@ Conditional rendering using `display: none`.
32
58
  `b-show` sets `display: none` when the condition is false. The element stays in the DOM but is hidden.
33
59
  :::
34
60
 
61
+ ## b-html
62
+
63
+ Renders raw HTML content.
64
+
65
+ > [!WARNING]
66
+ > Only use on trusted content. This exposes you to XSS vulnerabilities if used with user input.
67
+
68
+ ```html
69
+ <div b-html="rawContent"></div>
70
+ ```
71
+
72
+ ```js
73
+ {
74
+ rawContent: '<b>Bold</b> and <i>Italic</i>'
75
+ }
76
+ ```
77
+
78
+ ## b-text
79
+
80
+ Sets the text content of an element.
81
+
82
+ > [!WARNING] Limitation
83
+ > **`b-text` is extremely weak.** Unlike `[[ ]]`, it **does not** support JavaScript expressions (math, logic, functions). It ONLY supports simple property paths.
84
+
85
+ ### Functionality
86
+
87
+ | Syntax | Supported? | Example |
88
+ | :--- | :--- | :--- |
89
+ | **Property Path** | Yes | `b-text="user.name"` |
90
+ | **Math** | No | `b-text="count + 1"` |
91
+ | **Logic** | No | `b-text="isActive ? 'Yes' : 'No'"` |
92
+ | **Methods** | No | `b-text="formatDate(date)"` |
93
+
94
+ Use `[[ ]]` for anything complex. `b-text` is strictly for direct property binding.
95
+
96
+ ```html
97
+ <!-- Works -->
98
+ <span b-text="user.name"></span>
99
+
100
+ <!-- Does NOT Work (Use [[ ]] instead) -->
101
+ <span b-text="user.firstName + ' ' + user.lastName"></span>
102
+ <span b-text="count + 1"></span>
103
+ ```
104
+
105
+ ## b-var
106
+
107
+ Creates a reference to a DOM element accessible via `this.$vars`.
108
+
109
+ Use `b-var` when you need direct DOM access (e.g., `.focus()`, `.click()`, `.play()`).
110
+
111
+ ### Usage
112
+
113
+ ```html
114
+ <input type="file" b-var="fileInput" style="display:none">
115
+ <button @click="$vars.fileInput.click()">Upload</button>
116
+ ```
117
+
118
+ Or in script:
119
+
120
+ ```javascript
121
+ export default {
122
+ openPicker() {
123
+ this.$vars.fileInput.click();
124
+ }
125
+ }
126
+ ```
127
+
35
128
  ## b-for
36
129
 
37
130
  List rendering.
@@ -40,7 +133,7 @@ List rendering.
40
133
 
41
134
  ```html
42
135
  <ul>
43
- <li b-for="item in items">{{ item }}</li>
136
+ <li b-for="item in items">[[ item ]]</li>
44
137
  </ul>
45
138
  ```
46
139
 
@@ -49,7 +142,7 @@ List rendering.
49
142
  ```html
50
143
  <ul>
51
144
  <li b-for="todo in todos">
52
- {{ todo.text }} - {{ todo.done ? 'Done' : 'Pending' }}
145
+ [[ todo.text ]] - [[ todo.done ? 'Done' : 'Pending' ]]
53
146
  </li>
54
147
  </ul>
55
148
  ```
@@ -61,7 +154,7 @@ Use `$index` (implicit variable):
61
154
  ```html
62
155
  <ul>
63
156
  <li b-for="item in items">
64
- #{{ $index + 1 }}: {{ item.name }}
157
+ #[[ $index + 1 ]]: [[ item.name ]]
65
158
  </li>
66
159
  </ul>
67
160
  ```
@@ -70,10 +163,10 @@ Use `$index` (implicit variable):
70
163
 
71
164
  ```html
72
165
  <div b-for="category in categories">
73
- <h3>{{ category.name }}</h3>
166
+ <h3>[[ category.name ]]</h3>
74
167
  <ul>
75
168
  <li b-for="product in category.products">
76
- {{ product.name }}
169
+ [[ product.name ]]
77
170
  </li>
78
171
  </ul>
79
172
  </div>
@@ -83,8 +176,8 @@ Use `$index` (implicit variable):
83
176
 
84
177
  ```html
85
178
  <li b-for="user in users">
86
- <span b-show="user.active">✅ {{ user.name }}</span>
87
- <span b-show="!user.active">❌ {{ user.name }}</span>
179
+ <span b-show="user.active">✅ [[ user.name ]]</span>
180
+ <span b-show="!user.active">❌ [[ user.name ]]</span>
88
181
  </li>
89
182
  ```
90
183
 
@@ -96,7 +189,7 @@ Two-way data binding for form inputs.
96
189
 
97
190
  ```html
98
191
  <input b-sync="username" placeholder="Enter username">
99
- <p>Hello, {{ username }}!</p>
192
+ <p>Hello, [[ username ]]!</p>
100
193
  ```
101
194
 
102
195
  ### Checkbox
@@ -112,7 +205,7 @@ Two-way data binding for form inputs.
112
205
  <input type="radio" name="size" value="small" b-sync="selectedSize">
113
206
  <input type="radio" name="size" value="medium" b-sync="selectedSize">
114
207
  <input type="radio" name="size" value="large" b-sync="selectedSize">
115
- <p>Selected: {{ selectedSize }}</p>
208
+ <p>Selected: [[ selectedSize ]]</p>
116
209
  ```
117
210
 
118
211
  ### Select Dropdown
@@ -123,14 +216,14 @@ Two-way data binding for form inputs.
123
216
  <option value="uk">United Kingdom</option>
124
217
  <option value="ca">Canada</option>
125
218
  </select>
126
- <p>Country: {{ country }}</p>
219
+ <p>Country: [[ country ]]</p>
127
220
  ```
128
221
 
129
222
  ### Textarea
130
223
 
131
224
  ```html
132
225
  <textarea b-sync="message" rows="4"></textarea>
133
- <p>{{ message.length }} characters</p>
226
+ <p>[[ message.length ]] characters</p>
134
227
  ```
135
228
 
136
229
  ### In b-for Loops
@@ -138,7 +231,7 @@ Two-way data binding for form inputs.
138
231
  ```html
139
232
  <li b-for="todo in todos">
140
233
  <input type="checkbox" b-sync="todo.done">
141
- <span class="{{ todo.done ? 'done' : '' }}">{{ todo.text }}</span>
234
+ <span class="[[ todo.done ? 'done' : '' ]]">[[ todo.text ]]</span>
142
235
  </li>
143
236
  ```
144
237
 
@@ -220,8 +313,8 @@ Client-side navigation (prevents page reload).
220
313
  ### With Dynamic Routes
221
314
 
222
315
  ```html
223
- <a href="/user/{{ userId }}" b-link>View Profile</a>
224
- <a href="/product/{{ productId }}" b-link>{{ productName }}</a>
316
+ <a href="/user/[[ userId ]]" b-link>View Profile</a>
317
+ <a href="/product/[[ productId ]]" b-link>[[ productName ]]</a>
225
318
  ```
226
319
 
227
320
  ::: tip Router Required
@@ -272,7 +365,7 @@ Lego.define('user-card', `...`, {
272
365
 
273
366
  ```html
274
367
  <li b-for="item in items" b-show="item.visible">
275
- {{ item.name }}
368
+ [[ item.name ]]
276
369
  </li>
277
370
  ```
278
371
 
@@ -281,7 +374,7 @@ Lego.define('user-card', `...`, {
281
374
  ```html
282
375
  <li b-for="todo in todos">
283
376
  <input type="checkbox" b-sync="todo.done">
284
- {{ todo.text }}
377
+ [[ todo.text ]]
285
378
  </li>
286
379
  ```
287
380
 
@@ -303,7 +396,7 @@ Lego.define('user-card', `...`, {
303
396
  <div b-show="showPanel">Panel content</div>
304
397
 
305
398
  <!-- ❌ Verbose -->
306
- <div style="display: {{ showPanel ? 'block' : 'none' }}">Panel content</div>
399
+ <div style="display: [[ showPanel ? 'block' : 'none' ]]">Panel content</div>
307
400
  ```
308
401
 
309
402
  ### 2. Keep Event Handlers Simple
@@ -350,7 +443,7 @@ Move complex logic to methods.
350
443
 
351
444
  ```html
352
445
  <!-- For frequent toggling -->
353
- <div class="{{ visible ? '' : 'hidden' }}">Content</div>
446
+ <div class="[[ visible ? '' : 'hidden' ]]">Content</div>
354
447
  ```
355
448
 
356
449
  ```css
@@ -377,7 +470,7 @@ Paginate large lists:
377
470
  ```
378
471
 
379
472
  ```html
380
- <li b-for="item in visibleItems()">{{ item.name }}</li>
473
+ <li b-for="item in visibleItems()">[[ item.name ]]</li>
381
474
  ```
382
475
 
383
476
  ## Common Patterns
@@ -393,7 +486,7 @@ Paginate large lists:
393
486
 
394
487
  ```html
395
488
  <button @click="count--">-</button>
396
- <span>{{ count }}</span>
489
+ <span>[[ count ]]</span>
397
490
  <button @click="count++">+</button>
398
491
  ```
399
492
 
@@ -404,7 +497,7 @@ Paginate large lists:
404
497
  <ul>
405
498
  <li b-for="todo in todos">
406
499
  <input type="checkbox" b-sync="todo.done">
407
- <span class="{{ todo.done ? 'done' : '' }}">{{ todo.text }}</span>
500
+ <span class="[[ todo.done ? 'done' : '' ]]">[[ todo.text ]]</span>
408
501
  </li>
409
502
  </ul>
410
503
  ```
@@ -423,6 +516,22 @@ Paginate large lists:
423
516
  <div b-show="activeTab === 'settings'">Settings content</div>
424
517
  ```
425
518
 
519
+ ## See Also
520
+
521
+ Some directives are specific to certain features and are documented in their respective guides:
522
+
523
+ ### Component Directives
524
+
525
+ - **`b-id`**: Defines a component from a template.
526
+ - **`b-styles`**: Applies shared styles to a component.
527
+ - See [Components Guide](/guide/components)
528
+
529
+ ### Routing Directives
530
+
531
+ - **`b-target`**: Specifies the target element for surgical routing updates.
532
+ - **`b-link`**: Controls browser history behavior for links.
533
+ - See [Routing Guide](/guide/routing)
534
+
426
535
  ## Next Steps
427
536
 
428
537
  - See [directive examples](/examples/)
@@ -0,0 +1,248 @@
1
+ # Chaos Tends To A Minimum
2
+
3
+ > [!NOTE] This is a Recommendation, Not a Bible
4
+ > This page is one person's take on how to structure large LegoDOM applications. It is not enforced by the framework. You are encouraged to adapt, improve, or completely ignore it. If you find a better pattern, please share it.
5
+
6
+ ---
7
+
8
+ In LegoDOM land everything is a `.lego`. A `block` is a lego, a `widget` is a lego, a `component` is a lego, a `page` is a lego.
9
+
10
+ But what do they *mean*?
11
+
12
+
13
+ ## The Four Levels
14
+
15
+ | Level | Role | Knows About | Example |
16
+ | :--- | :--- | :--- | :--- |
17
+ | **Block** | Identity | Its own visuals | `<block-avatar>`, `<block-button>` |
18
+ | **Widget** | Intent | How an interaction works | `<widget-file-trigger>`, `<widget-dropdown>` |
19
+ | **Component** | Computation | Business data & API calls | `<comp-profile-settings>`, `<comp-checkout-form>` |
20
+ | **Page** | Coordination | Layout & Routing | `<page-dashboard>`, `<page-login>` |
21
+
22
+
23
+ ## The Litmus Test: The Avatar Upload
24
+
25
+ **The Question:**
26
+ > I have an avatar. When I click it, it opens a file picker. When the file changes, it POSTs to `/v1/avatars`. What is it? A Block? A Widget? A Component?
27
+
28
+ **The Technical Answer:** `main.js` allows all of this in one file. It will work.
29
+
30
+ **The Architectural Answer:** You have conflated three distinct responsibilities into one:
31
+ 1. **Identity** Looking like an avatar.
32
+ 2. **Intent** Picking a file.
33
+ 3. **Computation** Saving to *your specific server*.
34
+
35
+ **The Consequence:**
36
+ If you later want to display that avatar in a read-only user list, you *cannot reuse this component* because clicking it triggers unwanted upload logic. You will be forced to create a new, duplicate `<block-avatar>` just for display.
37
+
38
+ ### The "Lego Way" Solution
39
+
40
+ Split this into its three atomic truths to maximize reusability.
41
+
42
+ #### 1. The Block (Identity)
43
+
44
+ ```html
45
+ <!-- block-avatar.lego -->
46
+ <template>
47
+ <img class="avatar" src="[[ src ]]" alt="[[ alt ]]">
48
+ </template>
49
+
50
+ <style>
51
+ .avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; }
52
+ </style>
53
+
54
+ <script>
55
+ export default {
56
+ src: '/default-avatar.png',
57
+ alt: 'User'
58
+ }
59
+ </script>
60
+ ```
61
+ - **Role:** Just the visuals. Circular crop, fallback, size classes.
62
+ - **Logic:** None. Zero business knowledge.
63
+ - **Reusability:** Used *everywhere* e.g. Navbar, User List, Profile Page, Comments.
64
+
65
+ #### 2. The Widget (Intent)
66
+
67
+ ```html
68
+ <!-- widget-file-trigger.lego -->
69
+ <template>
70
+ <div @click="openPicker()">
71
+ <slot></slot>
72
+ <input type="file" style="display:none" b-var="avatarFileElement" @change="onFileChange">
73
+ </div>
74
+ </template>
75
+
76
+ <script>
77
+ export default {
78
+ openPicker() {
79
+ this.$vars.avatarFileElement.click();
80
+ },
81
+ onFileChange(event) {
82
+ // It doesn't know about /v1/avatars. It just hands you the file.
83
+ this.$emit('file-selected', { file: event.target.files[0] });
84
+ }
85
+ }
86
+ </script>
87
+ ```
88
+ - **Role:** The mechanic. Wraps any slotted content and makes it clickable to open a file dialog.
89
+ - **Logic:** Handles the hidden `<input type="file">`, listens for `change`, and **emits an event**.
90
+ - **Boundary:** It uses `$emit` (from `main.js`) to broadcast what happened. It never makes API calls.
91
+
92
+ #### 3. The Component (Computation a.k.a. Context)
93
+
94
+ ```html
95
+ <!-- comp-profile-settings.lego -->
96
+ <template>
97
+ <h2>Your Profile</h2>
98
+ <widget-file-trigger @file-selected="uploadAvatar">
99
+ <block-avatar src="[[ user.avatarUrl ]]"></block-avatar>
100
+ </widget-file-trigger>
101
+ <p>Click avatar to change</p>
102
+ </template>
103
+
104
+ <script>
105
+ export default {
106
+ user: { avatarUrl: '/me.jpg' },
107
+
108
+ async uploadAvatar(event) {
109
+ const file = event.detail.file;
110
+ // BUSINESS LOGIC LIVES HERE
111
+ const formData = new FormData();
112
+ formData.append('avatar', file);
113
+ const res = await fetch('/v1/avatars', { method: 'POST', body: formData });
114
+ const data = await res.json();
115
+ this.user.avatarUrl = data.url; // Reactivity updates the block-avatar
116
+ }
117
+ }
118
+ </script>
119
+ ```
120
+ - **Role:** The boss. Assembles the parts and owns the API call.
121
+ - **Logic:** Knows about `user`, knows about `/v1/avatars`, handles the business outcome.
122
+ - **Boundary:** This is the *only* place that knows about your specific backend.
123
+
124
+
125
+ ## The Definitions
126
+
127
+ ### Blocks (Atoms)
128
+
129
+ > **TL;DR** A Block is an irreducible thing. A UI with identity, but no narrative intent.
130
+
131
+ A Block cannot be broken down into smaller Blocks. It is self-contained.
132
+
133
+ - **State:** Visual only. It can track "Is my mouse over me?" or "Am I spinning?". It never knows about User IDs, Auth tokens, or business data.
134
+ - **Naming:** `block-avatar`, `block-button`, `block-spinner`, `block-card`.
135
+ - **Rule:** If you find yourself nesting Blocks inside other Blocks, you have graduated to a Widget.
136
+
137
+ > [!WARNING] Don't Confuse Styling with Blocks
138
+ > You can use CSS to style many `<h1>` elements. That doesn't mean you need a `<block-title-header>`. Only create a Block when it has distinct *behavior* or *identity* beyond mere styling.
139
+
140
+
141
+ ### Widgets (Molecules)
142
+
143
+ > **TL;DR** A Widget is an interaction, not a thing.
144
+
145
+ A Widget gives Blocks a reason to exist together. It defines *how* an interaction works without embedding *who* it's for or *what* business outcome it serves.
146
+
147
+ - **State:** Internal UI state only. "Is the dropdown open?" "Which tab is active?"
148
+ - **Naming:** `widget-dropdown`, `widget-modal`, `widget-datepicker`, `widget-file-trigger`.
149
+ - **Rule:** Widgets are portable. You should be able to copy a Widget to a completely different project and it should still work.
150
+ - **Communication:** Uses `$emit()` to broadcast events. Never makes API calls itself.
151
+
152
+
153
+ ### Components (Organisms)
154
+
155
+ > **TL;DR** Components are where your app features come to life.
156
+
157
+ A Component is a Widget (or set of Widgets) bound to specific data, rules, and responsibility. It's where interaction becomes meaningful to *this* application.
158
+
159
+ - **State:** Domain-specific. Owns data fetched from APIs. Knows about the current user.
160
+ - **Naming:** `comp-profile-settings`, `comp-order-history`, `comp-payroll-table`.
161
+ - **Rule:** A Component knows *who* it is for, *what* data it owns, and *what* outcome it must produce.
162
+ - **Communication:** Listens to Widget events (like `@file-selected`) and performs business transactions.
163
+
164
+
165
+ ### Pages (Coordination)
166
+
167
+ > **TL;DR** A Page is the top-level host that routing targets.
168
+
169
+ Pages are the uppermost hosts. They orchestrate the layout of Components within the context of a LegoDOM application.
170
+
171
+ - **Role:** Define the grid. Orchestrate Components. Handle route parameters.
172
+ - **Naming:** `page-dashboard`, `page-login`, `page-user-profile`.
173
+ - **Rule:** Pages are the *only* UI units directly known to `<lego-router>`. While a Component owns the *logic* of a feature, a Page owns the *real estate*.
174
+
175
+
176
+ ## Recommended Directory Structure
177
+
178
+ ```text
179
+ src/
180
+ ├── blocks/ # Design System primitives
181
+ │ ├── block-avatar.lego
182
+ │ ├── block-button.lego
183
+ │ └── block-input.lego
184
+ ├── widgets/ # Generic, portable UI tools
185
+ │ ├── widget-dropdown.lego
186
+ │ ├── widget-modal.lego
187
+ │ └── widget-file-trigger.lego
188
+ ├── components/ # Domain-specific features
189
+ │ ├── comp-profile-settings.lego
190
+ │ └── comp-order-history.lego
191
+ ├── pages/ # Route targets
192
+ │ ├── page-dashboard.lego
193
+ │ └── page-login.lego
194
+ └── main.js # App entry, routes, globals
195
+
196
+
197
+ ## Scaling to Multi-Domain Apps
198
+
199
+ For large enterprise apps with multiple business domains (HRIS, Finance, Planning, Messages), the flat structure breaks down. Use a **Domain-First** approach.
200
+
201
+ ### Domain-First Structure
202
+
203
+ ```text
204
+ src/
205
+ ├── shared/ # Truly universal (used by 3+ domains)
206
+ │ ├── blocks/
207
+ │ │ ├── block-button.lego
208
+ │ │ └── block-avatar.lego
209
+ │ └── widgets/
210
+ │ ├── widget-datepicker.lego
211
+ │ └── widget-modal.lego
212
+
213
+ ├── hris/ # Human Resources domain
214
+ │ ├── blocks/
215
+ │ ├── widgets/
216
+ │ │ └── widget-leave-calendar.lego
217
+ │ ├── components/
218
+ │ │ └── comp-employee-list.lego
219
+ │ └── pages/
220
+ │ └── page-employees.lego
221
+
222
+ ├── finance/ # Finance domain
223
+ │ ├── widgets/
224
+ │ ├── components/
225
+ │ └── pages/
226
+
227
+ ├── planning/ # Planning domain
228
+ │ ├── widgets/
229
+ │ ├── components/
230
+ │ └── pages/
231
+
232
+ └── main.js
233
+ ```
234
+
235
+ ### Rules for Multi-Domain
236
+
237
+ 1. **Shared** = Only what is used by 3+ domains. Be ruthless.
238
+ 2. **Domain folders** = Each domain owns its own `blocks/`, `widgets/`, `components/`, `pages/`.
239
+ 3. **No cross-domain Component imports.** If HRIS needs Finance data, go through a shared service or global state, not by importing `finance/components/...`.
240
+
241
+
242
+ ## Summary
243
+
244
+ | If you keep it all in one file... | The "Enterprise" Standard |
245
+ | :--- | :--- |
246
+ | Name it `<comp-avatar-upload>` and accept it is not reusable. | Let the **Widget** handle the interaction. Let the **Component** handle the transaction. |
247
+
248
+ **This is a recommendation.** If you find a pattern that works better for your team, use it. The goal is clarity, reusability, and reducing arguments - not rigid adherence to a doctrine.