@powerhousedao/academy 5.1.0-dev.9 → 5.1.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +43 -1108
  2. package/blog/BeyondCommunication-ABlueprintForDevelopment.md +1 -2
  3. package/blog/TheChallengeOfChange.md +0 -1
  4. package/docs/academy/00-EthereumArgentinaHackathon.md +207 -0
  5. package/docs/academy/01-GetStarted/00-ExploreDemoPackage.mdx +27 -24
  6. package/docs/academy/01-GetStarted/01-CreateNewPowerhouseProject.md +9 -118
  7. package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +28 -110
  8. package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +145 -191
  9. package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +218 -0
  10. package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/05-VetraStudio.md +6 -48
  11. package/docs/academy/01-GetStarted/06-ReactorMCP.md +58 -0
  12. package/docs/academy/01-GetStarted/_04-BuildToDoListEditor +1 -1
  13. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/03-BuilderTools.md +2 -2
  14. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/02-SpecifyTheStateSchema.md +44 -75
  15. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/03-SpecifyDocumentOperations.md +22 -28
  16. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/04-UseTheDocumentModelGenerator.md +31 -28
  17. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/05-ImplementDocumentReducers.md +206 -211
  18. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/06-ImplementDocumentModelTests.md +62 -176
  19. package/docs/academy/02-MasteryTrack/02-DocumentModelCreation/07-ExampleToDoListRepository.md +0 -21
  20. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/01-BuildingDocumentEditors.md +319 -309
  21. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/00-DocumentToolbar.mdx +0 -4
  22. package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/01-OperationHistory.md +0 -4
  23. package/docs/academy/02-MasteryTrack/05-Launch/04-ConfigureEnvironment.md +1 -1
  24. package/docs/academy/03-ExampleUsecases/Chatroom/02-CreateNewPowerhouseProject.md +35 -111
  25. package/docs/academy/03-ExampleUsecases/Chatroom/03-DefineChatroomDocumentModel.md +76 -255
  26. package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +160 -281
  27. package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +35 -188
  28. package/docs/academy/03-ExampleUsecases/Chatroom/06-LaunchALocalReactor.md +7 -95
  29. package/docs/academy/03-ExampleUsecases/Chatroom/_category_.json +1 -1
  30. package/docs/academy/04-APIReferences/00-PowerhouseCLI.md +2 -6
  31. package/docs/academy/04-APIReferences/01-ReactHooks.md +501 -291
  32. package/docs/academy/05-Architecture/00-PowerhouseArchitecture.md +39 -7
  33. package/docs/academy/05-Architecture/02-ReferencingMonorepoPackages +65 -0
  34. package/docs/academy/05-Architecture/04-MovingBeyondCRUD +61 -0
  35. package/docs/academy/06-ComponentLibrary/00-DocumentEngineering.md +24 -72
  36. package/docs/academy/08-Glossary.md +0 -7
  37. package/docusaurus.config.ts +3 -28
  38. package/package.json +1 -1
  39. package/sidebars.ts +12 -49
  40. package/src/css/custom.css +18 -26
  41. package/docs/academy/01-GetStarted/04-WriteDocumentModelTests.md +0 -378
  42. package/docs/academy/01-GetStarted/05-BuildToDoListEditor.md +0 -560
  43. package/docs/academy/03-ExampleUsecases/TodoList/00-GetTheStarterCode.md +0 -24
  44. package/docs/academy/03-ExampleUsecases/TodoList/01-GenerateTodoListDocumentModel.md +0 -211
  45. package/docs/academy/03-ExampleUsecases/TodoList/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
  46. package/docs/academy/03-ExampleUsecases/TodoList/03-AddTestsForTodoListActions.md +0 -462
  47. package/docs/academy/03-ExampleUsecases/TodoList/04-GenerateTodoListDocumentEditor.md +0 -45
  48. package/docs/academy/03-ExampleUsecases/TodoList/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
  49. package/docs/academy/03-ExampleUsecases/TodoList/06-GenerateTodoDriveExplorer.md +0 -61
  50. package/docs/academy/03-ExampleUsecases/TodoList/07-AddSharedComponentForShowingTodoListStats.md +0 -384
  51. package/docs/academy/03-ExampleUsecases/TodoList/_category_.json +0 -8
  52. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/VetraPackageLibrary.md +0 -7
  53. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/_category_.json +0 -9
  54. package/docs/academy/04-APIReferences/06-VetraRemoteDrive.md +0 -160
  55. package/docs/academy/04-APIReferences/renown-sdk/00-Overview.md +0 -316
  56. package/docs/academy/04-APIReferences/renown-sdk/01-Authentication.md +0 -672
  57. package/docs/academy/04-APIReferences/renown-sdk/02-APIReference.md +0 -957
  58. package/docs/academy/04-APIReferences/renown-sdk/_category_.json +0 -8
  59. package/docs/academy/10-TodoListTutorial/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
  60. package/docs/academy/10-TodoListTutorial/03-AddTestsForTodoListActions.md +0 -462
  61. package/docs/academy/10-TodoListTutorial/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
  62. package/docs/academy/10-TodoListTutorial/07-AddSharedComponentForShowingTodoListStats.md +0 -370
  63. package/static/img/Vetra-logo-dark.svg +0 -11
  64. package/static/img/vetra-logo-light.svg +0 -11
  65. /package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/images/Modules.png +0 -0
  66. /package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/images/VetraStudioDrive.png +0 -0
  67. /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline/360/237/232/247" → 02-RevisionHistoryTimeline} +0 -0
  68. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /01-WhatIsADocumentModel" → 01-WhatIsADocumentModel} +0 -0
  69. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-DAOandDocumentsModelsQ+A" → 02-DAOandDocumentsModelsQ+A} +0 -0
  70. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-domain-modeling" → 02-domain-modeling} +0 -0
  71. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /03-BenefitsOfDocumentModels" → 03-BenefitsOfDocumentModels} +0 -0
  72. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /04-UtilitiesAndTips" → 04-UtilitiesAndTips} +0 -0
  73. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /05-best-practices" → 05-best-practices} +0 -0
  74. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /_category_.json" → _category_.json} +0 -0
  75. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /three-data-layers.png" → three-data-layers.png} +0 -0
