apprun 3.37.0 → 3.37.2

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/CHANGELOG.md CHANGED
@@ -1,17 +1,34 @@
1
1
  # Change Log
2
2
 
3
- ## Releases
3
+ ## V3.37.2
4
+
5
+ * Include rule / prompt files for AI Coding Agent
6
+ * Improved type definitions for better TypeScript support
7
+ - Support async function and async generator in `Action<T>` that is used in `Update<T, E>`
8
+ - Added `State` type to allow the initial state to be a function or an async function
9
+ * Fixed routing basePath initialization issue - not to set basePath to location.pathname by default
10
+
11
+ ## V3.37.1
12
+
13
+ * Make `immer` and `property-information` as dependencies
14
+
15
+ ## V3.37.0
16
+
17
+ * Introduced `createState` helper for simplified immutable state updates using Immer [create-state-immer](#docs/done/blog-create-state-immer.md)
18
+ * Added `addComponents` function for adding components to routes [add-components](#docs/done/blog-addcomponents-feature.md)
19
+ * Moved the component to dev-tools
20
+
4
21
 
5
22
  ## 3.36.1
6
23
 
7
24
  * Continue code review with AI
8
25
  * Use property-information to improve property and attribute handling
9
- * Performance improvements in virtual DOM handling, see [analysis reports](docs/done/framework-reordering-comparison.md)
10
- * New hierarchical matching behavior, see [hierarchical routing requirements](docs/requirements/req-hierarchical-routing.md)
26
+ * Performance improvements in virtual DOM handling, see [analysis reports](#docs/done/framework-reordering-comparison.md)
27
+ * New hierarchical matching behavior, see [hierarchical routing requirements](#docs/requirements/req-hierarchical-routing.md)
11
28
 
12
29
  ## 3.36.0
13
30
 
14
- * Code review by using Copilot and Claude Sonnet 4, see [plan-apprun-bugfixes.md](docs/plan/plan-apprun-bugfixes.md) for details.
31
+ * Code review by using Copilot and Claude Sonnet 4, see [plan-apprun-bugfixes.md](#docs/plan/plan-apprun-bugfixes.md) for details.
15
32
  * Enhanced type definitions (apprun.d.ts) for better TypeScript support
16
33
  * Fixed minor bugs and edge cases in virtual DOM handling
17
34
  * Fixed bugs in router initialization logic
package/README.md CHANGED
@@ -3,6 +3,9 @@
3
3
  [![AppRun Docs](https://img.shields.io/badge/docs-website-blue.svg)](https://apprun.js.org/docs)
4
4
  [![AppRun Playground](https://img.shields.io/badge/playground-online-green.svg)](https://apprun.js.org/#play)
5
5
  [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][downloads-url] [![License][license-image]][license-url] [![twitter][twitter-badge]][twitter] [![Discord Chat][discord-image]][discord-invite]
6
+ 🕵️ We now have rule / prompt files that you can use with your AI Coding Agent:
7
+ - [For Components using JSX](ai/apprun.prompt.md)
8
+ - [For Components using HTML](ai/apprun-html.prompt.md)
6
9
 
7
10
  🚀 July 2025, We have started to improve the codebase using AI. See [whats new](#new) for details.
8
11
 
@@ -0,0 +1,290 @@
1
+ ---
2
+ mode: agent
3
+ ---
4
+
5
+ # AppRun Component Creation Rules
6
+
7
+ When creating AppRun components, follow these guidelines for consistent, maintainable code.
8
+
9
+ ## Core Component Architecture
10
+ - **HTML templates**: Always use `html` tagged literals for UI rendering
11
+ - **State management**: Handle state through event handlers that return new state objects
12
+ - **Event handling**: Use `event-name` syntax in templates with handlers `(state, param) => newState`
13
+ - **Local events**: Use `run()` for component-local events (no registration needed)
14
+ - **Immutable updates**: Always return new state objects, never mutate existing state
15
+
16
+ ## Component Creation Patterns
17
+
18
+ ### 1. Functional Components (Pure UI Components)
19
+ **When to use**: For reusable UI pieces that don't manage their own state.
20
+
21
+ ```js
22
+ // Create pure function components that take props and return HTML
23
+ export const AgentModal = (agent, onClose) => {
24
+ return html`
25
+ <div class="modal-overlay" @click=${run(onClose, false)}>
26
+ <div class="modal-content" @click=${(e) => e.stopPropagation()}>
27
+ <!-- Use conditional rendering for dynamic content -->
28
+ ${agent.status ? html`<h2>${agent.name}</h2>` : html`
29
+ <input value="${agent.name || ''}" @input=${(e) => agent.name = e.target.value}>
30
+ `}
31
+ </div>
32
+ </div>
33
+ `;
34
+ };
35
+ ```
36
+
37
+ **Rules for functional components**:
38
+ - Export as pure functions
39
+ - Accept props as parameters
40
+ - Return HTML template literals
41
+ - Handle events through callback props
42
+ - No internal state management
43
+
44
+ ### 2. Stateful Page Components (Full Components)
45
+ **When to use**: For main application views that manage state and handle complex interactions.
46
+
47
+ **Step 1: Create async state initialization**
48
+ ```js
49
+ // Always use async state function for API calls and data loading
50
+ const state = async () => {
51
+ const data = await api.getData();
52
+ return {
53
+ ...initialState,
54
+ data,
55
+ loading: false
56
+ };
57
+ };
58
+ ```
59
+
60
+ **Step 2: Define event handlers as separate functions**
61
+ ```js
62
+ // Create specific handlers for each user interaction
63
+ const selectWorld = async (state, worldName) => {
64
+ if (worldName === state.worldName) return state;
65
+ const agents = await api.getAgents(worldName);
66
+ return ({ ...state, worldName, agents });
67
+ };
68
+
69
+ const openModal = (state, item = null) => {
70
+ return ({
71
+ ...state,
72
+ editingItem: item || { name: 'New Item' },
73
+ showModal: true
74
+ });
75
+ };
76
+ ```
77
+
78
+ **Step 3: Create view function with proper rendering patterns**
79
+ ```js
80
+ // View function should be pure - only render, never mutate
81
+ const view = (state) => {
82
+ return html`
83
+ <div class="container">
84
+ ${state.items.map(item => html`
85
+ <div class="item" @click=${run(openModal, item)}>
86
+ ${item.name}
87
+ </div>
88
+ `)}
89
+ ${state.showModal ? ModalComponent(state.editingItem, closeModal) : ''}
90
+ </div>
91
+ `;
92
+ };
93
+ ```
94
+
95
+ **Step 4: Define minimal update object for routing**
96
+ ```js
97
+ // Keep update object minimal - most events handled by run()
98
+ const update = {
99
+ '/,#': state => state,
100
+ // Only add global events or routing here
101
+ };
102
+ ```
103
+
104
+ **Step 5: Export Component instance**
105
+ ```js
106
+ // Create and export component instance without class syntax
107
+ // enable global events with {global_event: true}
108
+ export default new Component(state, view, update, {global_event: true});
109
+
110
+ // Mount to DOM element
111
+ component.start('#main'); // For visible components
112
+ component.mount('#modal'); // For modal/hidden components
113
+ ```
114
+
115
+ ## Event Handling Best Practices
116
+
117
+ ### Using run() for Local Events
118
+ **✅ Correct patterns**:
119
+ ```js
120
+ // Simple event with parameter
121
+ @click=${run(selectWorld, world.name)}
122
+
123
+ // Event with callback function
124
+ @click=${run(closeModal, false)}
125
+
126
+ // Global event
127
+ @input=${run('updateModalAgentName')}
128
+ ```
129
+
130
+ **❌ Common mistakes to avoid**:
131
+ ```js
132
+ // DON'T wrap run() in arrow function - prevents re-rendering
133
+ @click=${() => run(selectWorld, world.name)}
134
+
135
+ // DON'T manually pass event in arrow function
136
+ @click=${(e) => run(updateName, e)}
137
+ ```
138
+
139
+ ### Event Handler Function Rules
140
+ ```js
141
+ // Event handlers automatically receive event as last parameter
142
+ const updateModalAgentName = (state, eventParam) => {
143
+ const name = eventParam.target.value;
144
+ return ({...state, agentName: name });
145
+ };
146
+
147
+ // Handle event.stopPropagation() in handler when needed
148
+ const openAgentModal = (state, agent, e) => {
149
+ e.stopPropagation();
150
+ return ({...state, editingAgent: agent, showModal: true });
151
+ };
152
+ ```
153
+
154
+
155
+ ## Template Rendering Guidelines
156
+
157
+ ### Conditional Rendering Patterns
158
+ ```js
159
+ // Simple boolean conditions
160
+ ${state.loading ? html`<div>Loading...</div>` : ''}
161
+
162
+ // Complex conditional with nested HTML blocks
163
+ ${agent.status ? html`
164
+ <h2>${agent.name}</h2>
165
+ <p>Status: Active</p>
166
+ ` : html`
167
+ <input value="${agent.name || ''}" @input=${run(updateName)}>
168
+ <button @click=${run(saveAgent)}>Save</button>
169
+ `}
170
+ ```
171
+
172
+ ### List Rendering with map()
173
+ ```js
174
+ // Basic list rendering
175
+ ${state.items.map(item => html`
176
+ <div class="${item.active ? 'active' : ''}">${item.name}</div>
177
+ `)}
178
+
179
+ // Complex list items with events
180
+ ${state.agents.map(agent => html`
181
+ <div class="agent-card" @click=${run(selectAgent, agent.id)}>
182
+ <h3>${agent.name}</h3>
183
+ <button @click=${run(editAgent, agent)}
184
+ @click=${(e) => e.stopPropagation()}>
185
+ Edit
186
+ </button>
187
+ </div>
188
+ `)}
189
+ ```
190
+
191
+ ### Component Composition
192
+ ```js
193
+ // Pass data and callbacks to child components
194
+ ${state.showModal ? AgentModal(state.editingAgent, closeModal) : ''}
195
+
196
+ // Conditional component rendering with fallbacks
197
+ ${state.currentView === 'list'
198
+ ? AgentList(state.agents, selectAgent)
199
+ : AgentDetail(state.selectedAgent, goBack)
200
+ }
201
+ ```
202
+
203
+ ## State Management Rules
204
+
205
+ ### Immutable State Updates
206
+ ```js
207
+ // ✅ Always spread existing state for updates
208
+ return ({ ...state, newProperty: value });
209
+
210
+ // ✅ Update nested objects immutably
211
+ return ({
212
+ ...state,
213
+ user: { ...state.user, name: newName },
214
+ items: [...state.items, newItem]
215
+ });
216
+
217
+ // ❌ Never mutate state directly
218
+ state.newProperty = value; // Wrong!
219
+ state.items.push(newItem); // Wrong!
220
+ ```
221
+
222
+ ### Async State Updates
223
+ ```js
224
+ // Standard async handler
225
+ const handler = async (state, param) => {
226
+ try {
227
+ const data = await api.call(param);
228
+ return ({ ...state, data, loading: false });
229
+ } catch (error) {
230
+ return ({ ...state, error: error.message, loading: false });
231
+ }
232
+ };
233
+
234
+ // Generator for progressive updates
235
+ const handler = async function* (state, param) {
236
+ yield ({ ...state, loading: true });
237
+ try {
238
+ const data = await api.call(param);
239
+ yield ({ ...state, data, loading: false });
240
+ } catch (error) {
241
+ yield ({ ...state, error: error.message, loading: false });
242
+ }
243
+ };
244
+ ```
245
+
246
+ ### Error Handling in Components
247
+ ```js
248
+ // Always handle errors in async operations
249
+ const saveData = async (state, data) => {
250
+ try {
251
+ await api.saveData(data);
252
+ return ({ ...state, showModal: false, saved: true });
253
+ } catch (error) {
254
+ return ({ ...state, error: `Save failed: ${error.message}` });
255
+ }
256
+ };
257
+ ```
258
+
259
+ ## Component Creation Checklist
260
+
261
+ When creating any AppRun component, ensure you follow these rules:
262
+
263
+ ### Essential Requirements
264
+ - ✅ **Event handlers must return new state** - No return = no re-render
265
+ - ✅ **View functions are pure** - Only render, never mutate state
266
+ - ✅ **Use `run()` for local events** - No registration needed in update object
267
+ - ✅ **Use `app.run()` for global events** - Cross-component communication
268
+ - ✅ **Immutable state updates** - Always spread existing state `{...state, newProp}`
269
+ - ✅ **HTML template literals** - Use `html` tagged templates for all UI
270
+ - ✅ **Conditional rendering** - Use ternary operators and template conditionals
271
+
272
+ ### Component Structure Guidelines
273
+ - ✅ **Functional components**: Export pure functions that take props and return HTML
274
+ - ✅ **Stateful components**: Use async state, separate handlers, Component instance
275
+ - ✅ **Error boundaries**: Always handle async errors in try/catch blocks
276
+ - ✅ **Component composition**: Pass data and callbacks to child components
277
+ - ✅ **Event delegation**: Use event.stopPropagation() when needed in handlers
278
+
279
+ ### File Organization
280
+ - ✅ **One component per file** - Keep components focused and reusable
281
+ - ✅ **Named exports for functions** - `export const ComponentName = (...) => html`
282
+ - ✅ **Default export for pages** - `export default new Component(...)`
283
+ - ✅ **Import dependencies** - Use static imports, omit `.js`/`.ts` extensions
284
+
285
+ ### Performance Considerations
286
+ - ✅ **Minimal update object** - Only include routing and global events
287
+ - ✅ **Efficient rendering** - Use conditional rendering to avoid unnecessary DOM updates
288
+ - ✅ **State normalization** - Keep state flat when possible for easier updates
289
+ - ✅ **Event handler optimization** - Define handlers outside render when possible
290
+
@@ -0,0 +1,166 @@
1
+ ---
2
+ mode: agent
3
+ ---
4
+ # AppRun Component Creation Guide
5
+
6
+ ## Core Concepts
7
+
8
+ AppRun follows the State-View-Update architecture pattern with TypeScript support for type-safe component development.
9
+
10
+ ## Step 1: Define State Interface
11
+
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
17
+
18
+ ```typescript
19
+ interface ComponentState {
20
+ data: DataType[];
21
+ currentIndex: number;
22
+ loading: boolean;
23
+ error: string | null;
24
+ }
25
+ ```
26
+
27
+ ## Step 2: Create Component Structure
28
+
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
34
+
35
+ ```typescript
36
+ 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
+ }
46
+ }
47
+ ```
48
+
49
+ ## Step 3: Design View Function
50
+
51
+ ### **View Structure**
52
+ ```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;
61
+ }
62
+ ```
63
+
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)
68
+
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
76
+
77
+ ## Step 4: Implement Event Handling
78
+
79
+ ### **$onclick Usage Rules**
80
+
81
+ #### Action Pattern
82
+ ```typescript
83
+ // String actions for simple state updates
84
+ $onclick='action-name'
85
+
86
+ // Tuple actions for data passing
87
+ $onclick={['action-name', data]}
88
+ ```
89
+
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
95
+
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
100
+
101
+ ## Step 5: Create Update Functions
102
+
103
+ ### **Update Function Signatures**
104
+ ```typescript
105
+ update = {
106
+ // State update (returns new state)
107
+ 'action-name': (state: State, payload?: any): State => ({ ...state, changes }),
108
+
109
+ // Side effect (no return, no re-render)
110
+ 'action-name': (state: State, payload?: any): void => { /* side effects */ },
111
+
112
+ // Async operations (generators for progressive updates)
113
+ 'action-name': async function* (state: State): AsyncGenerator<State> { /* async logic */ }
114
+ }
115
+ ```
116
+
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
121
+
122
+ ## Step 6: Handle Data Loading
123
+
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
128
+
129
+ ```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 };
139
+ }
140
+ }
141
+ }
142
+ ```
143
+
144
+ ## Step 7: Error Handling
145
+
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
152
+
153
+ ## Step 8: Navigation & Side Effects
154
+
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
160
+
161
+ ```typescript
162
+ 'navigate-action': (state: State, data: any): void => {
163
+ window.location.href = '/path/' + data.id;
164
+ }
165
+ ```
166
+
package/apprun.d.ts CHANGED
@@ -31,6 +31,7 @@
31
31
  * - Comprehensive options typing matching implementation
32
32
  * - Added proper deprecation warnings
33
33
  * - Enhanced error handling and validation
34
+ * - Added support for async generator and generator functions in Action types
34
35
  */
35
36
 
36
37
  import { TemplateResult } from 'lit-html';
@@ -39,6 +40,8 @@ declare module 'apprun' {
39
40
 
40
41
  export type Element = HTMLElement | string;
41
42
 
43
+ export type State<T> = T | Promise<T> | (() => T) | (() => Promise<T>);
44
+
42
45
  export type VNode = {
43
46
  tag: string | Function,
44
47
  props: {},
@@ -47,7 +50,7 @@ declare module 'apprun' {
47
50
 
48
51
  export type VDOM = false | string | VNode | Array<VNode | string> | TemplateResult;
49
52
  export type View<T> = (state: T) => VDOM | void;
50
- export type Action<T> = (state: T, ...p: any[]) => T | Promise<T> | void;
53
+ export type Action<T> = (state: T, ...p: any[]) => T | Promise<T> | void | AsyncGenerator<T> | Generator<T>;
51
54
  export type ActionDef<T, E> = (readonly [E, Action<T>, {}?]);
52
55
  export type Update<T, E = any> = ActionDef<T, E>[] | { [name: string]: Action<T> | {}[] } | (E | Action<T> | {})[];
53
56
  export type Router = (url: string, ...args: any[]) => any;
@@ -106,7 +109,7 @@ declare module 'apprun' {
106
109
  /** @deprecated Use runAsync() instead. query() will be removed in a future version. */
107
110
  query(name: string, ...args: any[]): Promise<any[]>;
108
111
 
109
- start<T, E = any>(element?: Element | string, model?: T, view?: View<T>, update?: Update<T, E>,
112
+ start<T, E = any>(element?: Element | string, state?: State<T>, view?: View<T>, update?: Update<T, E>,
110
113
  options?: AppStartOptions<T>): Component<T, E>;
111
114
 
112
115
  h(tag: string | Function, props?: any, ...children: any[]): VNode | VNode[];
@@ -126,9 +129,9 @@ declare module 'apprun' {
126
129
  }
127
130
 
128
131
  export class Component<T = any, E = any> {
129
- constructor(state?: T, view?: View<T>, update?: Update<T, E>, options?: any);
132
+ constructor(state?: State<T>, view?: View<T>, update?: Update<T, E>, options?: any);
130
133
  readonly element: Element;
131
- readonly state: T;
134
+ protected state: State<T>;
132
135
  view?: View<T>;
133
136
  update?: Update<T, E>;
134
137