@powerhousedao/academy 5.1.0-staging.0 → 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 (76) hide show
  1. package/CHANGELOG.md +46 -1148
  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 +10 -155
  7. package/docs/academy/01-GetStarted/02-DefineToDoListDocumentModel.md +35 -122
  8. package/docs/academy/01-GetStarted/03-ImplementOperationReducers.md +155 -178
  9. package/docs/academy/01-GetStarted/04-BuildToDoListEditor.md +218 -0
  10. package/docs/academy/{02-MasteryTrack/01-BuilderEnvironment → 01-GetStarted}/05-VetraStudio.md +22 -62
  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 +79 -195
  26. package/docs/academy/03-ExampleUsecases/Chatroom/04-ImplementOperationReducers.md +241 -435
  27. package/docs/academy/03-ExampleUsecases/Chatroom/05-ImplementChatroomEditor.md +27 -388
  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 +13 -49
  40. package/src/css/custom.css +18 -26
  41. package/docs/academy/01-GetStarted/04-WriteDocumentModelTests.md +0 -425
  42. package/docs/academy/01-GetStarted/05-BuildToDoListEditor.md +0 -557
  43. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/images/Modules.png +0 -0
  44. package/docs/academy/02-MasteryTrack/01-BuilderEnvironment/images/VetraStudioDrive.png +0 -0
  45. package/docs/academy/02-MasteryTrack/05-Launch/05-DockerDeployment.md +0 -384
  46. package/docs/academy/03-ExampleUsecases/TodoList/00-GetTheStarterCode.md +0 -24
  47. package/docs/academy/03-ExampleUsecases/TodoList/01-GenerateTodoListDocumentModel.md +0 -211
  48. package/docs/academy/03-ExampleUsecases/TodoList/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
  49. package/docs/academy/03-ExampleUsecases/TodoList/03-AddTestsForTodoListActions.md +0 -462
  50. package/docs/academy/03-ExampleUsecases/TodoList/04-GenerateTodoListDocumentEditor.md +0 -45
  51. package/docs/academy/03-ExampleUsecases/TodoList/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
  52. package/docs/academy/03-ExampleUsecases/TodoList/06-GenerateTodoDriveExplorer.md +0 -61
  53. package/docs/academy/03-ExampleUsecases/TodoList/07-AddSharedComponentForShowingTodoListStats.md +0 -384
  54. package/docs/academy/03-ExampleUsecases/TodoList/_category_.json +0 -8
  55. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/VetraPackageLibrary.md +0 -7
  56. package/docs/academy/03-ExampleUsecases/VetraPackageLibrary/_category_.json +0 -9
  57. package/docs/academy/04-APIReferences/06-VetraRemoteDrive.md +0 -160
  58. package/docs/academy/04-APIReferences/renown-sdk/00-Overview.md +0 -316
  59. package/docs/academy/04-APIReferences/renown-sdk/01-Authentication.md +0 -672
  60. package/docs/academy/04-APIReferences/renown-sdk/02-APIReference.md +0 -957
  61. package/docs/academy/04-APIReferences/renown-sdk/_category_.json +0 -8
  62. package/docs/academy/10-TodoListTutorial/02-ImplementTodoListDocumentModelReducerOperationHandlers.md +0 -171
  63. package/docs/academy/10-TodoListTutorial/03-AddTestsForTodoListActions.md +0 -462
  64. package/docs/academy/10-TodoListTutorial/05-ImplementTodoListDocumentEditorUIComponents.md +0 -422
  65. package/docs/academy/10-TodoListTutorial/07-AddSharedComponentForShowingTodoListStats.md +0 -370
  66. package/static/img/Vetra-logo-dark.svg +0 -11
  67. package/static/img/vetra-logo-light.svg +0 -11
  68. /package/docs/academy/02-MasteryTrack/03-BuildingUserExperiences/06-DocumentTools/{02-RevisionHistoryTimeline/360/237/232/247" → 02-RevisionHistoryTimeline} +0 -0
  69. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /01-WhatIsADocumentModel" → 01-WhatIsADocumentModel} +0 -0
  70. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-DAOandDocumentsModelsQ+A" → 02-DAOandDocumentsModelsQ+A} +0 -0
  71. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /02-domain-modeling" → 02-domain-modeling} +0 -0
  72. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /03-BenefitsOfDocumentModels" → 03-BenefitsOfDocumentModels} +0 -0
  73. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /04-UtilitiesAndTips" → 04-UtilitiesAndTips} +0 -0
  74. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /05-best-practices" → 05-best-practices} +0 -0
  75. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /_category_.json" → _category_.json} +0 -0
  76. /package/docs/academy/05-Architecture/05-DocumentModelTheory/{360/237/232/247 /three-data-layers.png" → three-data-layers.png} +0 -0
@@ -1,425 +0,0 @@
1
- # Write document model tests
2
-
3
- :::tip Tutorial Repository
4
- 📦 **Reference Code**: [step-4-implement-tests-for-todos-operations](https://github.com/powerhouse-inc/todo-tutorial/tree/step-4-implement-tests-for-todos-operations)
5
-
6
- This step focuses on writing comprehensive tests for the reducers you implemented in the previous step.
7
- :::
8
-
9
- <details>
10
- <summary>📖 How to use this tutorial</summary>
11
-
12
- ### Compare your tests
13
-
14
- After writing tests:
15
-
16
- ```bash
17
- # Compare your tests with the reference
18
- git diff tutorial/step-4-implement-tests-for-todos-operations -- document-models/todo-list/src/tests/
19
-
20
- # View the reference test implementation
21
- git show tutorial/step-4-implement-tests-for-todos-operations:document-models/todo-list/src/tests/todos.test.ts
22
- ```
23
-
24
- ### Visual comparison with GitHub Desktop
25
-
26
- After committing your work, compare visually:
27
- 1. **Branch** menu → **"Compare to Branch..."**
28
- 2. Select `tutorial/step-4-implement-tests-for-todos-operations`
29
- 3. Review differences in the visual interface
30
-
31
- </details>
32
-
33
- In order to make sure the operation reducers are working as expected, you need to write tests for them. When you generated your document model code, we created some boilerplate tests for you. Now we'll enhance them to properly verify our reducer logic.
34
-
35
- ## Understanding the generated test file
36
-
37
- Navigate to `/document-models/todo-list/src/tests/todos.test.ts`. You will see that we have some basic "sanity check" style tests already. These make sure that your operations at least result in a valid document model state.
38
-
39
- ```typescript
40
- import { describe, it, expect } from "vitest";
41
- import { generateMock } from "@powerhousedao/codegen";
42
- import {
43
- reducer,
44
- utils,
45
- isTodoListDocument,
46
- addTodoItem,
47
- AddTodoItemInputSchema,
48
- updateTodoItem,
49
- UpdateTodoItemInputSchema,
50
- deleteTodoItem,
51
- DeleteTodoItemInputSchema,
52
- } from "todo-tutorial/document-models/todo-list";
53
-
54
- describe("Todos Operations", () => {
55
- it("should handle addTodoItem operation", () => {
56
- // The `createDocument` utility function from your document model creates
57
- // a new empty document, i.e. one with your default initial state
58
- const document = utils.createDocument();
59
-
60
- // The generateMock function takes one of your generated input schemas
61
- // and creates an object populated with random values for each field
62
- const input = generateMock(AddTodoItemInputSchema());
63
-
64
- // We call your document model's reducer with the new document we just created
65
- // and the action we want to test, `addTodoItem` in this case.
66
- // The reducer returns a new object, which is the document with the action applied.
67
- // If successful, there will be an operation which corresponds to this action
68
- // in the updated document's operations list.
69
- const updatedDocument = reducer(document, addTodoItem(input));
70
-
71
- // When you generate a document model, we give you validation utilities like
72
- // `isTodoListDocument` which confirms the document is of the correct form
73
- // in a way that TypeScript recognizes
74
- expect(isTodoListDocument(updatedDocument)).toBe(true);
75
-
76
- // At the start a document will have 0 operations, so after applying this action
77
- // there should now be one operation
78
- expect(updatedDocument.operations.global).toHaveLength(1);
79
-
80
- // The operation added to the list should correspond to the correct action type
81
- expect(updatedDocument.operations.global[0].action.type).toBe("ADD_TODO_ITEM");
82
- });
83
-
84
- it("should handle updateTodoItem operation", () => {
85
- // ... boilerplate test
86
- });
87
-
88
- it("should handle deleteTodoItem operation", () => {
89
- // ... boilerplate test
90
- });
91
- });
92
- ```
93
-
94
- ## Enhance the tests
95
-
96
- The boilerplate tests check that operations are applied, but they don't verify the **actual results**. Let's write more comprehensive tests.
97
-
98
- ### Test 1: Update the addTodoItem test
99
-
100
- The add test is already fairly complete. We just need to add type annotations:
101
-
102
- ```typescript
103
- it("should handle addTodoItem operation", () => {
104
- const document = utils.createDocument();
105
- // added-line
106
- const input: AddTodoItemInput = generateMock(AddTodoItemInputSchema());
107
-
108
- const updatedDocument = reducer(document, addTodoItem(input));
109
- expect(isTodoListDocument(updatedDocument)).toBe(true);
110
-
111
- expect(updatedDocument.operations.global).toHaveLength(1);
112
- expect(updatedDocument.operations.global[0].action.type).toBe("ADD_TODO_ITEM");
113
- expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
114
- expect(updatedDocument.operations.global[0].index).toEqual(0);
115
- });
116
- ```
117
-
118
- ### Test 2: Replace the updateTodoItem test
119
-
120
- Delete the existing boilerplate and add two separate tests - one for updating text, one for updating the checked state:
121
-
122
- ```typescript
123
- // removed-start
124
- it("should handle updateTodoItem operation", () => {
125
- const document = utils.createDocument();
126
- const input = generateMock(UpdateTodoItemInputSchema());
127
- const updatedDocument = reducer(document, updateTodoItem(input));
128
- expect(isTodoListDocument(updatedDocument)).toBe(true);
129
- // ...
130
- });
131
- // removed-end
132
- ```
133
-
134
- **Add the new test for updating text:**
135
-
136
- ```typescript
137
- it("should handle updateTodoItem operation to update text", () => {
138
- // We need there to already be a todo item in the document,
139
- // since we want to test updating an existing item
140
- const mockItem = generateMock(TodoItemSchema());
141
-
142
- // We also need to generate a mock input for the update operation we are testing
143
- const input: UpdateTodoItemInput = generateMock(UpdateTodoItemInputSchema());
144
-
145
- // Since the mocks are generated with random values, we need to set the `id`
146
- // on our mock input to match the `id` of the existing mock item
147
- input.id = mockItem.id;
148
-
149
- // We want to easily check if the item's text was updated to our new value,
150
- // so we assign a variable and use that for the mock input's text field
151
- const newText = "new text";
152
- input.text = newText;
153
-
154
- // We are only testing updating the text here, so we want the checked field
155
- // on the input to be undefined, i.e. it should not change anything on the existing item
156
- input.checked = undefined;
157
-
158
- // We can pass a different initial state to the `createDocument` utility,
159
- // so in this case we pass in an `items` array with our existing item already in it
160
- const document = utils.createDocument({
161
- global: {
162
- items: [mockItem],
163
- },
164
- });
165
-
166
- // Create an updated document by applying the reducer with the action and input
167
- const updatedDocument = reducer(document, updateTodoItem(input));
168
-
169
- // Use our validator to check that the document conforms to the document model schema
170
- expect(isTodoListDocument(updatedDocument)).toBe(true);
171
-
172
- // There should now be one operation in the operations list
173
- expect(updatedDocument.operations.global).toHaveLength(1);
174
- expect(updatedDocument.operations.global[0].action.type).toBe("UPDATE_TODO_ITEM");
175
-
176
- // Find the updated item in the items list by its `id`
177
- const updatedItem = updatedDocument.state.global.items.find(
178
- (item) => item.id === input.id,
179
- );
180
-
181
- // The item's text should now be updated to be our new text
182
- expect(updatedItem?.text).toBe(newText);
183
-
184
- // The item's `checked` field should be unchanged
185
- expect(updatedItem?.checked).toBe(mockItem.checked);
186
- });
187
- ```
188
-
189
- **Add the new test for updating checked state:**
190
-
191
- ```typescript
192
- it("should handle updateTodoItem operation to update checked", () => {
193
- // Generate a mock existing item
194
- const mockItem = generateMock(TodoItemSchema());
195
-
196
- // Generate a mock input
197
- const input: UpdateTodoItemInput = generateMock(UpdateTodoItemInputSchema());
198
-
199
- // Set the mock input's `id` to the mock item's `id`
200
- input.id = mockItem.id;
201
-
202
- // We want the new `checked` field value to be the opposite of the
203
- // randomly generated value from the mock
204
- const newChecked = !mockItem.checked;
205
- input.checked = newChecked;
206
-
207
- // Leave the `text` field unchanged
208
- input.text = undefined;
209
-
210
- // Create a document with the existing item in it
211
- const document = utils.createDocument({
212
- global: {
213
- items: [mockItem],
214
- },
215
- });
216
-
217
- // Apply the reducer with the action and the mock input
218
- const updatedDocument = reducer(document, updateTodoItem(input));
219
-
220
- // Validate your document
221
- expect(isTodoListDocument(updatedDocument)).toBe(true);
222
-
223
- // Get the updated item by its `id`
224
- const updatedItem = updatedDocument.state.global.items.find(
225
- (item) => item.id === input.id,
226
- );
227
-
228
- // The item's `text` field should remain unchanged
229
- expect(updatedItem?.text).toBe(mockItem.text);
230
-
231
- // The item's `checked` field should be updated to our new checked value
232
- expect(updatedItem?.checked).toBe(newChecked);
233
- });
234
- ```
235
-
236
- ### Test 3: Update the deleteTodoItem test
237
-
238
- The boilerplate delete test passes even without an existing item to delete. Let's fix that:
239
-
240
- ```typescript
241
- it("should handle deleteTodoItem operation", () => {
242
- // removed-start
243
- const document = utils.createDocument();
244
- const input = generateMock(DeleteTodoItemInputSchema());
245
- // removed-end
246
- // added-start
247
- // Create an existing item to delete
248
- const mockItem = generateMock(TodoItemSchema());
249
- const document = utils.createDocument({
250
- global: {
251
- items: [mockItem],
252
- },
253
- });
254
- const input: DeleteTodoItemInput = generateMock(DeleteTodoItemInputSchema());
255
- input.id = mockItem.id;
256
- // added-end
257
-
258
- const updatedDocument = reducer(document, deleteTodoItem(input));
259
- expect(isTodoListDocument(updatedDocument)).toBe(true);
260
-
261
- expect(updatedDocument.operations.global).toHaveLength(1);
262
- expect(updatedDocument.operations.global[0].action.type).toBe("DELETE_TODO_ITEM");
263
- expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
264
- expect(updatedDocument.operations.global[0].index).toEqual(0);
265
-
266
- // added-start
267
- // Verify the item was actually removed
268
- const updatedItems = updatedDocument.state.global.items;
269
- expect(updatedItems).toHaveLength(0);
270
- // added-end
271
- });
272
- ```
273
-
274
- ## Complete test file
275
-
276
- Here's the complete test file with all updates. Don't forget to add the missing imports:
277
-
278
- <details>
279
- <summary>Complete todos.test.ts</summary>
280
-
281
- ```typescript
282
- import { describe, it, expect } from "vitest";
283
- import { generateMock } from "@powerhousedao/codegen";
284
- import type {
285
- AddTodoItemInput,
286
- DeleteTodoItemInput,
287
- UpdateTodoItemInput,
288
- } from "todo-tutorial/document-models/todo-list";
289
- import {
290
- reducer,
291
- utils,
292
- isTodoListDocument,
293
- addTodoItem,
294
- AddTodoItemInputSchema,
295
- updateTodoItem,
296
- UpdateTodoItemInputSchema,
297
- deleteTodoItem,
298
- DeleteTodoItemInputSchema,
299
- TodoItemSchema,
300
- } from "todo-tutorial/document-models/todo-list";
301
-
302
- describe("Todos Operations", () => {
303
- it("should handle addTodoItem operation", () => {
304
- const document = utils.createDocument();
305
- const input: AddTodoItemInput = generateMock(AddTodoItemInputSchema());
306
-
307
- const updatedDocument = reducer(document, addTodoItem(input));
308
- expect(isTodoListDocument(updatedDocument)).toBe(true);
309
-
310
- expect(updatedDocument.operations.global).toHaveLength(1);
311
- expect(updatedDocument.operations.global[0].action.type).toBe("ADD_TODO_ITEM");
312
- expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
313
- expect(updatedDocument.operations.global[0].index).toEqual(0);
314
- });
315
-
316
- it("should handle updateTodoItem operation to update text", () => {
317
- const mockItem = generateMock(TodoItemSchema());
318
- const input: UpdateTodoItemInput = generateMock(UpdateTodoItemInputSchema());
319
- input.id = mockItem.id;
320
- const newText = "new text";
321
- input.text = newText;
322
- input.checked = undefined;
323
-
324
- const document = utils.createDocument({
325
- global: {
326
- items: [mockItem],
327
- },
328
- });
329
-
330
- const updatedDocument = reducer(document, updateTodoItem(input));
331
- expect(isTodoListDocument(updatedDocument)).toBe(true);
332
-
333
- expect(updatedDocument.operations.global).toHaveLength(1);
334
- expect(updatedDocument.operations.global[0].action.type).toBe("UPDATE_TODO_ITEM");
335
-
336
- const updatedItem = updatedDocument.state.global.items.find(
337
- (item) => item.id === input.id,
338
- );
339
- expect(updatedItem?.text).toBe(newText);
340
- expect(updatedItem?.checked).toBe(mockItem.checked);
341
- });
342
-
343
- it("should handle updateTodoItem operation to update checked", () => {
344
- const mockItem = generateMock(TodoItemSchema());
345
- const input: UpdateTodoItemInput = generateMock(UpdateTodoItemInputSchema());
346
- input.id = mockItem.id;
347
- const newChecked = !mockItem.checked;
348
- input.checked = newChecked;
349
- input.text = undefined;
350
-
351
- const document = utils.createDocument({
352
- global: {
353
- items: [mockItem],
354
- },
355
- });
356
-
357
- const updatedDocument = reducer(document, updateTodoItem(input));
358
- expect(isTodoListDocument(updatedDocument)).toBe(true);
359
-
360
- const updatedItem = updatedDocument.state.global.items.find(
361
- (item) => item.id === input.id,
362
- );
363
- expect(updatedItem?.text).toBe(mockItem.text);
364
- expect(updatedItem?.checked).toBe(newChecked);
365
- });
366
-
367
- it("should handle deleteTodoItem operation", () => {
368
- const mockItem = generateMock(TodoItemSchema());
369
- const document = utils.createDocument({
370
- global: {
371
- items: [mockItem],
372
- },
373
- });
374
-
375
- const input: DeleteTodoItemInput = generateMock(DeleteTodoItemInputSchema());
376
- input.id = mockItem.id;
377
-
378
- const updatedDocument = reducer(document, deleteTodoItem(input));
379
- expect(isTodoListDocument(updatedDocument)).toBe(true);
380
-
381
- expect(updatedDocument.operations.global).toHaveLength(1);
382
- expect(updatedDocument.operations.global[0].action.type).toBe("DELETE_TODO_ITEM");
383
-
384
- const updatedItems = updatedDocument.state.global.items;
385
- expect(updatedItems).toHaveLength(0);
386
- });
387
- });
388
- ```
389
-
390
- </details>
391
-
392
- :::tip Check your work
393
-
394
- To make sure everything works as expected:
395
-
396
- ```bash
397
- # Check types compile correctly
398
- pnpm tsc
399
-
400
- # Check linting passes
401
- pnpm lint
402
-
403
- # Run tests
404
- pnpm test
405
-
406
- # Compare with reference implementation
407
- git diff tutorial/step-4-implement-tests-for-todos-operations -- document-models/todo-list/src/tests/
408
- ```
409
-
410
- Expected test output:
411
-
412
- ```bash
413
- ✓ document-models/todo-list/src/tests/document-model.test.ts (3 tests) 1ms
414
- ✓ document-models/todo-list/src/tests/todos.test.ts (4 tests) 8ms
415
-
416
- Test Files 2 passed (2)
417
- Tests 7 passed (7)
418
- ```
419
-
420
- :::
421
-
422
- ## Up next: Building the editor
423
-
424
- In the next chapter, you'll learn how to implement a user interface (editor) for your document model so you can interact with it visually.
425
-