@@ -10,9 +10,9 @@ Reducers are the core logic units of your document model. They are the functions
10
10
 
11
11
  Before diving into the specifics of writing reducers, let's recall the preceding steps:
12
12
 
13
- 1. **State Schema Definition**: You designed the GraphQL `type` definitions for your document's data structure (e.g., `TodoListState`, `TodoItem`).
13
+ 1. **State Schema Definition**: You designed the GraphQL `type` definitions for your document's data structure (e.g., `ToDoListState`, `ToDoItem`).
14
14
  2. **Document Operation Specification**: You defined the GraphQL `input` types that specify the parameters for each allowed modification to your document (e.g., `AddTodoItemInput`, `UpdateTodoItemInput`). These were then associated with named operations (e.g., `ADD_TODO_ITEM`) in the Connect application.
15
- 3. **Code Generation**: You used `ph generate <YourModelName.phd>` to create the necessary TypeScript types, action creators, and, crucially, the skeleton file for your reducers (typically `document-models/<your-model-name>/src/reducers/todos.ts`).
15
+ 3. **Code Generation**: You used `ph generate <YourModelName.phdm.zip>` to create the necessary TypeScript types, action creators, and, crucially, the skeleton file for your reducers (typically `document-models/<YourModelName>/src/reducers/<your-model-name>.ts`).
16
16
 
17
17
  This generated reducer file is our starting point. It will contain function stubs or an object structure expecting your reducer implementations, all typed according to your schema.
18
18
 
@@ -27,7 +27,7 @@ Let's break down its components and principles:
27
27
  - **`currentState`**: This is the complete, current state of your document model instance before the operation is applied. It's crucial to treat this as **immutable**.
28
28
  - **`action`**: This is an object describing the operation to be performed. It typically has:
29
29
  - A `type` property: A string identifying the operation (e.g., `'ADD_TODO_ITEM'`).
