apprun 3.37.3 → 3.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,166 +1,602 @@
1
- ---
2
- mode: agent
3
- ---
4
1
  # AppRun Component Creation Guide
5
2
 
6
- ## Core Concepts
3
+ ## Core Architecture Patterns
7
4
 
8
- AppRun follows the State-View-Update architecture pattern with TypeScript support for type-safe component development.
5
+ AppRun follows the State-View-Update architecture with TypeScript support. Choose the appropriate component pattern based on your needs:
9
6
 
10
- ## Step 1: Define State Interface
7
+ ### **Stateful Class Components (Self-Contained)**
8
+ Use for components that manage their own state internally, handle side effects, and coordinate with parents via global events. Modern pattern using `mounted` lifecycle.
11
9
 
12
- ### **State Management**
13
- 1. Define typed state interface with all required properties
14
- 2. Initialize with sensible defaults including loading/error states
15
- 3. Plan for immutable updates using spread operator
16
- 4. Include states for: loading, error, data, UI state
10
+ ### **Container Class Components (Legacy)**
11
+ Use for components that manage state, handle side effects, and coordinate data flow using traditional state initialization.
17
12
 
13
+ ### **Functional Components (Presentation/Dumb Components)**
14
+ Use for components that only render UI based on props with minimal logic.
15
+
16
+ ## Component Patterns
17
+
18
+ ### **Pattern 1: Stateful Class Component (Modern - Recommended)**
19
+
20
+ Use `mounted` lifecycle to receive props and convert to initial state. Functions defined at module level for better testing.
21
+
22
+ #### **Module-Level Functions (separate file)**
18
23
  ```typescript
19
- interface ComponentState {
20
- data: DataType[];
21
- currentIndex: number;
24
+ // component-functions.ts
25
+ export interface ComponentState {
26
+ mode: 'create' | 'edit' | 'delete';
27
+ formData: FormType;
22
28
  loading: boolean;
23
29
  error: string | null;
30
+ successMessage: string | null;
24
31
  }
25
- ```
26
32
 
27
- ## Step 2: Create Component Structure
33
+ export interface ComponentProps {
34
+ mode?: string;
35
+ initialData?: FormType;
36
+ }
28
37
 
29
- ### **Component-Based Architecture**
30
- - Extend `Component<StateType>` class
31
- - Initialize state with async function for API data loading
32
- - Plan MVU (Model-View-Update) separation
33
- - Use functional design principles
38
+ export const initializeState = (props: ComponentProps): ComponentState => ({
39
+ mode: props.mode || 'create',
40
+ formData: props.initialData || getDefaultFormData(),
41
+ loading: false,
42
+ error: null,
43
+ successMessage: null
44
+ });
45
+
46
+ export const saveData = async function* (state: ComponentState): AsyncGenerator<ComponentState> {
47
+ yield { ...state, loading: true, error: null };
48
+ try {
49
+ await performSave(state.formData);
50
+ yield {
51
+ ...state,
52
+ loading: false,
53
+ successMessage: 'Data saved successfully!'
54
+ };
55
+ setTimeout(() => app.run('data-saved'), 2000);
56
+ } catch (error) {
57
+ yield { ...state, loading: false, error: error.message };
58
+ }
59
+ };
60
+
61
+ export const closeModal = (): void => {
62
+ app.run('close-modal');
63
+ };
64
+ ```
34
65
 
66
+ #### **Component Structure**
35
67
  ```typescript
68
+ // component.tsx
69
+ import { initializeState, saveData, closeModal, type ComponentState, type ComponentProps } from './component-functions';
70
+
36
71
  export default class MyComponent extends Component<ComponentState> {
37
- state = async () => {
38
- const data = await getData();
39
- return {
40
- data,
41
- currentIndex: 0,
42
- loading: false,
43
- error: null
44
- };
45
- }
72
+ declare props: Readonly<ComponentProps>;
73
+ mounted = (props: ComponentProps): ComponentState => initializeState(props);
74
+
75
+ view = (state: ComponentState) => {
76
+ // Guard clauses
77
+ if (state.successMessage) {
78
+ return (
79
+ <div className="modal-backdrop" $onclick={[closeModal]}>
80
+ <div className="success-message">
81
+ <p>{state.successMessage}</p>
82
+ </div>
83
+ </div>
84
+ );
85
+ }
86
+
87
+ return (
88
+ <div className="modal-backdrop" $onclick={[closeModal]}>
89
+ <form>
90
+ <input
91
+ value={state.formData.name}
92
+ $bind="formData.name"
93
+ disabled={state.loading}
94
+ />
95
+ <button
96
+ $onclick={[saveData]}
97
+ disabled={state.loading}
98
+ >
99
+ {state.loading ? 'Saving...' : 'Save'}
100
+ </button>
101
+ <button $onclick={[closeModal]}>Cancel</button>
102
+ </form>
103
+ </div>
104
+ );
105
+ };
46
106
  }
