@base-framework/base 3.0.507 → 3.0.509

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/copilot.md CHANGED
@@ -1,97 +1,481 @@
1
- # Copilot Guide for Base
2
-
3
- This guide helps AI agents (and humans) work effectively with the Base framework when this repo is open in the editor.
4
-
5
- ## Core concepts
6
- - Layouts are plain JS objects parsed into DOM (browser) or HTML strings (server). See: `modules/layout/element/parser.js`, `modules/layout/render/*`.
7
- - Public API: `src/base.js` re-exports `Builder`, `Component`, `Atom`, `Data`, `SimpleData`, `Model`, `Import`, `NavLink`, `router`, `Html`, `Directives`, etc.
8
- - Units and Components: `Unit` provides lifecycle/context; `Component` adds state/events. See: `modules/component/{unit.js,component.js}`.
9
- - Data types: `Data` (deep), `SimpleData` (shallow), `Model` (server-backed). See: `modules/data/types/**`.
10
-
11
- ## Atoms (from wiki)
12
- - Create atoms with `Atom((props, children) => layout)`. Optional args supported:
13
- - `Div({class:'text'})`, `Div('text')`, `Div([Header()])`, `Div({class:'x'}, ['child'])`.
14
- - Events receive `(event, parentComponent)` directly in the layout: `{ click(e, parent) { /*...*/ } }`.
15
- - Prefer composition (nest atoms via children) over inheritance.
16
-
17
- ## Layout authoring
18
- - Shape: `{ tag:'div', class:'name', children:[...] }`; `nest` `children`; `text` makes a text node; `html|innerHTML` sets raw HTML; default button type is `button`.
19
- - Watchers: use `[[path]]` inside strings or arrays. Examples:
20
- - Single: `{ class: 'counter-[[count]]' }`
21
- - Specific data: `{ value: ['[[path]]', data] }`
22
- - Multi-source + callback: `{ class: ['[[a]] [[b]]', [dataA, dataB], ([a,b]) => `${a}-${b}`] }`
23
- - Directives: mapped in `modules/layout/directives/core/default-directives.js` (e.g., `bind`, `watch`, `map`, `for`, `route`, `switch`, `useData`, `useState`, `onCreated`, `onDestroyed`, `cache`).
24
-
25
- ### Directives cookbook
26
- - bind
27
- - Text input: `{ tag:'input', type:'text', bind:'form.name' }`
28
- - Checkbox: `{ tag:'input', type:'checkbox', bind:'form.accepted' }`
29
- - Select with options: `{ tag:'select', bind:'form.color', children:[{ map: ['[[colors]]', data, (c)=>({ tag:'option', value:c, text:c })] }] }`
30
- - map (lists)
31
- - `{ tag:'ul', children:[{ map: ['[[items]]', data, (item,i)=>({ tag:'li', text:item.name })] }] }`
32
- - for (range/iterables)
33
- - `{ for: [0, 5, (i)=>({ tag:'span', text:i })] }`
34
- - watch
35
- - `{ class: 'status-[[status]]' }` or `{ text: ['[[user.name]]', data] }`
36
- - Lifecycle
37
- - `{ onCreated:(el,p)=>{/* setup */}, onDestroyed:(el,p)=>{/* cleanup */} }`
38
- - Cache/persist
39
- - Parent `persist:true` keeps children components alive across re-renders; opt-out per child with `persist:false`.
40
-
41
- ## Components
42
- - Extend `Component`; implement `render()`; `Unit._cacheRoot` auto-caches root as `panel`. Use `this.getId('child')` for stable ids.
43
- - State: override `setupStates()` with primitives or `{ state, callBack }`; use `this.state.increment('count')`, `toggle`, etc.
44
- - Data: override `setData()` and set `this.data = new Data({...})` (or `SimpleData`/`Model`). Deep nested updates are supported.
45
- - Lifecycle: `onCreated`, `beforeSetup`, `afterSetup`, `afterLayout`, `beforeDestroy`.
46
- - Persistence: Parent `persist: true` retains child component state across re-renders (child can opt-out with `persist: false`).
47
-
48
- ### Patterns
49
- - Stateless helpers: wrap inline layout with Jot; `Builder.render` auto-wraps non-Units.
50
- - Stable ids: use `this.getId('x')` for elements you need to re-select after updates.
51
- - Child communication: pass data via props and use `useData` to bind; avoid global mutation.
52
-
53
- ## Rendering & routing
54
- - Render anything: `Builder.render(x, container, parent?)`; non-Unit inputs are wrapped with `Jot` automatically.
55
- - Router: `router.data.path` is reactive; `router.navigate(uri, data?, replace?)` to change routes.
56
- - `NavLink` tracks active path using `[value: ['[[path]]', router.data]]`.
57
- - `route` renders all matching routes; `switch` renders the first match. Both support lazy imports (`import` or `() => import(...)`).
58
-
59
- ### Router examples
60
- - Basic navigate: `router.navigate('/users/123')`
61
- - With data payload: `router.navigate('/search', { q: 'term' })`
62
- - Declarative routes via directives:
63
- - `{ switch: [
64
- ['/users/:id', () => import('./components/user.js')],
65
- ['/', Home]
66
- ] }`
67
- - NavLink: prefer `NavLink` for links that need active state.
68
-
69
- ## Data patterns (from wiki)
70
- - Mutations: `data.set('a.b', v)`, `data.push('arr', v)`, `data.splice('arr', i)`, `data.refresh('key')`, `data.revert()`.
71
- - Linking: `data.link(otherData, 'prop')` or `data.link(otherData)` to sync.
72
- - Local storage: `data.setKey('KEY'); data.resume(defaults); data.store()`.
73
-
74
- ### Forms and lists
75
- - Forms: bind inputs with `bind` to `this.data` paths; use `Model` when server-backed persistence is needed.
76
- - Lists: `map` over `[[items]]` and keep keys stable via `this.getId` on child components.
77
-
78
- ## Tips for extending
79
- - New directive: `Directives.add('name', (ele, attrValue, parent) => { /* apply behavior */ })` and reference in layout as `{ name: value }`.
80
- - Prefer atoms/components returning layout objects; avoid direct DOM ops—use `Builder`, `Html`, and directives.
81
- - Wrap quick stateless bits with Jot; `Builder.render` auto-wraps non-Units.
82
-
83
- ### Import/Jot
84
- - Import: lazy-load a component or layout: `{ import: () => import('./components/big.js') }` or in routes as shown above.
85
- - Jot: `Jot(layoutOrFn)` for tiny pieces without full component ceremony.
86
-
87
- ## Build/types
88
- - `npm run build` `dist/` via esbuild (bundle, ESM, sourcemap, minify) and TypeScript declarations in `dist/types`.
89
- - Consumers import from package root; types at `dist/types/base.d.ts`.
90
-
91
- ## Best practices and pitfalls
92
- - Keep layouts pure; avoid mutating layout objects after render—use data/state to trigger updates.
93
- - Prefer `useData`/`useState` directives for local bindings inside layouts rather than manual re-selects.
94
- - When binding arrays/objects, prefer `Data` (deep) over `SimpleData` (shallow) to ensure watchers fire on nested changes.
95
- - Use `persist:true` thoughtfully; leaking long-lived components can retain listeners.
96
-
97
- See also: `documents/base.wiki/*` for deeper explanations and examples (Atoms, Components, Layout, Directives, Data, Router, Migration).
1
+ # Copilot instructions for this repo
2
+
3
+ Purpose: concise, project-specific guidance so AI agents are instantly productive in this codebase.
4
+
5
+ ## Architecture (what to know first)
6
+ - Render UI from plain JS objects ("layouts")—no templates. Parser turns objects into DOM (browser) or HTML strings (server).
7
+ - Public API re-exported by `src/base.js` (e.g., `Builder`, `Component`, `Atom`, `Data`, `SimpleData`, `Model`, `Import`, `NavLink`, `router`, `Html`, `Directives`).
8
+ - Runtime renderer switch: `modules/layout/render/*` via `RenderController.setup()` `BrowserRender` in browser, `ServerRender` otherwise.
9
+ - Component stack: `Unit` `Component`. `Unit` handles lifecycle/context; `Component` adds state (`StateTracker`) and events.
10
+ - Reactive data: `modules/data` provides `Data` (deep), `SimpleData` (shallow), `Model` (server-backed). `WatcherHelper` powers `[[prop.path]]` in strings/arrays.
11
+
12
+ ## CRITICAL: Common Mistakes to Avoid
13
+ 1. **DON'T use templates or JSX** - Base uses plain JavaScript objects for layouts
14
+ 2. **DON'T call `render()` directly** - Components call it internally; you return layout objects from `render()`
15
+ 3. **DON'T mutate props** - Props are read-only; use `this.data` or `this.state` for mutable values
16
+ 4. **DON'T use `this.setState()`** - Use `this.state.set('key', value)` or `this.state.increment('key')`
17
+ 5. **DON'T forget `new` with Components** - Always: `new MyComponent()`, never just `MyComponent()`
18
+ 6. **DON'T use `new` with Atoms** - Always: `Button()`, never `new Button()`
19
+ 7. **DON'T mix data initialization locations** - Use `setData()` for initial setup, not `beforeSetup` or constructor
20
+
21
+ ## Authoring layouts (house rules)
22
+ - Shape: `{ tag: 'div', class: 'name', children: [...] }`. Shorthands: `nest` `children`; `text` creates text node; `html|innerHTML` sets raw HTML; buttons default `type: 'button'`.
23
+ - **Default tag is 'div'** - Omit `tag` for divs: `{ class: 'container' }` renders as `<div class="container"></div>`
24
+ - Events on elements receive `(event, parentComponent)` and are bound on the element: `{ click(e, parent) { parent.doThing(); } }`.
25
+ - **Event names are lowercase** - Use `click`, `mouseover`, `change`, not `onClick` or `CLICK`
26
+ - Watchers become `watch` directives automatically:
27
+ - **PREFERRED**: Current component data: `{ class: 'counter-[[count]]' }` (simplest, use this by default)
28
+ - Specific data source: `{ value: ['[[path]]', data] }` (when you need different data than `this.data`)
29
+ - Multi-source with callback: `{ class: ['[[a]] [[b]]', [dataA, dataB], ([a,b]) => `${a}-${b}`] }` (advanced use)
30
+ - Directives are just attrs mapped in `modules/layout/directives/core/default-directives.js` (e.g., `bind`, `watch`, `map`, `for`, `route`, `switch`, `useState`, `useData`, `onCreated`, `onDestroyed`).
31
+ - **Null/undefined props are ignored** - Use `{ class: condition ? 'active' : null }` to conditionally add attributes
32
+
33
+ ## Common directives (quick cookbook)
34
+ - **bind** (two-way by default, binds to `this.data` in component):
35
+ - Text input: `{ tag: 'input', type: 'text', bind: 'form.name' }` (binds to `this.data.form.name`)
36
+ - Checkbox: `{ tag: 'input', type: 'checkbox', bind: 'form.accepted' }` (binds to boolean)
37
+ - Select + options: `{ tag: 'select', bind: 'form.color', children: [{ map: ['[[colors]]', data, (c) => ({ tag:'option', value:c, text:c })] }] }`
38
+ - **IMPORTANT**: `bind` requires the component to have `this.data` set via `setData()`
39
+ - Custom attribute: `{ tag: 'a', bind: 'href:link.url' }` (binds to href instead of value)
40
+ - With filter: `{ bind: ['count', (v) => Math.round(v)] }` (transform displayed value)
41
+
42
+ - **map** (render lists from arrays, signature: `[watcherString, dataSource, callback]`):
43
+ - Basic: `{ tag: 'ul', children: [{ map: ['[[items]]', data, (item, i) => ({ tag:'li', text:item.name })] }] }`
44
+ - Callback receives: `(item, index)` - use both for keyed lists
45
+ - **IMPORTANT**: The callback must return a layout object, not a string
46
+
47
+ - **Watchers** (one-way binding, auto-updates when data changes):
48
+ - Simple: `{ class: 'status-[[status]]' }` (watches `this.data.status`)
49
+ - Multiple: `{ text: 'User: [[name]] Age: [[age]]' }` (watches multiple props)
50
+ - Deep paths: `{ text: '[[user.profile.name]]' }` (nested object access)
51
+
52
+ - **Lifecycle hooks** (element-level, different from component lifecycle):
53
+ - `{ onCreated: (el, parent) => {/* el is DOM node, parent is component */} }`
54
+ - `{ onDestroyed: (el, parent) => {/* cleanup before removal */} }`
55
+ - **IMPORTANT**: These fire for each element, not once per component
56
+
57
+ - **Cache/persist**:
58
+ - `cache: 'propName'` - stores DOM element in `this.propName` (use for later access)
59
+ - `persist: true` on parent preserves child component instances across re-renders
60
+ - Child can opt-out with `persist: false`
61
+
62
+ ## Components and state/data
63
+ - Extend `Component` and implement `render()` - return layout object (never call `render()` manually)
64
+ - Root element auto-cached as `this.panel` - access after `afterSetup()` lifecycle hook
65
+ - Use `this.getId('child')` for stable DOM IDs across re-renders
66
+
67
+ ### State Management
68
+ - Override `setupStates()` to define reactive state properties:
69
+ ```javascript
70
+ setupStates() {
71
+ return {
72
+ count: 0, // Initial value
73
+ active: { state: false, callBack: (val) => {/* fires on change */} }
74
+ };
75
+ }
76
+ ```
77
+ - Access via `this.state.count` or `this.state.get('count')`
78
+ - Update methods: `this.state.set('count', 5)`, `this.state.increment('count')`, `this.state.toggle('active')`
79
+ - **NEVER** use `this.setState()` - that method doesn't exist
80
+
81
+ ### Data Management
82
+ - Override `setData()` to attach reactive data (runs during component initialization):
83
+ ```javascript
84
+ setData() {
85
+ this.data = new Data({ name: '', items: [] });
86
+ }
87
+ ```
88
+ - **CRITICAL**: Initialize data in `setData()`, NOT in `beforeSetup()` or constructor
89
+ - Use `Data` for deep nested objects, `SimpleData` for flat objects, `Model` for server-backed data
90
+ - Access/modify: `this.data.name = 'test'` or `this.data.set('name', 'test')`
91
+ - Components with `route`/`switch` directives automatically receive `this.route` (a bindable Data object)
92
+
93
+ ### Lifecycle Execution Order
94
+ 1. `onCreated()` - component instance created, props available, NO DOM yet
95
+ 2. `beforeSetup()` - before render, good for computed props
96
+ 3. `render()` - return layout object (called automatically)
97
+ 4. `afterSetup()` - DOM created but not in document, `this.panel` available
98
+ 5. `afterLayout()` - DOM in document, safe for measurements/animations
99
+ 6. `beforeDestroy()` - cleanup before removal
100
+
101
+ ### Persistence
102
+ - Parent `persist: true` keeps child component instances alive during re-renders
103
+ - Child can opt-out: `persist: false`
104
+ - **WARNING**: Data initialized in `beforeSetup` can cause issues with persistence
105
+
106
+ ### Jot (Lightweight Components)
107
+ - Wrap objects/functions as components: `Jot({ render() { return {...}; } })`
108
+ - Non-Unit inputs to `Builder.render()` auto-wrapped with Jot
109
+
110
+ ### Useful state/data ops
111
+ - **State helpers** (only work on keys defined in `setupStates()`):
112
+ - `this.state.set('key', value)` - set any value
113
+ - `this.state.toggle('key')` - flip boolean
114
+ - `this.state.increment('key', amount?)` - add to number (default +1)
115
+ - `this.state.decrement('key', amount?)` - subtract from number (default -1)
116
+ - **DON'T** try to use these on undefined state keys
117
+
118
+ - **Data helpers** (work on `Data` and `SimpleData`):
119
+ - Get: `data.name` or `data.get('nested.path')`
120
+ - Set: `data.name = val` or `data.set('nested.path', val)` or `data.set({ key1: val1, key2: val2 })`
121
+ - Arrays: `data.push('arr', item)`, `data.splice('arr', idx, count)`, `data.unshift()`, `data.shift()`, `data.pop()`
122
+ - Refresh: `data.refresh('key')` - trigger watchers without changing value
123
+ - Revert: `data.revert()` - undo changes since last commit (Data only)
124
+ - Delete: `data.delete('key')` - remove property
125
+
126
+ - **Linking data sources** (two-way sync):
127
+ - Full link: `data.link(otherData)` - sync all properties
128
+ - Single prop: `data.link(otherData, 'propName')` - sync one property
129
+
130
+ - **Local storage persistence**:
131
+ ```javascript
132
+ setData() {
133
+ this.data = new Data({ count: 0 });
134
+ this.data.setKey('MY_STORAGE_KEY');
135
+ this.data.resume({ count: 0 }); // Load or use defaults
136
+ }
137
+
138
+ // Save when needed
139
+ saveData() {
140
+ this.data.store();
141
+ }
142
+ ```
143
+
144
+ ## Rendering and routing
145
+ - Render anything (function/layout/Unit/Component): `Builder.render(x, container, parent?)`. Non-Unit inputs are wrapped in a `Jot` component.
146
+ - Router: `router.data.path` is reactive; navigate via `router.navigate(uri, data?, replace?)`. `NavLink` uses `[value: ['[[path]]', router.data]]` to track active path.
147
+ - Routes via directives: `route` renders all matches; `switch` renders the first match. Both can lazy-load components via `import`.
148
+ - Lazy imports: use `Import` or dynamic `import()` in route/switch children to defer loading.
149
+ - NavLink patterns: `{ tag:'a', route:'/users', children:['Users'], useData: router.data }` or use `NavLink` which watches `router.data.path` for active state.
150
+
151
+ ### Router Setup (do this FIRST)
152
+ ```javascript
153
+ import { router } from '@base-framework/base';
154
+ router.setup('/base-url/', 'App Title');
155
+ ```
156
+
157
+ ### Route Directive (renders ALL matching routes)
158
+ ```javascript
159
+ {
160
+ route: [
161
+ { uri: '/users', component: UsersList },
162
+ { uri: '/users/:id', component: UserDetail },
163
+ { uri: '/users/:id/edit', component: UserEdit }
164
+ ]
165
+ }
166
+ // All matching routes render simultaneously
167
+ ```
168
+
169
+ ### Switch Directive (renders FIRST match only)
170
+ ```javascript
171
+ {
172
+ switch: [
173
+ { uri: '/login', component: Login },
174
+ { uri: '/dashboard', component: Dashboard },
175
+ { component: NotFound } // No uri = default/fallback route
176
+ ]
177
+ }
178
+ // Only one route renders
179
+ ```
180
+
181
+ ### Route Patterns
182
+ - **Exact match**: `/users` - matches only `/users`
183
+ - **Wildcard**: `/users*` - matches `/users`, `/users/123`, `/users/123/edit`
184
+ - **Required param**: `/users/:id` - matches `/users/123`, extracts `id: '123'`
185
+ - **Optional param**: `/users/:id?` - matches `/users` and `/users/123`
186
+ - **Multi-params**: `/users/:id/posts/:postId?*` - combines patterns
187
+
188
+ ### Accessing Route Data in Components
189
+ ```javascript
190
+ class UserDetail extends Component {
191
+ render() {
192
+ // this.route is automatically injected by route/switch directives
193
+ const userId = this.route.id; // From /users/:id
194
+ return { text: `User ID: [[id]]` }; // Watches this.route.id
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### Lazy Loading Routes
200
+ ```javascript
201
+ {
202
+ switch: [
203
+ { uri: '/heavy', import: () => import('./components/heavy.js') }
204
+ ]
205
+ }
206
+ // Component loads only when route matches
207
+ ```
208
+
209
+ ### NavLink Component
210
+ ```javascript
211
+ import { NavLink } from '@base-framework/base';
212
+
213
+ new NavLink({
214
+ href: '/users',
215
+ text: 'Users',
216
+ exact: true, // false = matches /users*
217
+ activeClass: 'active' // Class added when route matches
218
+ })
219
+ ```
220
+
221
+ ## Build and types
222
+ - Build to `dist/` with esbuild + TypeScript declarations: `npm run build` (bundles `src/base.js`, ESM, minified, sourcemaps; emits `dist/types/*.d.ts`).
223
+ - Consumers import from package root; `exports` maps ESM/CJS to `dist/base.js`.
224
+
225
+ ## Complete Working Examples
226
+
227
+ ### Example 1: Simple Counter Component (State + Watchers)
228
+ ```javascript
229
+ import { Component, Atom } from '@base-framework/base';
230
+
231
+ const Button = Atom((props, children) => ({
232
+ tag: 'button',
233
+ type: 'button',
234
+ ...props,
235
+ children
236
+ }));
237
+
238
+ class Counter extends Component {
239
+ setupStates() {
240
+ return {
241
+ count: 0
242
+ };
243
+ }
244
+
245
+ render() {
246
+ return {
247
+ class: 'counter',
248
+ children: [
249
+ { tag: 'h2', text: 'Count: [[count]]' }, // Watcher on this.state
250
+ Button({
251
+ click: () => this.state.increment('count'),
252
+ }, 'Increment'),
253
+ Button({
254
+ click: () => this.state.decrement('count'),
255
+ }, 'Decrement')
256
+ ]
257
+ };
258
+ }
259
+ }
260
+
261
+ // Usage
262
+ import { Builder } from '@base-framework/base';
263
+ Builder.render(new Counter(), document.body);
264
+ ```
265
+
266
+ ### Example 2: Form with Data Binding
267
+ ```javascript
268
+ import { Component, Data } from '@base-framework/base';
269
+
270
+ class UserForm extends Component {
271
+ setData() {
272
+ this.data = new Data({
273
+ form: {
274
+ name: '',
275
+ email: '',
276
+ role: 'user',
277
+ newsletter: false
278
+ }
279
+ });
280
+ }
281
+
282
+ render() {
283
+ return {
284
+ class: 'user-form',
285
+ children: [
286
+ {
287
+ tag: 'input',
288
+ type: 'text',
289
+ placeholder: 'Name',
290
+ bind: 'form.name' // Two-way binding to this.data.form.name
291
+ },
292
+ {
293
+ tag: 'input',
294
+ type: 'email',
295
+ placeholder: 'Email',
296
+ bind: 'form.email'
297
+ },
298
+ {
299
+ tag: 'select',
300
+ bind: 'form.role',
301
+ children: [
302
+ { tag: 'option', value: 'user', text: 'User' },
303
+ { tag: 'option', value: 'admin', text: 'Admin' }
304
+ ]
305
+ },
306
+ {
307
+ tag: 'label',
308
+ children: [
309
+ { tag: 'input', type: 'checkbox', bind: 'form.newsletter' },
310
+ { tag: 'span', text: 'Subscribe to newsletter' }
311
+ ]
312
+ },
313
+ {
314
+ tag: 'button',
315
+ type: 'button',
316
+ text: 'Submit',
317
+ click: () => this.handleSubmit()
318
+ },
319
+ // Preview with watchers
320
+ { tag: 'pre', text: 'Name: [[form.name]]\nEmail: [[form.email]]' }
321
+ ]
322
+ };
323
+ }
324
+
325
+ handleSubmit() {
326
+ console.log('Form data:', this.data.form);
327
+ }
328
+ }
329
+ ```
330
+
331
+ ### Example 3: List with Map Directive
332
+ ```javascript
333
+ import { Component, Data } from '@base-framework/base';
334
+
335
+ class TodoList extends Component {
336
+ setData() {
337
+ this.data = new Data({
338
+ newTodo: '',
339
+ todos: [
340
+ { id: 1, text: 'Learn Base', done: false },
341
+ { id: 2, text: 'Build app', done: false }
342
+ ]
343
+ });
344
+ }
345
+
346
+ render() {
347
+ return {
348
+ class: 'todo-list',
349
+ children: [
350
+ {
351
+ tag: 'input',
352
+ type: 'text',
353
+ bind: 'newTodo',
354
+ placeholder: 'New todo...'
355
+ },
356
+ {
357
+ tag: 'button',
358
+ type: 'button',
359
+ text: 'Add',
360
+ click: () => this.addTodo()
361
+ },
362
+ {
363
+ tag: 'ul',
364
+ children: [{
365
+ map: ['[[todos]]', this.data, (todo, index) => ({
366
+ tag: 'li',
367
+ class: todo.done ? 'done' : '',
368
+ children: [
369
+ {
370
+ tag: 'input',
371
+ type: 'checkbox',
372
+ checked: todo.done,
373
+ change: (e) => {
374
+ this.data.todos[index].done = e.target.checked;
375
+ this.data.refresh('todos');
376
+ }
377
+ },
378
+ { tag: 'span', text: todo.text },
379
+ {
380
+ tag: 'button',
381
+ type: 'button',
382
+ text: '×',
383
+ click: () => this.removeTodo(index)
384
+ }
385
+ ]
386
+ })]
387
+ }]
388
+ }
389
+ ]
390
+ };
391
+ }
392
+
393
+ addTodo() {
394
+ if (!this.data.newTodo.trim()) return;
395
+ this.data.push('todos', {
396
+ id: Date.now(),
397
+ text: this.data.newTodo,
398
+ done: false
399
+ });
400
+ this.data.newTodo = '';
401
+ }
402
+
403
+ removeTodo(index) {
404
+ this.data.splice('todos', index, 1);
405
+ }
406
+ }
407
+ ```
408
+
409
+ ### Example 4: Routing App
410
+ ```javascript
411
+ import { Component, router, NavLink } from '@base-framework/base';
412
+
413
+ // Setup router FIRST
414
+ router.setup('/app/', 'My App');
415
+
416
+ // Page components
417
+ class HomePage extends Component {
418
+ render() {
419
+ return { class: 'home', children: [{ tag: 'h1', text: 'Home' }] };
420
+ }
421
+ }
422
+
423
+ class UserDetail extends Component {
424
+ render() {
425
+ // this.route is automatically available from router
426
+ return {
427
+ class: 'user-detail',
428
+ children: [
429
+ { tag: 'h1', text: 'User Details' },
430
+ { tag: 'p', text: 'User ID: [[id]]' } // Watches this.route.id
431
+ ]
432
+ };
433
+ }
434
+ }
435
+
436
+ class NotFound extends Component {
437
+ render() {
438
+ return { class: 'not-found', children: [{ tag: 'h1', text: '404' }] };
439
+ }
440
+ }
441
+
442
+ // Main app with navigation
443
+ class App extends Component {
444
+ render() {
445
+ return {
446
+ class: 'app',
447
+ children: [
448
+ {
449
+ tag: 'nav',
450
+ children: [
451
+ new NavLink({ href: '/', text: 'Home', exact: true }),
452
+ new NavLink({ href: '/users', text: 'Users' }),
453
+ new NavLink({ href: '/about', text: 'About' })
454
+ ]
455
+ },
456
+ {
457
+ tag: 'main',
458
+ // Use switch for mutually exclusive routes
459
+ switch: [
460
+ { uri: '/', component: HomePage },
461
+ { uri: '/users/:id', component: UserDetail },
462
+ { uri: '/about', import: () => import('./about.js') },
463
+ { component: NotFound } // Fallback route
464
+ ]
465
+ }
466
+ ]
467
+ };
468
+ }
469
+ }
470
+
471
+ // Render app
472
+ import { Builder } from '@base-framework/base';
473
+ Builder.render(new App(), document.body);
474
+ ```
475
+
476
+ ## Pointers to examples
477
+ - Rendering: `modules/layout/render/{browser-render,server-render}.js`; Parser: `modules/layout/element/parser.js` + `modules/layout/watcher-helper.js`.
478
+ - Components: `modules/component/{unit.js,component.js}`; Directives registry: `modules/layout/directives/core/default-directives.js`.
479
+ - Data: `modules/data/types/**`; Router: `modules/router/router.js`, `modules/router/nav-link.js`.
480
+
481
+ Questions or gaps? Open an issue or add comments here with file pointers so we can refine these rules.