30
- - An `input` property (or similar, like `payload`): An object containing the data necessary for the operation, matching the GraphQL `input` type you defined (e.g., `{ text: 'Buy groceries' }` for `AddTodoItemInput`).
30
+ - An `input` property (or similar, like `payload`): An object containing the data necessary for the operation, matching the GraphQL `input` type you defined (e.g., `{ id: '1', text: 'Buy groceries' }` for `AddTodoItemInput`).
31
31
  - **`newState`**: The reducer must return a _new_ state object representing the state after the operation has been applied. If the operation does not result in a state change, the reducer should return the `currentState` object itself.
32
32
 
33
33
  ### Key principles guiding reducer implementation:
@@ -39,211 +39,204 @@ Let's break down its components and principles:
39
39
  2. **Immutability**:
40
40
  - **Never Mutate `currentState`**: You must never directly modify the `currentState` object or any of its nested properties.
41
41
  - **Always Return a New Object for Changes**: If the state changes, you must create and return a brand new object. If the state does not change, you return the original `currentState` object.
42
- - This is fundamental to Powerhouse's event sourcing architecture, enabling time travel, efficient change detection, and a clear audit trail.
43
-
44
- :::tip Powerhouse uses Immer.js
45
- Powerhouse uses **Immer.js** under the hood, which means you can write code that _looks like_ it's mutating the state directly (e.g., `state.items.push(...)`), but Immer ensures it results in an immutable update. This gives you the best of both worlds: readable code and immutable state.
46
- :::
42
+ - This is fundamental to Powerhouse's event sourcing architecture, enabling time travel, efficient change detection, and a clear audit trail. We'll explore techniques for immutability shortly.
47
43
 
48
44
  3. **Single Source of Truth**: The document state managed by reducers is the single source of truth for that document instance. All UI rendering and data queries are derived from this state.
49
45
 
50
46
  4. **Delegation to specific operation handlers**:
51
- While you can write one large reducer that uses a `switch` statement or `if/else if` blocks based on `action.type`, Powerhouse's generated code typically encourages a more modular approach. You'll often implement a separate function for each operation, which are then combined into a main reducer object or map. The `ph generate` command usually sets up this structure for you. For example, in your `document-models/todo-list/src/reducers/todos.ts`, you'll find an object structure like this:
47
+ While you can write one large reducer that uses a `switch` statement or `if/else if` blocks based on `action.type`, Powerhouse's generated code typically encourages a more modular approach. You'll often implement a separate function for each operation, which are then combined into a main reducer object or map. The `ph generate` command usually sets up this structure for you. For example, in your `document-models/to-do-list/src/reducers/to-do-list.ts`, you'll find an object structure like this:
52
48
 
53
49
  ```typescript
54
- import type { TodoListTodosOperations } from "todo-tutorial/document-models/todo-list";
50
+ import { ToDoListToDoListOperations } from "../../gen/to-do-list/operations.js"; // Generated type for operations
51
+ import { ToDoListState } from "../../gen/types.js"; // Generated type for state
55
52
 
56
- export const todoListTodosOperations: TodoListTodosOperations = {
57
- addTodoItemOperation(state, action) {
53
+ export const reducer: ToDoListToDoListOperations = {
54
+ addTodoItemOperation(state: ToDoListState, action, dispatch) {
58
55
  // Your logic for ADD_TODO_ITEM
56
+ // ...
57
+ return newState;
59
58
  },
60
- updateTodoItemOperation(state, action) {
59
+ updateTodoItemOperation(state: ToDoListState, action, dispatch) {
61
60
  // Your logic for UPDATE_TODO_ITEM
61
+ // ...
62
+ return newState;
62
63
  },
63
- deleteTodoItemOperation(state, action) {
64
+ deleteTodoItemOperation(state: ToDoListState, action, dispatch) {
64
65
  // Your logic for DELETE_TODO_ITEM
66
+ // ...
67
+ return newState;
65
68
  },
69
+ // ... other operations
66
70
  };
67
71
  ```
68
72
 
69
- The `TodoListTodosOperations` type is generated by Powerhouse and ensures your reducer object correctly implements all defined operations. The `state` and `action` parameters within these methods will also be strongly typed based on your schema.
73
+ The `ToDoListToDoListOperations` type (or similar, depending on your model name) is generated by Powerhouse and ensures your reducer object correctly implements all defined operations. The `state` and `action` parameters within these methods will also be strongly typed based on your schema.
70
74
 