47
107
  ```
48
108
 
49
- ## Step 3: Design View Function
109
+ ### **Pattern 2: Legacy Container Component**
50
110
 
51
- ### **View Structure**
111
+ Traditional pattern with state initialization function.
112
+
113
+ #### **Component Structure**
52
114
  ```typescript
53
- view = (state: State) => {
54
- // Guard clauses first (early returns)
55
- if (state.loading) return loadingView;
56
- if (state.error) return errorView;
57
- if (state.empty) return emptyView;
58
-
59
- // Main content last
60
- return mainView;
115
+ export default class MyComponent extends Component<ComponentState> {
116
+ state = async (): Promise<ComponentState> => {
117
+ // Initial state with async data loading
118
+ try {
119
+ const data = await loadData();
120
+ return {
121
+ data,
122
+ loading: false,
123
+ error: null,
124
+ selectedItem: null,
125
+ isEditing: false
126
+ };
127
+ } catch (error) {
128
+ return {
129
+ data: [],
130
+ loading: false,
131
+ error: error.message,
132
+ selectedItem: null,
133
+ isEditing: false
134
+ };
135
+ }
136
+ };
137
+
138
+ view = (state: ComponentState) => {
139
+ // Guard clauses for early returns
140
+ if (state.loading) return <div>Loading...</div>;
141
+ if (state.error) return <div>Error: {state.error}</div>;
142
+ if (state.data.length === 0) return <div>No data</div>;
143
+
144
+ // Main content
145
+ return (
146
+ <div>
147
+ {/* Render main UI */}
148
+ <PresentationComponent
149
+ data={state.data}
150
+ selectedItem={state.selectedItem}
151
+ onSelect={(item) => this.run('select-item', item)}
152
+ />
153
+ </div>
154
+ );
155
+ };
156
+
157
+ update = {
158
+ 'select-item': (state: ComponentState, item: DataType): ComponentState => ({
159
+ ...state,
160
+ selectedItem: item
161
+ }),
162
+
163
+ 'async-action': async function* (state: ComponentState): AsyncGenerator<ComponentState> {
164
+ try {
165
+ yield { ...state, loading: true, error: null };
166
+ const result = await performAsyncAction();
167
+ yield { ...state, loading: false, data: result };
168
+ } catch (error) {
169
+ yield { ...state, loading: false, error: error.message };
170
+ }
171
+ },
172
+
173
+ 'side-effect-action': (state: ComponentState): void => {
174
+ // No return value = no re-render
175
+ window.location.href = '/new-page';
176
+ }
177
+ };
61
178
  }
62
179
  ```
63
180
 
64
- ### **Multi-State Rendering**
65
- - State-driven conditional rendering with guard clauses
66
- - Early returns for cleaner code flow
67
- - Dedicated views for each state (loading, error, empty, main)
181
+ ### **Pattern 3: Functional Presentation Component**
68
182
 
69
- ### **Accessibility & Responsive Design**
70
- 1. Use semantic HTML elements
71
- 2. Add `title` attributes for tooltips
72
- 3. Proper button usage over div+click
73
- 4. Use conditional rendering based on data size
74
- 5. Implement adaptive visibility logic
75
- 6. Mobile-first button sizing
183
+ ```typescript
184
+ interface ComponentProps {
185
+ data: DataType[];
186
+ selectedItem: DataType | null;
187
+ loading?: boolean;
188
+ error?: string | null;
189
+ // Event handlers
190
+ onSelect?: (item: DataType) => void;
191
+ onDelete?: (id: string) => void;
192
+ }
76
193
 
77
- ## Step 4: Implement Event Handling
194
+ export default function MyPresentationComponent(props: ComponentProps) {
195
+ const {
196
+ data,
197
+ selectedItem,
198
+ loading = false,
199
+ error = null,
200
+ onSelect,
201
+ onDelete
202
+ } = props;
203
+
204
+ // Guard clauses
205
+ if (loading) return <div>Loading...</div>;
206
+ if (error) return <div>Error: {error}</div>;
207
+ if (data.length === 0) return <div>No data</div>;
208
+
209
+ return (
210
+ <div>
211
+ {data.map(item => (
212
+ <div
213
+ key={item.id}
214
+ $onclick={['select-item', item]}
215
+ className={selectedItem?.id === item.id ? 'selected' : ''}
216
+ >
217
+ {item.name}
218
+ <button $onclick={['delete-item', item.id]}>Delete</button>
219
+ </div>
220
+ ))}
221
+ </div>
222
+ );
223
+ }
224
+ ```
225
+
226
+ ## Event Handling Rules
78
227
 
79
- ### **$onclick Usage Rules**
228
+ ### **✅ DO: Use $on Directives for State Updates**
80
229
 
81
- #### Action Pattern
82
230
  ```typescript
83
- // String actions for simple state updates
84
- $onclick='action-name'
231
+ // String actions (handled in parent's update object)
232
+ $onclick="action-name"
233
+ $oninput="update-field"
85
234
 
86
- // Tuple actions for data passing
235
+ // Tuple actions (pass data to handler)
87
236
  $onclick={['action-name', data]}
237
+ $oninput={['update-field', 'fieldName']}
238
+ $onchange={['update-dropdown', 'provider']}
239
+
240
+ // Direct function references (modern pattern - recommended)
241
+ $onclick={[saveFunction]}
242
+ $onclick={[deleteFunction]}
243
+ $onclick={[closeModal]}
88
244
  ```
89
245
 
90
- #### Event Handling Best Practices
91
- 1. Use `$onclick` for all click events
92
- 2. Simple actions: string literals
93
- 3. Data passing: tuple format `[action, data]`
94
- 4. Conditional actions: ternary in JSX
246
+ ### **✅ DO: Use $bind for Two-Way Data Binding**
95
247
 
96
- #### Action Naming Convention
97
- - Use kebab-case: `'prev-item'`, `'next-item'`
98
- - Be descriptive: `'select-item'`, `'enter-item'`
99
- - Match business logic, not UI elements
248
+ ```typescript
249
+ // Automatic form field binding (modern pattern)
250
+ <input
251
+ value={state.formData.name}
252
+ $bind="formData.name"
253
+ />
254
+
255
+ <textarea
256
+ value={state.formData.description}
257
+ $bind="formData.description"
258
+ />
259
+
260
+ <select
261
+ value={state.formData.provider}
262
+ $bind="formData.provider"
263
+ >
264
+ <option value="openai">OpenAI</option>
265
+ <option value="anthropic">Anthropic</option>
266
+ </select>
267
+ ```
100
268
 
101
- ## Step 5: Create Update Functions
269
+ ### **✅ DO: Use Regular Properties for Non-State Actions**
102
270
 
103
- ### **Update Function Signatures**
104
271
  ```typescript
105
- update = {
106
- // State update (returns new state)
107
- 'action-name': (state: State, payload?: any): State => ({ ...state, changes }),
272
+ // DOM manipulation only
273
+ onclick={(e) => e.stopPropagation()}
274
+ onmouseenter={(e) => e.target.focus()}
108
275
 
109
- // Side effect (no return, no re-render)
110
- 'action-name': (state: State, payload?: any): void => { /* side effects */ },
276
+ // Side effects (functions that call app.run() or navigate)
277
+ onclick={handleSideEffect} // function calls app.run() internally
278
+ onclick={() => window.open('/new-page')}
111
279
 
112
- // Async operations (generators for progressive updates)
113
- 'action-name': async function* (state: State): AsyncGenerator<State> { /* async logic */ }
114
- }
280
+ // Event prevention
281
+ onsubmit={(e) => e.preventDefault()}
115
282
  ```
116
283
 
117
- ### **State Management Pattern**
118
- - Use immutable state updates with spread operator
119
- - Single source of truth for component state
120
- - Handle all state transitions explicitly
284
+ ### **❌ DON'T: Mix Patterns Incorrectly**
285
+
286
+ ```typescript
287
+ // Don't use $on with app.run() calls
288
+ $onclick={() => app.run('action')}
289
+ $onclick={(e) => this.run('action', e.target.value)}
290
+
291
+ // ❌ Don't use regular props for state updates
292
+ onclick="action-name" // Use $onclick for state updates
293
+
294
+ // ❌ Don't use arrow functions for simple state updates
295
+ $onclick={(e) => ({ ...state, field: e.target.value })}
121
296
 
122
- ## Step 6: Handle Data Loading
297
+ // Don't use manual form handlers when $bind is available
298
+ $oninput={(e) => updateField('name', e)} // Use $bind="formData.name" instead
299
+ ```
123
300
 
124
- ### **Data Loading Patterns**
125
- - Use async state function for initial data loading
126
- - Async generator pattern for progressive updates during user interactions
127
- - Graceful handling of loading transitions
301
+ ## Update Function Patterns
128
302
 
303
+ ### **Module-Level State Update Functions (Modern - Recommended)**
129
304
  ```typescript
130
- // Progressive updates for user interactions
131
- update = {
132
- 'refresh-data': async function* (state: State): AsyncGenerator<State> {
133
- try {
134
- yield { ...state, loading: true, error: null };
135
- const data = await fetchData();
136
- yield { ...state, data, loading: false, error: null };
137
- } catch (error: any) {
138
- yield { ...state, loading: false, error: error.message };
305
+ // Export functions for direct references in $on directives
306
+ export const saveData = async function* (state: State): AsyncGenerator<State> {
307
+ yield { ...state, loading: true, error: null };
308
+ try {
309
+ await performSave(state.formData);
310
+ yield {
311
+ ...state,
312
+ loading: false,
313
+ successMessage: 'Data saved successfully!'
314
+ };
315
+ // Global event for parent coordination
316
+ setTimeout(() => app.run('data-saved'), 2000);
317
+ } catch (error) {
318
+ yield { ...state, loading: false, error: error.message };
319
+ }
320
+ };
321
+
322
+ export const deleteData = async function* (state: State): AsyncGenerator<State> {
323
+ yield { ...state, loading: true, error: null };
324
+ try {
325
+ await performDelete(state.formData.id);
326
+ yield {
327
+ ...state,
328
+ loading: false,
329
+ successMessage: 'Data deleted successfully!'
330
+ };
331
+ setTimeout(() => app.run('data-deleted'), 2000);
332
+ } catch (error) {
333
+ yield { ...state, loading: false, error: error.message };
334
+ }
335
+ };
336
+
337
+ export const closeModal = (): void => {
338
+ app.run('close-modal'); // Global event for parent
339
+ };
340
+ ```
341
+
342
+ ### **Legacy Update Functions (Traditional Pattern)**
343
+ ```typescript
344
+ // Synchronous state update
345
+ 'action-name': (state: State, payload?: any): State => ({
346
+ ...state,
347
+ // immutable updates
348
+ field: newValue
349
+ }),
350
+
351
+ // Async progressive updates
352
+ 'async-action': async function* (state: State, payload?: any): AsyncGenerator<State> {
353
+ try {
354
+ yield { ...state, loading: true, error: null };
355
+ const result = await asyncOperation(payload);
356
+ yield { ...state, loading: false, data: result };
357
+ } catch (error) {
358
+ yield { ...state, loading: false, error: error.message };
359
+ }
360
+ },
361
+
362
+ // Side effect (no re-render)
363
+ 'navigate-action': (state: State, path: string): void => {
364
+ window.location.href = path;
365
+ },
366
+
367
+ // Form field updates (common pattern)
368
+ 'update-form-field': (state: State, field: string, event: Event): State => {
369
+ const target = event.target as HTMLInputElement;
370
+ const value = target.type === 'number' ? parseFloat(target.value) || 0 : target.value;
371
+
372
+ return {
373
+ ...state,
374
+ formData: {
375
+ ...state.formData,
376
+ [field]: value
139
377
  }
378
+ };
379
+ }
380
+ ```
381
+
382
+ ## Component Composition Patterns
383
+
384
+ ### **Parent-Child with Global Events (Modern - Recommended)**
385
+ ```typescript
386
+ // Parent Component (simplified state)
387
+ export default class WorldComponent extends Component<WorldState> {
388
+ view = (state: WorldState) => (
389
+ <div>
390
+ {/* Core world UI */}
391
+ <div className="world-content">
392
+ {state.agents.map(agent => (
393
+ <div key={agent.id} $onclick={['open-agent-edit', agent]}>
394
+ {agent.name}
395
+ </div>
396
+ ))}
397
+ </div>
398
+
399
+ {/* Conditional modal rendering */}
400
+ {state.showAgentEdit &&
401
+ <AgentEdit
402
+ agent={state.selectedAgent}
403
+ mode={state.editMode}
404
+ worldName={state.worldName}
405
+ />
406
+ }
407
+ </div>
408
+ );
409
+
410
+ update = {
411
+ 'open-agent-edit': (state, agent) => ({
412
+ ...state,
413
+ showAgentEdit: true,
414
+ editMode: 'edit',
415
+ selectedAgent: agent
416
+ }),
417
+
418
+ 'close-agent-edit': (state) => ({
419
+ ...state,
420
+ showAgentEdit: false
421
+ }),
422
+
423
+ // Global events from child components
424
+ 'agent-saved': async (state) => {
425
+ const agents = await getAgents(state.worldName);
426
+ return { ...state, agents, showAgentEdit: false };
427
+ },
428
+
429
+ 'agent-deleted': async (state) => {
430
+ const agents = await getAgents(state.worldName);
431
+ return { ...state, agents, showAgentEdit: false };
432
+ }
433
+ };
434
+ }
435
+
436
+ // Self-contained child component
437
+ export default class AgentEdit extends Component<AgentEditState> {
438
+ mounted = (props) => initializeState(props);
439
+
440
+ view = (state) => (
441
+ <div className="modal-backdrop" $onclick={[closeModal]}>
442
+ <form>
443
+ <input $bind="formData.name" />
444
+ <button $onclick={[saveAgent]}>Save</button>
445
+ </form>
446
+ </div>
447
+ );
448
+ }
449
+ ```
450
+
451
+ ### **Container + Presentation Pattern (Legacy)**
452
+ ```typescript
453
+ // Container Component (manages state)
454
+ export default class WorldComponent extends Component<WorldState> {
455
+ view = (state: WorldState) => (
456
+ <div>
457
+ <WorldChat
458
+ messages={state.messages}
459
+ userInput={state.userInput}
460
+ onSendMessage={(text) => this.run('send-message', text)}
461
+ />
462
+ <WorldSettings
463
+ world={state.world}
464
+ selectedAgent={state.selectedAgent}
465
+ onEditAgent={(agent) => this.run('edit-agent', agent)}
466
+ />
467
+ </div>
468
+ );
469
+ }
470
+
471
+ // Presentation Components (stateless)
472
+ function WorldChat(props: WorldChatProps) { /* render only */ }
473
+ function WorldSettings(props: WorldSettingsProps) { /* render only */ }
474
+ ```
475
+
476
+ ## State Management Rules
477
+
478
+ ### **✅ DO: Immutable Updates**
479
+ ```typescript
480
+ // Spread operator for updates
481
+ { ...state, field: newValue }
482
+
483
+ // Nested object updates
484
+ {
485
+ ...state,
486
+ nested: {
487
+ ...state.nested,
488
+ field: newValue
140
489
  }
141
490
  }
491
+
492
+ // Array updates
493
+ {
494
+ ...state,
495
+ items: [...state.items, newItem],
496
+ filteredItems: state.items.filter(item => item.id !== deletedId)
497
+ }
142
498
  ```
143
499
 
144
- ## Step 7: Error Handling
500
+ ### **✅ DO: Defensive Programming**
501
+ ```typescript
502
+ // Safe array operations
503
+ messages: state.messages || []
504
+ count: (state.items || []).length
145
505
 
146
- ### **Error Handling Strategy**
147
- 1. Wrap async operations in try-catch
148
- 2. Yield error states in generators
149
- 3. Provide user-friendly error messages
150
- 4. Include retry mechanisms
151
- 5. Defensive programming with null checks
506
+ // Safe object access
507
+ selectedItem: state.selectedItem?.name || 'None'
152
508
 
153
- ## Step 8: Navigation & Side Effects
509
+ // Default props in functional components
510
+ const { data = [], loading = false } = props;
511
+ ```
154
512
 
155
- ### **Navigation & Side-Effect Actions**
156
- 1. Use void actions for navigation (no re-render)
157
- 2. Direct `window.location.href` for page changes
158
- 3. Use anchor tags `<a href>` for static links
159
- 4. Actions with void return indicate no re-render needed
513
+ ### **❌ DON'T: Mutate State**
514
+ ```typescript
515
+ // Don't mutate existing state
516
+ state.field = newValue;
517
+ state.items.push(newItem);
518
+ state.nested.field = value;
519
+
520
+ // ❌ Don't use non-immutable array methods
521
+ state.items.sort();
522
+ state.items.reverse();
523
+ ```
524
+
525
+ ## Error Handling Patterns
160
526
 
527
+ ### **Component Error States**
161
528
  ```typescript
162
- 'navigate-action': (state: State, data: any): void => {
163
- window.location.href = '/path/' + data.id;
529
+ // State interface includes error
530
+ interface State {
531
+ data: DataType[];
532
+ loading: boolean;
533
+ error: string | null;
534
+ }
535
+
536
+ // View handles error states
537
+ view = (state: State) => {
538
+ if (state.error) {
539
+ return (
540
+ <div className="error-state">
541
+ <p>Error: {state.error}</p>
542
+ <button $onclick="retry-action">Retry</button>
543
+ </div>
544
+ );
545
+ }
546
+ // ... rest of view
547
+ };
548
+
549
+ // Update functions handle errors
550
+ 'load-data': async function* (state: State): AsyncGenerator<State> {
551
+ try {
552
+ yield { ...state, loading: true, error: null };
553
+ const data = await fetchData();
554
+ yield { ...state, loading: false, data, error: null };
555
+ } catch (error: any) {
556
+ yield { ...state, loading: false, error: error.message || 'Unknown error' };
557
+ }
164
558
  }
165
559
  ```
166
560
 
561
+ ## Best Practices Summary
562
+
563
+ ### **Component Design (Modern Patterns)**
564
+ - **Prefer stateful class components** with `mounted` lifecycle for self-contained components
565
+ - **Use module-level functions** for state updates to enable easy testing
566
+ - **Use $bind for form fields** instead of manual event handlers
567
+ - **Use direct function references** in $on directives: `$onclick={[saveFunction]}`
568
+ - **Use global events** for parent-child coordination
569
+ - Implement guard clauses for loading/error/empty states
570
+ - Follow single responsibility principle
571
+
572
+ ### **Event Handling (Updated Rules)**
573
+ - **$bind for two-way data binding**: `$bind="formData.fieldName"`
574
+ - **$on directives with direct function references**: `$onclick={[functionRef]}`
575
+ - **String/tuple actions for legacy patterns**: `$onclick="action-name"` or `$onclick={['action', data]}`
576
+ - **Regular properties for DOM manipulation**: `onclick={(e) => e.stopPropagation()}`
577
+ - **Global events for coordination**: `app.run('component-saved')`
578
+
579
+ ### **State Management (Enhanced)**
580
+ - **Module-level functions** return new state or use async generators
581
+ - **$bind automatically handles** form field updates
582
+ - **Global events coordinate** between parent and child components
583
+ - Always use immutable updates with spread operator
584
+ - Include loading, error, and success message states
585
+ - Use async generators for progressive updates
586
+ - Implement defensive programming with null checks
587
+
588
+ ### **Architecture Patterns**
589
+ - **Self-contained components** manage their own form state using `mounted`
590
+ - **Parent components** use simple boolean flags for conditional rendering
591
+ - **Module-level functions** enable easy unit testing
592
+ - **Global events** provide loose coupling between components
593
+ - **Success messages** with auto-close functionality
594
+ - **Modal patterns** with backdrop click to close
595
+
596
+ ### **Type Safety**
597
+ - Define comprehensive state interfaces with success message states
598
+ - Use proper TypeScript types for all module-level functions
599
+ - Type component props correctly for mounted pattern
600
+ - Provide default values for optional props
601
+
602
+ This guide covers modern AppRun patterns with emphasis on stateful components, module-level functions, $bind for forms, and global event coordination.