@powerhousedao/academy 4.1.0-dev.2 → 4.1.0-dev.20

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,3 +1,143 @@
1
+ ## 4.1.0-dev.20 (2025-08-15)
2
+
3
+ This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
4
+
5
+ ## 4.1.0-dev.19 (2025-08-14)
6
+
7
+ ### 🩹 Fixes
8
+
9
+ - **academy:** subgraph example ([ae3e24458](https://github.com/powerhouse-inc/powerhouse/commit/ae3e24458))
10
+
11
+ ### ❤️ Thank You
12
+
13
+ - Frank
14
+
15
+ ## 4.1.0-dev.18 (2025-08-14)
16
+
17
+ This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
18
+
19
+ ## 4.1.0-dev.17 (2025-08-12)
20
+
21
+ ### 🚀 Features
22
+
23
+ - refactor vetra command and remove vetra deps in connect and reactor ([#1753](https://github.com/powerhouse-inc/powerhouse/pull/1753))
24
+
25
+ ### ❤️ Thank You
26
+
27
+ - Guillermo Puente Sandoval @gpuente
28
+
29
+ ## 4.1.0-dev.16 (2025-08-12)
30
+
31
+ This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
32
+
33
+ ## 4.1.0-dev.15 (2025-08-12)
34
+
35
+ ### 🚀 Features
36
+
37
+ - **reactor-mcp,reactor-api,reactor-local,switchboard,ph-cli:** run mcp on express app ([d51fa590e](https://github.com/powerhouse-inc/powerhouse/commit/d51fa590e))
38
+
39
+ ### ❤️ Thank You
40
+
41
+ - acaldas @acaldas
42
+
43
+ ## 4.1.0-dev.14 (2025-08-11)
44
+
45
+ ### 🚀 Features
46
+
47
+ - update document engineering dep ([54dcee90d](https://github.com/powerhouse-inc/powerhouse/commit/54dcee90d))
48
+
49
+ ### ❤️ Thank You
50
+
51
+ - acaldas @acaldas
52
+
53
+ ## 4.1.0-dev.13 (2025-08-09)
54
+
55
+ This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
56
+
57
+ ## 4.1.0-dev.12 (2025-08-08)
58
+
59
+ This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
60
+
61
+ ## 4.1.0-dev.11 (2025-08-07)
62
+
63
+ ### 🚀 Features
64
+
65
+ - **switchboard,reactor-local,reactor-api:** moved vite loader to reactor-api package ([c84f0a2a3](https://github.com/powerhouse-inc/powerhouse/commit/c84f0a2a3))
66
+ - vetra package documents and app integration ([0e4053302](https://github.com/powerhouse-inc/powerhouse/commit/0e4053302))
67
+ - **vetra:** added vetra drive editor ([4ebafd143](https://github.com/powerhouse-inc/powerhouse/commit/4ebafd143))
68
+ - **ph-cli:** added verbose option to vetra command ([7310ec06c](https://github.com/powerhouse-inc/powerhouse/commit/7310ec06c))
69
+ - integrate package documents into reactor system ([939fe8e80](https://github.com/powerhouse-inc/powerhouse/commit/939fe8e80))
70
+ - **connect:** integrate Vetra package documents and editors ([2ecb9bd15](https://github.com/powerhouse-inc/powerhouse/commit/2ecb9bd15))
71
+
72
+ ### ❤️ Thank You
73
+
74
+ - acaldas @acaldas
75
+ - Guillermo Puente @gpuente
76
+ - Guillermo Puente Sandoval @gpuente
77
+
78
+ ## 4.1.0-dev.10 (2025-08-07)
79
+
80
+ ### 🚀 Features
81
+
82
+ - **builder-tools,codegen,design-system,reactor-api:** updated document-engineering version ([e74068b43](https://github.com/powerhouse-inc/powerhouse/commit/e74068b43))
83
+
84
+ ### ❤️ Thank You
85
+
86
+ - acaldas @acaldas
87
+
88
+ ## 4.1.0-dev.9 (2025-08-07)
89
+
90
+ This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
91
+
92
+ ## 4.1.0-dev.8 (2025-08-06)
93
+
94
+ ### 🚀 Features
95
+
96
+ - **switchboard,config,reactor-api:** handle auth in reactor-api ([f33c921ee](https://github.com/powerhouse-inc/powerhouse/commit/f33c921ee))
97
+
98
+ ### ❤️ Thank You
99
+
100
+ - acaldas @acaldas
101
+
102
+ ## 4.1.0-dev.7 (2025-08-06)
103
+
104
+ This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
105
+
106
+ ## 4.1.0-dev.6 (2025-08-06)
107
+
108
+ ### 🚀 Features
109
+
110
+ - **reactor-mcp:** load local document models and reload when they change ([0408a017c](https://github.com/powerhouse-inc/powerhouse/commit/0408a017c))
111
+ - **reactor-local,reactor-api,document-drive:** reload local document models when they change ([5d9af3951](https://github.com/powerhouse-inc/powerhouse/commit/5d9af3951))
112
+
113
+ ### ❤️ Thank You
114
+
115
+ - acaldas @acaldas
116
+
117
+ ## 4.1.0-dev.5 (2025-08-05)
118
+
119
+ This was a version bump only for @powerhousedao/academy to align it with other projects, there were no code changes.
120
+
121
+ ## 4.1.0-dev.4 (2025-08-02)
122
+
123
+ ### 🚀 Features
124
+
125
+ - ts morph integration ([#1729](https://github.com/powerhouse-inc/powerhouse/pull/1729))
126
+
127
+ ### ❤️ Thank You
128
+
129
+ - Guillermo Puente Sandoval @gpuente
130
+
131
+ ## 4.1.0-dev.3 (2025-08-01)
132
+
133
+ ### 🚀 Features
134
+
135
+ - **reactor-mcp:** setup of modular reactor tools ([ceab98b08](https://github.com/powerhouse-inc/powerhouse/commit/ceab98b08))
136
+
137
+ ### ❤️ Thank You
138
+
139
+ - acaldas @acaldas
140
+
1
141
  ## 4.1.0-dev.2 (2025-07-31)
2
142
 
3
143
  ### 🚀 Features
@@ -74,6 +74,29 @@ You have several options for styling your editor components:
74
74
 
75
75
  Choose the method or combination of methods that best suits your project needs and team preferences. Connect Studio (`ph connect`) will allow you to see your styles applied in real-time.
76
76
 
77
+ :::warning **Best practices for consistent reliable styles**
78
+
79
+ In any package the styles are being generated through the styles.css file with the help of the tailwindcss/cli package.
80
+
81
+ **1. Centralize style imports**
82
+
83
+ - Do not import styles directly in .tsx files.
84
+ - This works in development mode but will not be picked up in static production builds.
85
+ - Move all style imports into your main styles.css file.
86
+
87
+ **2. Use file imports instead of URL imports**
88
+
89
+ - @import url("...") → **Incorrect**, Ignored by tailwindcss/cli
90
+ - @import "..." → **Correct**, resolves from local files or node_modules
91
+ - Always prefer the file import syntax.
92
+
93
+ **Using `ph install` includes package styles automatically**
94
+
95
+ - When installing a package with `ph install` on any instance, package styles are automatically added to styles.css. This ensures production builds always include the required package styles.
96
+ :::
97
+
98
+
99
+
77
100
  ### State management in editors
78
101
 
79
102
  When you build an editor in Powerhouse, your main editor component receives `EditorProps`. These props are crucial for interacting with the document:
@@ -29,41 +29,40 @@ A subgraph in Powerhouse is a **GraphQL-based modular data component** that exte
29
29
 
30
30
 
31
31
 
32
- ```typescript title="Example of a context field"
33
- context: {
34
- admin: async (session) => {
35
- const admins = await operationalStore.get("admins");
36
- return admins.includes(session.user);
37
- }
38
- }
39
- ```
32
+ ## Example: Implement a search subgraph based on data from the reactor
33
+
34
+ In this example we implement a subgraph which allows to search through todo-list documents in a specific document drive.
40
35
 
41
- ## 1. How to generate a subgraph
36
+ First we will generate the subgraph with the help of the ph cli, then we will define the GraphQL schema and implement the resolvers and finally we will start the reactor and execute a query through the GraphQL Gateway.
37
+
38
+ ### 1. Generate the subgraph
42
39
 
43
40
  Let's start by generating a new subgraph. For our tutorial we will create a new subgraph within our To-do List project.
44
41
  Open your project and start your terminal.
45
42
  The Powerhouse toolkit provides a command-line utility to create new subgraphs easily.
46
43
 
47
44
  ```bash title="Run the following command to generate a new subgraph"
48
- ph generate --subgraph to-do-list
45
+ ph generate --subgraph search-todos
49
46
  ```
50
47
 
51
48
  ```bash title="Expected Output"
52
- Loaded templates: node_modules/@powerhousedao/codegen/dist/codegen/.hygen/templates
53
- FORCED: ./subgraphs/to-do-list/index.ts
49
+ Loaded templates: /projects/powerhouse/powerhouse/packages/codegen/dist/src/codegen/.hygen/templates
50
+ FORCED: ./subgraphs/search-todos/index.ts
54
51
  skipped: ./subgraphs/index.ts
55
52
  inject: ./subgraphs/index.ts
53
+
54
+ Loaded templates: /projects/powerhouse/powerhouse/packages/codegen/dist/src/codegen/.hygen/templates
55
+ FORCED: ./subgraphs/search-todos/resolvers.ts
56
+ FORCED: ./subgraphs/search-todos/schema.ts
56
57
  ```
57
58
 
58
59
  ### What happened?
59
- 1. A new subgraph was created in `./subgraphs/to-do-list/`
60
+ 1. A new subgraph was created in `./subgraphs/search-todos/`
60
61
  2. The subgraph was automatically registered in your project's registry
61
62
  3. Basic boilerplate code was generated with an example query
62
63
 
63
64
  If we now run `ph reactor` we will see the new subgraph being registered during the startup of the Reactor.
64
- > Registered /todolist subgraph.
65
-
66
- Alternatively, when you are running a local reactor with `ph reactor`, a series of subgraphs will automatically get registered, among those, one for the available document models in your Powerhouse project.
65
+ > Registered /graphql/search-todos subgraph.
67
66
 
68
67
  ```
69
68
  Initializing Subgraph Manager...
@@ -73,345 +72,69 @@ Initializing Subgraph Manager...
73
72
  > Registered /d/:drive subgraph.
74
73
  > Updating router
75
74
  > Registered /graphql supergraph
76
- > Registered /graphql/to-do-list subgraph.
75
+ > Registered /graphql/search-todos subgraph.
77
76
  > Updating router
78
77
  > Registered /graphql supergraph
79
78
  ➜ Reactor: http://localhost:4001/d/powerhouse
80
79
  ```
81
80
 
82
- ## 2. Building a to-do list subgraph
83
-
84
- Now that we've generated our subgraph, let's build a complete To-do List subgraph that extends the functionality of our To-do List document model. This subgraph will provide additional querying capabilities and demonstrate how subgraphs work with document models.
85
-
86
- ### 2.1 Understanding the to-do list document model
87
-
88
- Before building our subgraph, let's recall the structure of our To-do List document model from the [DocumentModelCreation tutorial](/academy/MasteryTrack/DocumentModelCreation/SpecifyTheStateSchema):
89
-
90
- ```graphql
91
- type ToDoListState {
92
- items: [ToDoItem!]!
93
- stats: ToDoListStats!
94
- }
95
-
96
- type ToDoItem {
97
- id: ID!
98
- text: String!
99
- checked: Boolean!
100
- }
101
-
102
- type ToDoListStats {
103
- total: Int!
104
- checked: Int!
105
- unchecked: Int
106
- }
107
- ```
108
-
109
- The document model has these operations:
110
- - `ADD_TODO_ITEM`: Adds a new to-do item
111
- - `UPDATE_TODO_ITEM`: Updates an existing to-do item
112
- - `DELETE_TODO_ITEM`: Deletes a to-do item
113
-
114
- ### 2.2 Define the subgraph schema
81
+ ## 2. Building a search subgraph
115
82
 
116
- Now let's create a subgraph that provides enhanced querying capabilities for our To-do List documents.
83
+ Now that we've generated our subgraph its tome to define the GraphQL schema and implement the resolvers.
117
84
 
118
- **Step 1: Define the schema in `subgraphs/to-do-list/schema.ts` by creating the file:**
85
+ **Step 1: Define the schema in `subgraphs/search-todos/schema.ts` by creating the file:**
119
86
 
120
87
  ```typescript
121
- export const typeDefs = `
122
- type Query {
123
- # Dashboard-style summary query - returns high-level metrics
124
- # Similar to ToDoListStats from document model but optimized for quick queries
125
- todoList: TodoListSummary
126
-
127
- # Filtered list query - lets you get items by completion status
128
- # More flexible than the basic document model - can filter checked/unchecked
129
- todoItems(checked: Boolean): [TodoItem!]!
130
-
131
- # Count-only query - when you just need numbers, not full data
132
- # Faster than getting full list when you only need totals for dashboards
133
- todoItemsCount(checked: Boolean): Int!
134
- }
135
-
136
- # This mirrors ToDoListStats from the document model
137
- # But it's a "view" optimized for summary reports and dashboards
138
- type TodoListSummary {
139
- total: Int! # Total number of items
140
- checked: Int! # Number of completed items
141
- unchecked: Int! # Number of pending items
142
- }
143
-
144
- # This matches the ToDoItem from the document model
145
- # Same data structure, but accessed through subgraph queries for filtering
146
- type TodoItem {
147
- id: ID! # Unique identifier
148
- text: String! # The task description
149
- checked: Boolean! # Completion status
150
- }
151
- }`
152
- ```
153
-
154
-
155
- <details>
156
- <summary> #### Understanding resolvers </summary>
88
+ import { gql } from "graphql-tag";
89
+ import type { DocumentNode } from "graphql";
157
90
 
158
- Before diving into the technical implementation, let's understand why these three different query types matter for your product.
159
- Think of resolvers as custom API endpoints that are automatically created based on what your users actually need to know about your data.
91
+ export const schema: DocumentNode = gql`
92
+ """
93
+ Subgraph definition
94
+ """
160
95
 
161
- When someone asks your system a question through GraphQL, the resolver:
162
-
163
- 1. **Understands the request** - "The user wants unchecked items"
164
- 2. **Knows where to get the data** - "I need to check the todo_items database table"
165
- 3. **Applies the right filters** - "Only get items where checked = false"
166
- 4. **Returns the answer** - "Here are the 5 unchecked items"
167
-
168
- **The three resolvers serve different business needs:**
169
-
170
- - **`todoList` Resolver - The Dashboard**
171
- - **Business value**: Perfect for executive dashboards or KPI displays
172
- - **Use case**: "We have 150 total tasks, 89 completed, 61 pending"
173
- - **Users**: Executives, managers, anyone needing high-level metrics
174
-
175
- - **`todoItems` Resolver - The Detailed List**
176
- - **Business value**: Great for operational views where people need to see actual tasks
177
- - **Use case**: "Show me all pending tasks" or "Show me everything"
178
- - **Users**: Workers, operators, anyone who needs to act on specific items
179
-
180
- - **`todoItemsCount` Resolver - The Counter**
181
- - **Business value**: Super fast for analytics or when you only need numbers
182
- - **Use case**: "How many completed tasks do we have?" → "47"
183
- - **Users**: Analysts, automated systems, performance dashboards
184
-
185
- **Why this architecture matters:**
186
- - **Performance**: Count queries are much faster than getting full lists when you only need numbers
187
- - **User Experience**: Different resolvers serve different user needs efficiently
188
- - **Flexibility**: Users can ask for exactly what they need, nothing more, nothing less
189
-
190
- </details>
191
-
192
- **Step 2: Create resolvers in `subgraphs/to-do-list/resolvers.ts`:**
193
-
194
- ```typescript
195
- // subgraphs/to-do-list/resolvers.ts
196
- interface SubgraphInstance {
197
- operationalStore: any;
96
+ type Query {
97
+ searchTodos(driveId: String!, searchTerm: String!): [String!]!
198
98
  }
199
99
 
200
- export const createResolvers = (subgraphInstance: SubgraphInstance) => ({
201
- Query: {
202
- todoList: async () => {
203
- const items = await subgraphInstance.operationalStore.getAll("todo_items");
204
- const total = items.length;
205
- const checked = items.filter((item: any) => item.checked).length;
206
- const unchecked = total - checked;
207
-
208
- return {
209
- total,
210
- checked,
211
- unchecked
212
- };
213
- },
214
-
215
- todoItems: async (parent: any, { checked }: any) => {
216
- let query = subgraphInstance.operationalStore.select("*").from("todo_items");
217
-
218
- if (checked !== undefined) {
219
- query = query.where("checked", checked);
220
- }
221
-
222
- const items = await query.orderBy("created_at", "asc");
223
- return items;
224
- },
225
-
226
- todoItemsCount: async (parent: any, { checked }: any) => {
227
- let query = subgraphInstance.operationalStore.count("* as count").from("todo_items");
228
-
229
- if (checked !== undefined) {
230
- query = query.where("checked", checked);
231
- }
232
-
233
- const result = await query.first();
234
- return result?.count || 0;
235
- }
236
- }
237
- });
238
- ```
239
-
240
- **Step 3: Implement the main class in `subgraphs/to-do-list/index.ts`:**
100
+ `;
241
101
 
242
- ```typescript
243
- // subgraphs/to-do-list/index.ts
244
- import { typeDefs } from './schema.js';
245
- import { createResolvers } from './resolvers.js';
246
-
247
- export default class ToDoListSubgraph {
248
- // Define the API endpoint where this subgraph will be accessible
249
- // Users can query this at: http://localhost:4001/graphql/to-do-list
250
- path = '/to-do-list';
251
-
252
- // GraphQL schema definition (what queries are available)
253
- typeDefs = typeDefs;
254
-
255
- // Query handlers (how to fetch the data)
256
- resolvers: any;
257
-
258
- // Database interface (injected by Powerhouse framework)
259
- operationalStore: any;
260
-
261
- constructor() {
262
- // Connect the resolvers to this subgraph instance
263
- // This gives resolvers access to the database through this.operationalStore
264
- this.resolvers = createResolvers(this);
265
- }
266
-
267
- // Called once when the subgraph starts up
268
- async onSetup() {
269
- await this.createOperationalTables();
270
- }
271
-
272
- // Create the database tables we need for storing todo items
273
- async createOperationalTables() {
274
- await this.operationalStore.schema.createTableIfNotExists(
275
- "todo_items", // Table name
276
- (table: any) => {
277
- table.string("id").primary(); // Unique identifier for each todo item
278
- table.string("text").notNullable(); // The actual todo task text
279
- table.boolean("checked").defaultTo(false); // Completion status (unchecked by default)
280
- table.timestamp("created_at").defaultTo(this.operationalStore.fn.now()); // When item was created
281
- table.timestamp("updated_at").defaultTo(this.operationalStore.fn.now()); // When item was last modified
282
- }
283
- );
284
- }
285
-
286
- // Event processor: Keeps subgraph data synchronized with document model changes
287
- // When users add/update/delete todos in Connect, this method handles the updates
288
- async process(event: any) {
289
- // Handle new todo item creation
290
- if (event.type === "ADD_TODO_ITEM") {
291
- await this.operationalStore.insert("todo_items", {
292
- id: event.input.id,
293
- text: event.input.text,
294
- checked: false,
295
- created_at: new Date(),
296
- updated_at: new Date()
297
- });
298
-
299
- console.log(`Added todo item: ${event.input.text}`);
300
- }
301
-
302
- // Handle todo item updates (text changes, checking/unchecking)
303
- if (event.type === "UPDATE_TODO_ITEM") {
304
- const updateData: any = {
305
- updated_at: new Date() // Always update the timestamp
306
- };
307
-
308
- // Only update fields that were actually changed
309
- if (event.input.text !== undefined) {
310
- updateData.text = event.input.text;
311
- }
312
- if (event.input.checked !== undefined) {
313
- updateData.checked = event.input.checked;
314
- }
315
-
316
- await this.operationalStore.update("todo_items")
317
- .where("id", event.input.id)
318
- .update(updateData);
319
-
320
- console.log(`Updated todo item: ${event.input.id}`);
321
- }
322
-
323
- // Handle todo item deletion
324
- if (event.type === "DELETE_TODO_ITEM") {
325
- await this.operationalStore.delete("todo_items")
326
- .where("id", event.input.id);
327
-
328
- console.log(`Deleted todo item: ${event.input.id}`);
329
- }
330
- }
331
- }
332
102
  ```
333
103
 
334
- ### 2.3 Understanding the implementation
335
-
336
- **What this multi-file approach provides:**
337
-
338
- 1. **Schema separation** (`schema.ts`): Clean GraphQL type definitions
339
- 2. **Resolver isolation** (`resolvers.ts`): Business logic separated from structure
340
- 3. **Main orchestration** (`index.ts`): Combines everything and handles lifecycle methods
341
-
342
- **Key features implemented:**
343
- - A `todo_items` operational table to store individual to-do items
344
- - Fields that match our document model structure
345
- - Timestamps for tracking when items were created and updated
346
- - Resolvers that fetch and filter todo items from the operational store
347
- - Event processing to keep the subgraph data synchronized with document model changes
348
-
349
- ### 2.4 Understanding the document model event integration
350
-
351
- Notice that our `index.ts` file already includes a `process` method - this is the **processor integration** that keeps our subgraph synchronized with To-do List document model events. When users interact with To-do List documents through Connect, this method automatically handles the updates.
352
-
353
- **How the existing processor integration works:**
104
+ **Step 2: Create resolvers in `subgraphs/search-todos/resolvers.ts`:**
354
105
 
355
- The `process` method in our `index.ts` file handles three types of document model events:
356
-
357
- **1. Adding new todo items:**
358
- ```typescript
359
- if (event.type === "ADD_TODO_ITEM") {
360
- await this.operationalStore.insert("todo_items", {
361
- id: event.input.id,
362
- text: event.input.text,
363
- checked: false,
364
- created_at: new Date(),
365
- updated_at: new Date()
366
- });
367
- }
368
- ```
369
-
370
- **2. Updating existing items:**
371
106
  ```typescript
372
- if (event.type === "UPDATE_TODO_ITEM") {
373
- // Only update fields that were actually changed
374
- const updateData = { updated_at: new Date() };
375
- if (event.input.text !== undefined) updateData.text = event.input.text;
376
- if (event.input.checked !== undefined) updateData.checked = event.input.checked;
377
-
378
- await this.operationalStore.update("todo_items")
379
- .where("id", event.input.id)
380
- .update(updateData);
381
- }
382
- ```
107
+ // subgraphs/search-todos/resolvers.ts
108
+ import { type Subgraph } from "@powerhousedao/reactor-api";
109
+ import { type ToDoListDocument } from "document-models/to-do-list/index.js";
110
+
111
+ export const getResolvers = (subgraph: Subgraph) => {
112
+ const reactor = subgraph.reactor;
113
+
114
+ return {
115
+ Query: {
116
+ searchTodos: async (parent: unknown, args: { driveId: string, searchTerm: string }) => {
117
+ const documents = await reactor.getDocuments(args.driveId);
118
+ const todoItems: string[] = [];
119
+ for (const docId of documents) {
120
+ const doc: ToDoListDocument = await reactor.getDocument(docId);
121
+ if (doc.header.documentType !== "powerhouse/todolist") {
122
+ continue;
123
+ }
124
+
125
+ const amountEntries = doc.state.global.items.filter(e => e.text.includes(args.searchTerm)).length;
126
+ if (amountEntries > 0) {
127
+ todoItems.push(docId);
128
+ }
129
+ }
130
+ return todoItems;
131
+ },
132
+ },
133
+ };
134
+ };
383
135
 
384
- **3. Deleting items:**
385
- ```typescript
386
- if (event.type === "DELETE_TODO_ITEM") {
387
- await this.operationalStore.delete("todo_items")
388
- .where("id", event.input.id);
389
- }
390
136
  ```
391
137
 
392
- **The integration happens automatically:**
393
- 1. **User action**: Someone adds a todo item in Connect
394
- 2. **Document model**: Processes the `ADD_TODO_ITEM` operation
395
- 3. **Framework routing**: Powerhouse automatically calls your subgraph's `process` method
396
- 4. **Subgraph response**: Your `process` method updates the operational store
397
- 5. **Query availability**: Users can now query the updated data via GraphQL
398
-
399
- ### 2.5 Summary of what we've built
400
-
401
- Our complete To-do List subgraph includes:
402
-
403
- - **GraphQL schema** (`schema.ts`): Defines `todoList`, `todoItems`, and `todoItemsCount` queries
404
- - **Resolvers** (`resolvers.ts`): Handle data fetching and filtering from the operational store
405
- - **Main subgraph class** (`index.ts`): Coordinates everything and includes:
406
- - **Operational table creation**: Sets up the `todo_items` table with proper schema
407
- - **Event processing**: The `process` method keeps subgraph data synchronized with document model changes
408
- - **Real-time updates**: Automatically handles `ADD_TODO_ITEM`, `UPDATE_TODO_ITEM`, and `DELETE_TODO_ITEM` events
409
-
410
- **Key features:**
411
- - **Filtering capability**: The `todoItems` query accepts an optional `checked` parameter
412
- - **Performance optimization**: The `todoItemsCount` query returns just numbers when you don't need full data
413
- - **Real-time synchronization**: Changes in Connect immediately appear in subgraph queries
414
- - **Complete statistics**: The `todoList` query returns total, checked, and unchecked counts
415
138
 
416
139
  ## 3. Testing the to-do list subgraph
417
140
 
@@ -421,21 +144,21 @@ To activate the subgraph, run:
421
144
  ```bash
422
145
  ph reactor
423
146
  ```
424
- Or, for full system startup:
425
-
426
- ```bash title="Start the Reactor & Connect in Studio or Locally"
427
- ph dev
428
- ```
429
147
 
430
148
  You should see the subgraph being registered in the console output:
431
149
  ```
432
- > Registered /graphql/to-do-list subgraph.
150
+ > Registered /graphql/search-todos subgraph.
433
151
  ```
434
152
 
435
153
  ### 3.2. Create some test data
436
154
  Before testing queries, let's create some To-do List documents with test data:
437
155
 
438
- 1. Open Connect at `http://localhost:3001` in another terminal
156
+ 1. Start Connect
157
+ ```bash
158
+ ph connect
159
+ ```
160
+
161
+ 1. Open Connect at `http://localhost:3000` in the browser
439
162
  2. Add the 'remote' drive that is running locally via the (+) 'Add Drive' button. Add 'http://localhost:4001/d/powerhouse'
440
163
  3. Create a new To-do List document
441
164
  4. Add some test items:
@@ -447,92 +170,21 @@ Before testing queries, let's create some To-do List documents with test data:
447
170
  Open your browser and go to:
448
171
 
449
172
  ```bash
450
- http://localhost:4001/graphql/to-do-list
173
+ http://localhost:4001/graphql
451
174
  ```
452
175
 
453
176
  ### 3.4. Test the queries
454
177
 
455
- **Query 1: Get To-do List statistics**
178
+ **Query 1: Search for Todos **
456
179
  ```graphql
457
180
  query {
458
- todoList {
459
- total
460
- checked
461
- unchecked
462
- }
181
+ searchTodos(driveId: "powerhouse", searchTerm: "Test")
463
182
  }
464
183
  ```
465
184
 
466
- **Query 2: Get all to-do items**
467
- ```graphql
468
- query {
469
- todoItems {
470
- id
471
- text
472
- checked
473
- }
474
- }
475
- ```
185
+ You should get a list of the document Ids which contain the search term "Test".
476
186
 
477
- **Query 3: Get only unchecked items**
478
- ```graphql
479
- query {
480
- todoItems(checked: false) {
481
- id
482
- text
483
- checked
484
- }
485
- }
486
- ```
487
-
488
- **Query 4: Get count of completed items**
489
- ```graphql
490
- query {
491
- todoItemsCount(checked: true)
492
- }
493
- ```
494
-
495
- ### 3.5. Expected responses
496
-
497
- **For the statistics query:**
498
- ```json
499
- {
500
- "data": {
501
- "todoList": {
502
- "total": 3,
503
- "checked": 1,
504
- "unchecked": 2
505
- }
506
- }
507
- }
508
- ```
509
-
510
- **For the items query:**
511
- ```json
512
- {
513
- "data": {
514
- "todoItems": [
515
- {
516
- "id": "item-1",
517
- "text": "Learn about subgraphs",
518
- "checked": false
519
- },
520
- {
521
- "id": "item-2",
522
- "text": "Build a To-do List subgraph",
523
- "checked": true
524
- },
525
- {
526
- "id": "item-3",
527
- "text": "Test the subgraph",
528
- "checked": false
529
- }
530
- ]
531
- }
532
- }
533
- ```
534
-
535
- ### 3.6. Test real-time updates
187
+ ### 3.5. Test real-time updates
536
188
 
537
189
  To verify that your subgraph stays synchronized with document changes:
538
190
 
@@ -544,9 +196,10 @@ To verify that your subgraph stays synchronized with document changes:
544
196
 
545
197
  This demonstrates the real-time synchronization between the document model and the subgraph through event processing.
546
198
 
547
- ## 4. Working with the supergraph or gateway
548
199
 
549
- A supergraph is a GraphQL schema that combines multiple underlying GraphQL APIs, known as subgraphs, into a single, unified graph. This architecture allows different teams to work independently on their respective services (subgraphs) while providing a single entry point for clients or users to query all available data.
200
+ ## 4. Working with the GraphQL Gateway
201
+
202
+ The GraphQL Gateway is a GraphQL schema that combines multiple underlying GraphQL APIs, known as subgraphs, into a single, unified graph. This architecture allows different teams to work independently on their respective services (subgraphs) while providing a single entry point for clients or users to query all available data.
550
203
 
551
204
  ### 4.1 Key concepts
552
205
 
@@ -268,19 +268,41 @@ A subgraph is a GraphQL schema that exposes your processed data to clients. It:
268
268
 
269
269
  ### Configure the Subgraph
270
270
 
271
- Open `./subgraphs/todo/index.ts` and configure the resolvers:
271
+ Open `./subgraphs/todo/schema.ts`and configure the schema:
272
272
 
273
273
  ```ts
274
- import { Subgraph } from "@powerhousedao/reactor-api";
275
274
  import { gql } from "graphql-tag";
275
+ import type { DocumentNode } from "graphql";
276
+
277
+ export const schema: DocumentNode = gql`
278
+
279
+ # Define the structure of a todo item as returned by GraphQL
280
+ type ToDoListEntry {
281
+ task: String! # The task description (! means required/non-null)
282
+ status: Boolean! # The completion status (true = done, false = pending)
283
+ }
284
+
285
+ # Define available queries
286
+ type Query {
287
+ todos(driveId: ID!): [ToDoListEntry] # Get array of todos for a specific drive
288
+ }
289
+ `;
290
+
291
+ ```
292
+
293
+ Open `./subgraphs/todo/resolvers.ts` and configure the resolvers:
294
+
295
+ ```ts
296
+ // subgraphs/search-todos/resolvers.ts
297
+ import { type Subgraph } from "@powerhousedao/reactor-api";
298
+ import { type ToDoListDocument } from "document-models/to-do-list/index.js";
276
299
  import { TodoIndexerProcessor } from "../../processors/todo-indexer/index.js";
277
300
 
278
- export class TodoSubgraph extends Subgraph {
279
- // Human-readable name for this subgraph
280
- name = "Todos";
301
+ export const getResolvers = (subgraph: Subgraph) => {
302
+ const reactor = subgraph.reactor;
303
+ const relationalDb = subgraph.relationalDb;
281
304
 
282
- // GraphQL resolvers - functions that fetch data for each field
283
- resolvers = {
305
+ return {
284
306
  Query: {
285
307
  todos: {
286
308
  // Resolver function for the "todos" query
@@ -288,7 +310,7 @@ export class TodoSubgraph extends Subgraph {
288
310
  resolve: async (_: any, args: {driveId: string}) => {
289
311
  // Query the database using the processor's static query method
290
312
  // This gives us access to the namespaced database for the specific drive
291
- const todos = await TodoIndexerProcessor.query(args.driveId, this.relationalDb)
313
+ const todos = await TodoIndexerProcessor.query(args.driveId, relationalDb)
292
314
  .selectFrom("todo") // Select from the "todo" table
293
315
  .selectAll() // Get all columns
294
316
  .execute(); // Execute the query
@@ -302,29 +324,10 @@ export class TodoSubgraph extends Subgraph {
302
324
  },
303
325
  },
304
326
  };
305
-
306
- // GraphQL schema definition using GraphQL Schema Definition Language (SDL)
307
- typeDefs = gql`
308
-
309
- # Define the structure of a todo item as returned by GraphQL
310
- type ToDoListEntry {
311
- task: String! # The task description (! means required/non-null)
312
- status: Boolean! # The completion status (true = done, false = pending)
313
- }
314
-
315
- # Define available queries
316
- type Query {
317
- todos(driveId: ID!): [ToDoListEntry] # Get array of todos for a specific drive
318
- }
319
- `;
320
-
321
- // Cleanup method called when the subgraph disconnects
322
- async onDisconnect() {
323
- // Add any cleanup logic here if needed
324
- }
325
- }
327
+ };
326
328
  ```
327
329
 
330
+
328
331
  ## Now query the data via the supergraph.
329
332
 
330
333
  **Understanding the Supergraph**
@@ -709,42 +709,50 @@ Notes:
709
709
 
710
710
  ```
711
711
  Command Overview:
712
- The vetra command sets up a complete Vetra development environment for working with Vetra projects.
713
- It starts three coordinated services: a Vetra Switchboard, a Local Reactor, and Connect Studio,
714
- enabling full document collaboration and real-time processing with a "Vetra" drive.
712
+ The vetra command sets up a Vetra development environment for working with Vetra projects.
713
+ It starts a Vetra Switchboard and optionally Connect Studio, enabling document collaboration
714
+ and real-time processing with a "Vetra" drive or connection to remote drives.
715
715
 
716
716
  This command:
717
717
  1. Starts a Vetra Switchboard with a "Vetra" drive for document storage
718
- 2. Starts a Local Reactor that connects to the Vetra Switchboard as a remote drive
719
- 3. Starts Connect Studio pointing to the Local Reactor for user interaction
720
- 4. Enables real-time updates, collaboration, and code generation across all services
718
+ 2. Optionally connects to remote drives instead of creating a local drive
719
+ 3. Starts Connect Studio pointing to the Switchboard for user interaction (unless disabled)
720
+ 4. Enables real-time updates, collaboration, and code generation
721
721
 
722
722
  Options:
723
- --generate Generate code automatically when document models are updated.
724
- This keeps your code in sync with model changes across all services.
723
+ --logs Enable verbose logging for all services. This provides detailed
724
+ output from Switchboard and Connect during startup and operation.
725
725
 
726
- --switchboard-port <port> Specify the port to use for the Vetra Switchboard service.
727
- Default is 4001. The Switchboard handles document storage.
726
+ --switchboard-port <port> Specify the port to use for the Vetra Switchboard service.
727
+ Default is 4001. The Switchboard handles document storage.
728
728
 
729
- --reactor-port <port> Specify the port to use for the Local Reactor service.
730
- Default is 4002. The Reactor provides the main API and connects to switchboard.
729
+ --connect-port <port> Specify the port to use for Connect Studio.
730
+ Default is 3000. Connect provides the user interface.
731
731
 
732
- --https-key-file <path> Path to the SSL key file if using HTTPS for secure connections.
732
+ --https-key-file <path> Path to the SSL key file if using HTTPS for secure connections.
733
733
 
734
- --https-cert-file <path> Path to the SSL certificate file if using HTTPS.
734
+ --https-cert-file <path> Path to the SSL certificate file if using HTTPS.
735
735
 
736
- --config-file <path> Path to the powerhouse.config.js file. This allows you to
737
- customize the behavior of all services in the Vetra development environment.
736
+ --config-file <path> Path to the powerhouse.config.js file. This allows you to
737
+ customize the behavior of the Vetra development environment.
738
738
 
739
- -w, --watch Watch for local changes to document models and processors,
740
- and automatically update both the Switchboard and Reactor accordingly.
739
+ -w, --watch Watch for local changes to document models and processors,
740
+ and automatically update the Switchboard accordingly.
741
+
742
+ --remote-drive <url> URL of remote drive to connect to. When specified, the switchboard
743
+ connects to this remote drive instead of creating a local Vetra drive.
744
+
745
+ --disable-connect Skip Connect initialization (only start switchboard and reactor).
746
+ Use this when you only need the backend services running.
741
747
 
742
748
  Examples:
743
- $ ph vetra # Start complete Vetra environment with defaults
744
- $ ph vetra --generate # Auto-generate code on model changes
745
- $ ph vetra --switchboard-port 5000 --reactor-port 5001 # Use custom ports
746
- $ ph vetra --config-file custom.powerhouse.config.js # Use custom configuration
747
- $ ph vetra --watch # Watch for changes and auto-update
749
+ $ ph vetra # Start Vetra environment with defaults
750
+ $ ph vetra --switchboard-port 5000 --connect-port 3001 # Use custom ports
751
+ $ ph vetra --config-file custom.powerhouse.config.js # Use custom configuration
752
+ $ ph vetra --watch # Watch for changes and auto-update
753
+ $ ph vetra --logs # Enable detailed logging
754
+ $ ph vetra --remote-drive http://localhost:4001/d/docs # Connect to remote drive
755
+ $ ph vetra --disable-connect # Start only backend services
748
756
  $ ph vetra --https-key-file key.pem --https-cert-file cert.pem # Use HTTPS
749
757
  ```
750
758
 
@@ -0,0 +1,325 @@
1
+ # Tutorial Verification System - Technical Architecture
2
+
3
+ ## Overview
4
+
5
+ This document outlines the technical architecture for an automated tutorial verification system that ensures the Powerhouse Academy tutorials remain accurate and functional as the codebase evolves.
6
+
7
+ ## System Architecture
8
+
9
+ ### 1. Test Execution Environment
10
+
11
+ ```
12
+ ┌─────────────────────────────────────────┐
13
+ │ GitHub Actions Runner (Ubuntu) │
14
+ │ ┌─────────────────────────────────────┐ │
15
+ │ │ Isolated Docker Container │ │
16
+ │ │ ├── Node.js 22 │ │
17
+ │ │ ├── pnpm │ │
18
+ │ │ ├── Powerhouse CLI (ph-cli) │ │
19
+ │ │ ├── Playwright browsers │ │
20
+ │ │ └── Temporary filesystem │ │
21
+ │ └─────────────────────────────────────┘ │
22
+ └─────────────────────────────────────────┘
23
+ ```
24
+
25
+ **Execution Environments:**
26
+ - **Development**: Local machine (`pnpm test:e2e`)
27
+ - **CI/CD**: GitHub Actions runners (automatically on PRs)
28
+ - **Scheduled**: Nightly runs to catch regressions
29
+
30
+ ### 2. Tutorial Agent Architecture
31
+
32
+ The "agent" is a test orchestrator that processes tutorials through a structured workflow:
33
+
34
+ ```typescript
35
+ // packages/tutorial-verifier/src/tutorial-agent.ts
36
+ class TutorialAgent {
37
+ async verifyTutorial(tutorialPath: string) {
38
+ // 1. Parse tutorial markdown
39
+ const steps = await this.parseTutorialSteps(tutorialPath);
40
+
41
+ // 2. Create isolated environment
42
+ const sandbox = await this.createSandbox();
43
+
44
+ // 3. Execute each step sequentially
45
+ for (const step of steps) {
46
+ await this.executeStep(step, sandbox);
47
+ await this.verifyStepOutcome(step, sandbox);
48
+ }
49
+
50
+ // 4. Generate report
51
+ return this.generateReport();
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Component Integration
57
+
58
+ ### 3. E2E Test Framework Integration
59
+
60
+ **Extends existing Playwright E2E infrastructure:**
61
+
62
+ ```typescript
63
+ // test/connect-e2e/tests/tutorial-get-started.spec.ts
64
+ import { test, expect } from '@playwright/test';
65
+
66
+ test('Tutorial: Create new to-do list document', async ({ page }) => {
67
+ const agent = new TutorialAgent();
68
+
69
+ // Phase 1: CLI Commands (no browser needed)
70
+ await agent.executeCliStep('ph init my-todo-project');
71
+ await agent.verifyFileExists('my-todo-project/package.json');
72
+
73
+ // Phase 2: Browser Automation (uses Playwright)
74
+ await agent.startConnect(); // Starts ph connect in background
75
+ await page.goto('http://localhost:3000');
76
+
77
+ // Phase 3: UI Verification (Playwright E2E)
78
+ await page.click('text=Local Drive');
79
+ await page.click('button:has-text("DocumentModel")');
80
+ await expect(page.locator('text=Document Model Editor')).toBeVisible();
81
+
82
+ // Phase 4: Cleanup
83
+ await agent.cleanup();
84
+ });
85
+ ```
86
+
87
+ ### 4. Playwright Browser Automation
88
+
89
+ **UI automation for Connect Studio interactions:**
90
+
91
+ ```typescript
92
+ // UI automation for Connect Studio
93
+ class ConnectUIAgent {
94
+ constructor(private page: Page) {}
95
+
96
+ async createDocumentModel(name: string) {
97
+ // Navigate to document creation
98
+ await this.page.click('button:has-text("DocumentModel")');
99
+
100
+ // Fill in model details
101
+ await this.page.fill('input[placeholder="Document Name"]', name);
102
+ await this.page.fill('input[placeholder="Document Type"]', `powerhouse/${name.toLowerCase()}`);
103
+
104
+ // Define schema in code editor
105
+ await this.page.click('.monaco-editor');
106
+ await this.page.keyboard.type(`
107
+ type ${name}State {
108
+ items: [TodoItem!]!
109
+ }
110
+ `);
111
+
112
+ // Save and verify
113
+ await this.page.click('button:has-text("Save")');
114
+ await expect(this.page.locator(`text=${name}.phdm.zip`)).toBeVisible();
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## Execution Flow
120
+
121
+ ### 5. Tutorial Processing Pipeline
122
+
123
+ ```
124
+ Tutorial Markdown File
125
+
126
+ [Tutorial Parser]
127
+
128
+ ┌─────────────────┐
129
+ │ Execution Steps │
130
+ ├─────────────────┤
131
+ │ 1. CLI Commands │ ← Uses child_process.spawn()
132
+ │ 2. File Checks │ ← Uses fs.existsSync()
133
+ │ 3. UI Actions │ ← Uses Playwright page.click()
134
+ │ 4. Verifications│ ← Custom assertion logic
135
+ └─────────────────┘
136
+
137
+ [Report Generator]
138
+
139
+ Test Results + Screenshots
140
+ ```
141
+
142
+ ### 6. Step-by-Step Processing Example
143
+
144
+ **Tutorial Markdown:**
145
+ ```markdown
146
+ ## Quick start
147
+ Create a new Powerhouse project with a single command:
148
+ ```bash
149
+ ph init
150
+ ```
151
+
152
+ Then start Connect:
153
+ ```bash
154
+ ph connect
155
+ ```
156
+
157
+ Navigate to http://localhost:3000 and click on "Local Drive".
158
+ ```
159
+
160
+ **Agent Processing:**
161
+
162
+ ```typescript
163
+ // 1. Tutorial Parser extracts executable steps
164
+ const steps = [
165
+ { type: 'cli', command: 'ph init', expectedFiles: ['package.json'] },
166
+ { type: 'cli', command: 'ph connect', background: true },
167
+ { type: 'browser', action: 'navigate', url: 'http://localhost:3000' },
168
+ { type: 'browser', action: 'click', selector: 'text=Local Drive' }
169
+ ];
170
+
171
+ // 2. Agent executes each step
172
+ for (const step of steps) {
173
+ switch (step.type) {
174
+ case 'cli':
175
+ const result = execSync(step.command);
176
+ if (step.expectedFiles) {
177
+ for (const file of step.expectedFiles) {
178
+ assert(existsSync(file), `Expected file ${file} not found`);
179
+ }
180
+ }
181
+ break;
182
+
183
+ case 'browser':
184
+ if (step.action === 'navigate') {
185
+ await page.goto(step.url);
186
+ } else if (step.action === 'click') {
187
+ await page.click(step.selector);
188
+ }
189
+ break;
190
+ }
191
+ }
192
+ ```
193
+
194
+ ## Deployment Architecture
195
+
196
+ ### 7. Runtime Environment Distribution
197
+
198
+ ```
199
+ ┌─────────────────────────────────────────────────────────┐
200
+ │ GitHub Actions Runner │
201
+ │ │
202
+ │ ┌─────────────────┐ ┌─────────────────────────────────┐ │
203
+ │ │ Tutorial Agent │ │ Sandbox Environment │ │
204
+ │ │ (Node.js) │ │ │ │
205
+ │ │ │ │ ┌─────────────┐ │ │
206
+ │ │ • Parse MD │ │ │ ph connect │ :3000 │ │
207
+ │ │ • Execute CLI │ │ └─────────────┘ │ │
208
+ │ │ • Control tests │ │ │ │
209
+ │ └─────────────────┘ │ ┌─────────────┐ │ │
210
+ │ │ │ Playwright │ │ │
211
+ │ ┌─────────────────┐ │ │ Browser │ │ │
212
+ │ │ Report Gen │ │ └─────────────┘ │ │
213
+ │ │ • Screenshots │ │ │ │
214
+ │ │ • Error logs │ │ /tmp/tutorial-test-xyz/ │ │
215
+ │ │ • Success rates │ │ ├── my-todo-project/ │ │
216
+ │ └─────────────────┘ │ │ ├── package.json │ │
217
+ │ │ │ └── ... │ │
218
+ │ └─────────────────────────────────┘ │
219
+ └─────────────────────────────────────────────────────────┘
220
+ ```
221
+
222
+ ### 8. Integration with Existing Test Suite
223
+
224
+ **File Structure Extension:**
225
+
226
+ ```
227
+ Your Current E2E Tests:
228
+ test/connect-e2e/tests/
229
+ ├── todo-document.spec.ts ← Existing
230
+ ├── drive.spec.ts ← Existing
231
+ └── app.spec.ts ← Existing
232
+
233
+ Added Tutorial Tests:
234
+ test/connect-e2e/tests/tutorials/
235
+ ├── get-started.spec.ts ← New: Tests "Get Started" tutorial
236
+ ├── mastery-track.spec.ts ← New: Tests "Mastery Track" tutorials
237
+ └── cookbook.spec.ts ← New: Tests "Cookbook" recipes
238
+ ```
239
+
240
+ **No changes to existing code** - just additional test files that run alongside current tests.
241
+
242
+ ## Performance & Timing
243
+
244
+ ### 9. Execution Timeline
245
+
246
+ ```
247
+ 0s │ Start test
248
+ 2s │ ├── Parse tutorial markdown
249
+ 4s │ ├── Create temp directory
250
+ 6s │ ├── Run 'ph init'
251
+ 15s │ ├── Verify project structure
252
+ 20s │ ├── Start 'ph connect' (background)
253
+ 35s │ ├── Wait for Connect to be ready
254
+ 40s │ ├── Launch Playwright browser
255
+ 45s │ ├── Navigate to localhost:3000
256
+ 50s │ ├── Perform UI interactions
257
+ 55s │ ├── Verify expected elements
258
+ 60s │ ├── Take screenshots
259
+ 65s │ ├── Cleanup (kill processes, delete files)
260
+ 70s │ └── Generate report
261
+ ```
262
+
263
+ ## Implementation Phases
264
+
265
+ ### Phase 1: Proof of Concept (1-2 days)
266
+ - Single tutorial verification (Get Started)
267
+ - Basic CLI command execution
268
+ - Simple file existence checks
269
+ - Integration with existing Playwright setup
270
+
271
+ ### Phase 2: Core Features (1-2 weeks)
272
+ - Tutorial markdown parser
273
+ - Comprehensive step execution engine
274
+ - Browser automation for Connect Studio
275
+ - Error reporting and screenshots
276
+
277
+ ### Phase 3: Advanced Features (2-3 weeks)
278
+ - Multiple tutorial support
279
+ - Parallel execution
280
+ - Detailed reporting dashboard
281
+ - Integration with CI/CD pipeline
282
+
283
+ ### Phase 4: Intelligence Layer (2-3 weeks)
284
+ - Failure pattern analysis
285
+ - Automated suggestion generation
286
+ - Historical trend tracking
287
+ - LLM-powered issue diagnosis
288
+
289
+ ## Key Technical Decisions
290
+
291
+ ### Technology Stack
292
+ - **Test Framework**: Playwright (existing)
293
+ - **Runtime**: Node.js 22 + TypeScript
294
+ - **CLI Execution**: child_process.spawn()
295
+ - **File Operations**: Node.js fs module
296
+ - **Browser Automation**: Playwright
297
+ - **CI/CD**: GitHub Actions (existing)
298
+
299
+ ### Design Principles
300
+ - **Isolation**: Each test runs in isolated environment
301
+ - **Repeatability**: Tests can run multiple times with same results
302
+ - **Extensibility**: Easy to add new tutorials
303
+ - **Integration**: Builds on existing infrastructure
304
+ - **Cleanup**: Automatic resource cleanup after tests
305
+
306
+ ## Success Metrics
307
+
308
+ - **Tutorial Success Rate**: % of tutorials that pass verification
309
+ - **Time to Detection**: How quickly outdated tutorials are identified
310
+ - **False Positive Rate**: % of failures due to test issues vs actual problems
311
+ - **Coverage**: Number of tutorial steps automatically verified
312
+ - **Developer Confidence**: Reduced manual testing effort
313
+
314
+ ## Risk Mitigation
315
+
316
+ ### Technical Risks
317
+ - **Flaky Tests**: Use retry mechanisms and wait strategies
318
+ - **Environment Dependencies**: Containerized execution environments
319
+ - **Resource Cleanup**: Comprehensive cleanup in finally blocks
320
+ - **Process Management**: Proper process lifecycle management
321
+
322
+ ### Operational Risks
323
+ - **Maintenance Overhead**: Gradual rollout with proven patterns
324
+ - **CI/CD Load**: Efficient test execution and parallel processing
325
+ - **Team Adoption**: Clear documentation and training materials
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/academy",
3
- "version": "4.1.0-dev.2",
3
+ "version": "4.1.0-dev.20",
4
4
  "homepage": "https://powerhouse.academy",
5
5
  "repository": {
6
6
  "type": "git",