71
- ## Implementing reducer logic: A practical guide
75
+ The `dispatch` parameter is an advanced feature allowing a reducer to trigger subsequent operations. While powerful for complex workflows, it's often not needed for basic operations and can be ignored if unused.
72
76
 
73
- Let's use our familiar `TodoList` example to illustrate common patterns.
77
+ ## Implementing reducer logic: A practical guide
74
78
 
75
- ### Basic implementation (matching Get Started)
79
+ Let's use our familiar `ToDoList` example to illustrate common patterns. For this example, we'll assume our state schema has been updated to include a `stats` object to track the number of total, checked, and unchecked items.
76
80
 
77
- The basic implementation matches what you built in the Get Started tutorial:
81
+ Our `ToDoListState` now looks like this:
78
82
 
79
83
  ```typescript
80
- import { generateId } from "document-model/core";
81
- import type { TodoListTodosOperations } from "todo-tutorial/document-models/todo-list";
82
-
83
- export const todoListTodosOperations: TodoListTodosOperations = {
84
- addTodoItemOperation(state, action) {
85
- // Generate a unique ID for the new todo item
86
- const id = generateId();
87
-
88
- // Add the new item to the state (Immer handles immutability)
89
- state.items.push({ ...action.input, id, checked: false });
90
- },
91
-
92
- updateTodoItemOperation(state, action) {
93
- // Find the item to update by its ID
94
- const item = state.items.find((item) => item.id === action.input.id);
95
-
96
- // Return early if item not found
97
- if (!item) return;
98
-
99
- // Update only the fields that are provided (partial update)
100
- item.text = action.input.text ?? item.text;
101
- item.checked = action.input.checked ?? item.checked;
102
- },
103
-
104
- deleteTodoItemOperation(state, action) {
105
- // Filter out the item with the matching ID
106
- state.items = state.items.filter((item) => item.id !== action.input.id);
107
- },
108
- };
109
- ```
110
-
111
- :::info Key Pattern: ID Generation
112
- Notice that `addTodoItemOperation` uses `generateId()` from `document-model/core` to create a unique ID. This is the recommended pattern — the ID is generated in the reducer, not passed from the UI. This ensures consistent, unique IDs across all operations.
113
- :::
114
-
115
- ### Advanced implementation (with statistics tracking)
116
-
117
- :::info Advanced Feature
118
- This section extends the basic reducers with statistics tracking, matching the advanced schema from the previous section. This demonstrates how to update computed/derived state alongside your primary data.
119
- :::
120
-
121
- For the advanced version with `stats`, we need to update the statistics whenever items are added, updated, or deleted:
84
+ interface ToDoItem {
85
+ id: string;
86
+ text: string;
87
+ checked: boolean;
88
+ }
122
89
 
123
- ```typescript
124
- import { generateId } from "document-model/core";
125
- import type { TodoListTodosOperations } from "todo-tutorial/document-models/todo-list";
126
-
127
- export const todoListTodosOperations: TodoListTodosOperations = {
128
- addTodoItemOperation(state, action) {
129
- // Generate a unique ID for the new todo item
130
- const id = generateId();
131
-
132
- // Update statistics
133
- state.stats.total += 1;
134
- state.stats.unchecked += 1;
90
+ interface ToDoListStats {
91
+ total: number;
92
+ checked: number;
93
+ unchecked: number;
94
+ }
135
95
 
136
- // Add the new item to the state
137
- state.items.push({
138
- id,
139
- text: action.input.text,
140
- checked: false, // New items always start as unchecked
141
- });
142
- },
96
+ interface ToDoListState {
97
+ items: ToDoItem[];
98
+ stats: ToDoListStats;
99
+ }
100
+ ```
143
101
 
144
- updateTodoItemOperation(state, action) {
145
- // Find the specific item we want to update
146
- const item = state.items.find((item) => item.id === action.input.id);
102
+ And our action creators (from `../../gen/creators` or `../../gen/operations.js`) provide actions like:
147
103
 
148
- if (!item) {
149
- throw new Error(`Item with id ${action.input.id} not found`);
150
- }
104
+ - `actions.addTodoItem({ id: 'some-id', text: 'New Task' })`
105
+ - `actions.updateTodoItem({ id: 'item-id', text: 'Updated Task Text', checked: true })`
106
+ - `actions.deleteTodoItem({ id: 'item-id' })`
151
107
 
152
- // Update text if provided
153
- if (action.input.text !== undefined) {
154
- item.text = action.input.text;
155
- }
108
+ ### 1. Adding an item (e.g., `addTodoItemOperation`)
156
109
 
157
- // Handle checked status changes and update stats
158
- if (action.input.checked !== undefined && action.input.checked !== item.checked) {
159
- if (action.input.checked) {
160
- state.stats.unchecked -= 1;
161
- state.stats.checked += 1;
162
- } else {
163
- state.stats.unchecked += 1;
164
- state.stats.checked -= 1;
165
- }
166
- item.checked = action.input.checked;
167
- }
168
- },
110
+ To add a new item to the `items` array immutably:
169
111
 
170
- deleteTodoItemOperation(state, action) {
171
- // Find the item to determine its checked status for stats
172
- const item = state.items.find((item) => item.id === action.input.id);
112
+ ```typescript
113
+ addTodoItemOperation(state: ToDoListState, action: /* AddTodoItemActionType */ any, dispatch) {
114
+ const newItem: ToDoItem = {
115
+ id: action.input.id,
116
+ text: action.input.text,
117
+ checked: false, // New items default to unchecked
118
+ };
119
+
120
+ // Return a new state object
121
+ return {
122
+ ...state, // Copy all existing properties from the current state
123
+ items: [...state.items, newItem], // Create a new items array: spread existing items, add the new one
124
+ };
125
+ }
126
+ ```
173
127
 
174
- if (item) {
175
- // Update statistics
176
- state.stats.total -= 1;
177
- if (item.checked) {
178
- state.stats.checked -= 1;
179
- } else {
180
- state.stats.unchecked -= 1;
181
- }
182
- }
128
+ **Explanation**:
183
129
 
184
- // Remove the item from the list
185
- state.items = state.items.filter((item) => item.id !== action.input.id);
186
- },
187
- };
188
- ```
130
+ - We use the spread operator (`...state`) to copy top-level properties from the old state into the new state object.
131
+ - For the `items` array, we create a _new_ array by spreading the existing `state.items` and then appending the `newItem`.
189
132
 
190
- ### Common patterns explained
133
+ ### 2. Updating an item (e.g., `updateTodoItemOperation`)
191
134
 
192
- #### 1. Adding an item
135
+ To update an existing item in the `items` array immutably:
193
136
 
194
137
  ```typescript
195
- addTodoItemOperation(state, action) {
196
- const id = generateId(); // Generate unique ID
197
- state.items.push({ ...action.input, id, checked: false });
138
+ updateTodoItemOperation(state: ToDoListState, action: /* UpdateTodoItemActionType */ any, dispatch) {
139
+ const { id, text, checked } = action.input;
140
+
141
+ // Return a new state object
142
+ return {
143
+ ...state,
144
+ items: state.items.map(item => {
145
+ if (item.id === id) {
146
+ // This is the item to update. Return a *new* item object.
147
+ return {
148
+ ...item, // Copy existing properties of the item
149
+ // Update only fields that are provided in the action input
150
+ ...(text !== undefined && { text: text }),
151
+ ...(checked !== undefined && { checked: checked }),
152
+ };
153
+ }
154
+ // This is not the item we're looking for, return it unchanged.
155
+ return item;
156
+ }),
157
+ };
198
158
  }
199
159
  ```
200
160
 
201
- - We use `generateId()` to create a unique identifier
202
- - We spread `action.input` to get the text, add the generated ID and default `checked: false`
203
- - With Immer, this "mutation" is actually immutable
161
+ **Explanation**:
204
162
 
205
- #### 2. Updating an item
163
+ - We use the `map` array method, which always returns a _new_ array.
164
+ - For the item that matches `action.input.id`, we create a new item object using the spread operator (`...item`) and then overwrite the properties (`text`, `checked`) that are present in `action.input`.
165
+ - The conditional spread (`...(condition && { property: value })`) is a concise way to only include a property in the new object if its corresponding input value is provided. This elegantly handles partial updates.
166
+ - If an item doesn't match the ID, it's returned as is.
167
+
168
+ **Error Handling Note**: In a real application, you might want to add a check to see if an item with `action.input.id` actually exists. If not, you could throw an error or handle it according to your application's requirements:
206
169
 
207
170
  ```typescript
208
- updateTodoItemOperation(state, action) {
209
- const item = state.items.find((item) => item.id === action.input.id);
210
- if (!item) return;
211
-
212
- item.text = action.input.text ?? item.text;
213
- item.checked = action.input.checked ?? item.checked;
171
+ // Inside updateTodoItemOperation, before returning:
172
+ const itemToUpdate = state.items.find((item) => item.id === action.input.id);
173
+ if (!itemToUpdate) {
174
+ // Option 1: Throw an error (Powerhouse runtime might catch this)
175
+ throw new Error(`Item with id ${action.input.id} not found.`);
176
+ // Option 2: Return current state (no change)
177
+ // return state;
214
178
  }
179
+ // ... proceed with map
215
180
  ```
216
181
 
217
- - We find the item by ID
218
- - We use nullish coalescing (`??`) to only update fields that were provided
219
- - This allows partial updates (e.g., just changing `checked` without touching `text`)
182
+ ### 3. Deleting an item (e.g., `deleteTodoItemOperation`)
220
183
 
221
- #### 3. Deleting an item
184
+ To remove an item from the `items` array immutably:
222
185
 
223
186
  ```typescript
224
- deleteTodoItemOperation(state, action) {
225
- state.items = state.items.filter((item) => item.id !== action.input.id);
187
+ deleteTodoItemOperation(state: ToDoListState, action: /* DeleteTodoItemActionType */ any, dispatch) {
188
+ const { id } = action.input;
189
+
190
+ // Return a new state object
191
+ return {
192
+ ...state,
193
+ items: state.items.filter(item => item.id !== id), // Create a new array excluding the item to delete
194
+ };
226
195
  }
227
196
  ```
228
197
 
229
- - We use `filter` to create a new array without the deleted item
230
- - Immer handles making this immutable
198
+ **Explanation**:
199
+
200
+ - We use the `filter` array method, which returns a _new_ array containing only the elements for which the callback function returns `true`.
231
201
 
232
202
  ## Leveraging generated types
233
203
 
234
- As highlighted in [Using the Document Model Generator](04-UseTheDocumentModelGenerator.md), `ph generate` produces TypeScript types for your state (e.g., `TodoListState`, `TodoItem`) and the inputs for your operations (e.g., `AddTodoItemInput`, `UpdateTodoItemInput`).
204
+ As highlighted in [Using the Document Model Generator](04-UseTheDocumentModelGenerator.md), `ph generate` produces TypeScript types for your state (e.g., `ToDoListState`, `ToDoItem`) and the inputs for your operations (e.g., `AddTodoItemInput`, `UpdateTodoItemInput`).
235
205
 
236
206
  **Always use these generated types in your reducer implementations!**
237
207
 
238
208
  ```typescript
239
- import { generateId } from "document-model/core";
240
- import type { TodoListTodosOperations } from "todo-tutorial/document-models/todo-list";
241
-
242
- export const todoListTodosOperations: TodoListTodosOperations = {
243
- addTodoItemOperation(state, action) {
244
- // TypeScript knows action.input has { text: string }
245
- const id = generateId();
246
- state.items.push({ id, text: action.input.text, checked: false });
209
+ import {
210
+ ToDoListState,
211
+ AddTodoItemInput, // Generated input type
212
+ // ... other types
213
+ } from "../../gen/types.js";
214
+ import { ToDoListToDoListOperations } from "../../gen/to-do-list/operations.js"; // Generated operations type
215
+
216
+ // Define the type for the action more explicitly if needed, or rely on inferred types
217
+ // from ToDoListToDoListOperations. For complex actions, defining specific action types can be beneficial.
218
+ // For example:
219
+ // interface AddTodoItemAction {
220
+ // type: 'ADD_TODO_ITEM'; // Or the specific string constant used by the action creator
221
+ // input: AddTodoItemInput;
222
+ // }
223
+
224
+ export const reducer: ToDoListToDoListOperations = {
225
+ addTodoItemOperation(
226
+ state: ToDoListState,
227
+ action: { input: AddTodoItemInput /* plus type property */ },
228
+ dispatch,
229
+ ) {
230
+ // Now 'action.input.text' and 'action.input.id' are type-checked
231
+ const newItem = {
232
+ id: action.input.id,
233
+ text: action.input.text,
234
+ checked: false,
235
+ };
236
+ return {
237
+ ...state,
238
+ items: [...state.items, newItem],
239
+ };
247
240
  },
248
241
  // ... other reducers
249
242
  };
@@ -255,99 +248,101 @@ Using these types provides:
255
248
  - **Autocompletion and IntelliSense**: Improved developer experience in your IDE.
256
249
  - **Clearer code**: Types serve as documentation for the expected data structures.
257
250
 
258
- ## Practical implementation: Writing the `TodoList` reducers
251
+ ## Practical implementation: Writing the `ToDoList` reducers
259
252
 
260
- Now that you understand the principles, let's put them into practice by implementing the reducers for our `TodoList` document model.
253
+ Now that you understand the principles, let's put them into practice by implementing the reducers for our `ToDoList` document model.
261
254
 
262
255
  <details>
263
- <summary>Tutorial: Implementing the TodoList reducers</summary>
256
+ <summary>Tutorial: Implementing the ToDoList reducers</summary>
264
257
 
265
- This tutorial assumes you have followed the steps in the previous chapters, especially using `ph generate TodoList.phd` to scaffold your document model's code.
258
+ This tutorial assumes you have followed the steps in the previous chapters, especially using `ph generate ToDoList.phdm.zip` to scaffold your document model's code.
266
259
 
267
260
  ### Implement the operation reducers
268
261
 
269
- Navigate to `document-models/todo-list/src/reducers/todos.ts`. The generator will have created a skeleton file. Replace its contents with the following logic.
270
-
271
- **Basic version (without stats):**
272
-
273
- ```typescript
274
- import { generateId } from "document-model/core";
275
- import type { TodoListTodosOperations } from "todo-tutorial/document-models/todo-list";
276
-
277
- export const todoListTodosOperations: TodoListTodosOperations = {
278
- addTodoItemOperation(state, action) {
279
- const id = generateId();
280
- state.items.push({ ...action.input, id, checked: false });
281
- },
282
-
283
- updateTodoItemOperation(state, action) {
284
- const item = state.items.find((item) => item.id === action.input.id);
285
- if (!item) return;
286
-
287
- item.text = action.input.text ?? item.text;
288
- item.checked = action.input.checked ?? item.checked;
289
- },
290
-
291
- deleteTodoItemOperation(state, action) {
292
- state.items = state.items.filter((item) => item.id !== action.input.id);
293
- },
294
- };
295
- ```
296
-
297
- **Advanced version (with stats):**
262
+ Navigate to `document-models/to-do-list/src/reducers/to-do-list.ts`. The generator will have created a skeleton file. Replace its contents with the following logic.
298
263
 
299
264
  ```typescript
300
- import { generateId } from "document-model/core";
301
- import type { TodoListTodosOperations } from "todo-tutorial/document-models/todo-list";
302
-
303
- export const todoListTodosOperations: TodoListTodosOperations = {
304
- addTodoItemOperation(state, action) {
305
- const id = generateId();
306
-
265
+ import { ToDoListToDoListOperations } from "../../gen/to-do-list/operations.js";
266
+ import { ToDoListState } from "../../gen/types.js"; // Assuming this now includes the 'stats' object
267
+
268
+ // REMARKS: This is our main reducer object. It implements all operations defined in the schema.
269
+ // The ToDoListToDoListOperations type is auto-generated from our GraphQL specification and ensures type safety.
270
+ export const reducer: ToDoListToDoListOperations = {
271
+ // REMARKS: The addTodoItemOperation adds a new item and updates our tracking statistics.
272
+ // - state: The current document state. Powerhouse uses a library like Immer.js,
273
+ // so you can write code that looks like it's mutating the state directly.
274
+ // Behind the scenes, Powerhouse ensures this results in an immutable update.
275
+ // - action: Contains the operation's 'type' and 'input' data from the client.
276
+ // - dispatch: A function to trigger subsequent operations (advanced, not used here).
277
+ addTodoItemOperation(state, action, dispatch) {
278
+ // REMARKS: We update our statistics for total and unchecked items.
307
279
  state.stats.total += 1;
308
280
  state.stats.unchecked += 1;
309
281
 
282
+ // REMARKS: We push the new to-do item into the items array.
283
+ // The data for the new item comes from the operation's input.
310
284
  state.items.push({
311
- id,
285
+ id: action.input.id,
312
286
  text: action.input.text,
313
- checked: false,
287
+ checked: false, // New items always start as unchecked.
314
288
  });
315
289
  },
316
290
 
317
- updateTodoItemOperation(state, action) {
291
+ // REMARKS: The updateTodoItemOperation modifies an existing to-do item.
292
+ // It handles partial updates for text and checked status.
293
+ updateTodoItemOperation(state, action, dispatch) {
294
+ // REMARKS: First, we find the specific item we want to update using its ID.
318
295
  const item = state.items.find((item) => item.id === action.input.id);
296
+
297
+ // REMARKS: It's good practice to handle cases where the item might not be found.
319
298
  if (!item) {
320
299
  throw new Error(`Item with id ${action.input.id} not found`);
321
300
  }
322
301
 
323
- if (action.input.text !== undefined) {
302
+ // REMARKS: We only update the text if it was provided in the input.
303
+ // This allows for partial updates (e.g., just checking an item without changing its text).
304
+ if (action.input.text) {
324
305
  item.text = action.input.text;
325
306
  }
326
307
 
327
- if (action.input.checked !== undefined && action.input.checked !== item.checked) {
328
- if (action.input.checked) {
329
- state.stats.unchecked -= 1;
330
- state.stats.checked += 1;
331
- } else {
332
- state.stats.unchecked += 1;
333
- state.stats.checked -= 1;
334
- }
308
+ // REMARKS: When the checked status changes, we also update our statistics.
309
+ // We check for `true` and `false` explicitly.
310
+ if (action.input.checked) {
311
+ // This is true only if action.input.checked is true
312
+ // Note: This assumes the item was previously unchecked. For a more robust implementation,
313
+ // you could check `if (item.checked === false)` before updating stats to prevent inconsistencies.
314
+ state.stats.unchecked -= 1;
315
+ state.stats.checked += 1;
316
+ item.checked = action.input.checked;
317
+ }
318
+ if (action.input.checked === false) {
319
+ // Note: This assumes the item was previously checked.
320
+ state.stats.unchecked += 1;
321
+ state.stats.checked -= 1;
335
322
  item.checked = action.input.checked;
336
323
  }
337
324
  },
338
325
 
339
- deleteTodoItemOperation(state, action) {
326
+ // REMARKS: The deleteTodoItemOperation removes an item from the list.
327
+ deleteTodoItemOperation(state, action, dispatch) {
328
+ // REMARKS: Before removing the item, we find it to determine its checked status.
329
+ // This is necessary to correctly decrement our statistics.
340
330
  const item = state.items.find((item) => item.id === action.input.id);
341
331
 
342
- if (item) {
343
- state.stats.total -= 1;
344
- if (item.checked) {
345
- state.stats.checked -= 1;
346
- } else {
347
- state.stats.unchecked -= 1;
348
- }
332
+ // REMARKS: We always decrement the total count.
333
+ state.stats.total -= 1;
334
+
335
+ // REMARKS: We then decrement the 'checked' or 'unchecked' count based on the item's status.
336
+ if (item?.checked) {
337
+ // This is shorthand for item?.checked === true
338
+ state.stats.checked -= 1;
339
+ }
340
+ if (item?.checked === false) {
341
+ state.stats.unchecked -= 1;
349
342
  }
350
343
 
344
+ // REMARKS: Finally, we create a new 'items' array that excludes the deleted item.
345
+ // Assigning to 'state.items' is handled by Powerhouse to produce a new immutable state.
351
346
  state.items = state.items.filter((item) => item.id !== action.input.id);
352
347
  },
353
348
  };