jsgui3-server 0.0.142 → 0.0.144

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 (31) hide show
  1. package/docs/comprehensive-documentation.md +25 -6
  2. package/docs/configuration-reference.md +46 -11
  3. package/docs/controls-development.md +54 -26
  4. package/docs/publishers-guide.md +48 -32
  5. package/docs/troubleshooting.md +9 -8
  6. package/examples/controls/15) window, observable SSE/README.md +29 -17
  7. package/lab/README.md +19 -0
  8. package/lab/experiments/window_examples_dom_audit.js +241 -0
  9. package/lab/results/window_examples_dom_audit.json +131 -0
  10. package/lab/results/window_examples_dom_audit.md +46 -0
  11. package/package.json +7 -2
  12. package/publishers/http-observable-publisher.js +318 -125
  13. package/publishers/http-webpageorsite-publisher.js +6 -4
  14. package/resources/processors/bundlers/css-bundler.js +28 -173
  15. package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +32 -20
  16. package/resources/processors/bundlers/style-bundler.js +288 -0
  17. package/resources/processors/compilers/SASS_Compiler.js +88 -0
  18. package/resources/processors/extractors/js/css_and_js/AST_Node/CSS_And_JS_From_JS_String_Using_AST_Node_Extractor.js +64 -68
  19. package/resources/website-css-resource.js +24 -20
  20. package/resources/website-javascript-resource-processor.js +17 -57
  21. package/resources/website-javascript-resource.js +17 -57
  22. package/serve-factory.js +28 -21
  23. package/server.js +13 -7
  24. package/tests/README.md +31 -3
  25. package/tests/bundlers.test.js +41 -32
  26. package/tests/content-analysis.test.js +19 -18
  27. package/tests/error-handling.test.js +13 -11
  28. package/tests/observable-sse.test.js +5 -5
  29. package/tests/sass-controls.e2e.test.js +327 -0
  30. package/tests/test-runner.js +3 -1
  31. package/tests/window-examples.puppeteer.test.js +239 -0
@@ -284,11 +284,30 @@ body {
284
284
  }
285
285
  `;
286
286
 
287
- controls.MyControl = MyControl;
288
- module.exports = jsgui;
289
- ```
290
-
291
- 2. Create server:
287
+ controls.MyControl = MyControl;
288
+ module.exports = jsgui;
289
+ ```
290
+
291
+ SCSS/SASS: You can also set `MyControl.scss` or `MyControl.sass` using template literals. These are compiled to CSS during bundling and removed from the JS output, just like `.css`. CSS and SCSS blocks can be mixed in a control; the bundler preserves their order during compilation. If you mix indented `.sass` with `.scss`/`.css`, each block is compiled independently to preserve order. Inline CSS sourcemaps are emitted only when a single compilation pass is used; mixed syntax skips inline maps to keep them accurate.
292
+
293
+ To enable inline CSS sourcemaps for Sass/SCSS outputs, pass a `style` configuration:
294
+
295
+ ```javascript
296
+ Server.serve({
297
+ ctrl: MyControl,
298
+ src_path_client_js: require.resolve('./client.js'),
299
+ debug: true,
300
+ style: {
301
+ sourcemaps: {
302
+ enabled: true,
303
+ inline: true,
304
+ include_sources: true
305
+ }
306
+ }
307
+ });
308
+ ```
309
+
310
+ 2. Create server:
292
311
 
293
312
  ```javascript
294
313
  // server.js
@@ -1400,4 +1419,4 @@ MIT License - see LICENSE file for details.
1400
1419
  ---
1401
1420
 
1402
1421
  This documentation provides a comprehensive overview of JSGUI3 Server. For more detailed information about specific components, see the individual files in the `docs/` directory and the examples in `examples/`.</content>
1403
- <parameter name="filePath">c:\\Users\\james\\Documents\\repos\\jsgui3-server\\docs\\comprehensive-documentation.md
1422
+ <parameter name="filePath">c:\\Users\\james\\Documents\\repos\\jsgui3-server\\docs\\comprehensive-documentation.md
@@ -198,16 +198,51 @@ Configuration values are resolved in this order (later sources override earlier
198
198
  });
199
199
  ```
200
200
 
201
- #### `config`
202
- - **Type:** `string`
203
- - **Description:** Path to configuration file
204
- - **Default:** `'jsgui.config.js'` (if exists)
205
- - **Example:**
206
- ```javascript
207
- Server.serve({
208
- config: './my-config.js'
209
- });
210
- ```
201
+ #### `config`
202
+ - **Type:** `string`
203
+ - **Description:** Path to configuration file
204
+ - **Default:** `'jsgui.config.js'` (if exists)
205
+ - **Example:**
206
+ ```javascript
207
+ Server.serve({
208
+ config: './my-config.js'
209
+ });
210
+ ```
211
+
212
+ #### `style`
213
+ - **Type:** `object`
214
+ - **Description:** Style pipeline options for CSS/SCSS/Sass extraction and compilation.
215
+ - **Default:** `{}` (inherits debug behavior for sourcemaps)
216
+ - **Example:**
217
+ ```javascript
218
+ Server.serve({
219
+ ctrl: MyControl,
220
+ debug: true,
221
+ style: {
222
+ sourcemaps: {
223
+ enabled: true,
224
+ inline: true,
225
+ include_sources: true
226
+ },
227
+ load_paths: ['styles', 'controls'],
228
+ output_style: 'expanded',
229
+ quiet_dependencies: true,
230
+ compile_css_with_sass: true
231
+ }
232
+ });
233
+ ```
234
+
235
+ **Style options:**
236
+ - `sourcemaps.enabled` (`boolean`): Enable CSS sourcemaps. Defaults to `true` when `debug` is enabled.
237
+ - `sourcemaps.inline` (`boolean`): Inline sourcemaps into compiled CSS (default `true`).
238
+ - `sourcemaps.include_sources` (`boolean`): Embed sources content in the sourcemap (default `true`).
239
+ - `load_paths` (`string[]`): Sass load paths for `@use`/`@import`.
240
+ - `output_style` (`string`): Sass output style (e.g., `expanded`, `compressed`).
241
+ - `quiet_dependencies` (`boolean`): Suppress dependency warnings during Sass compilation.
242
+ - `compile_css_with_sass` (`boolean`): Compile `.css` blocks through Sass when mixing with SCSS (default `true`).
243
+ - `scss_sources` / `sass_sources` (`string[]`): Extra Sass/SCSS sources appended during compilation.
244
+
245
+ Inline CSS sourcemaps are emitted only when a single compilation pass is possible. Mixed `.sass` plus `.scss`/`.css` inputs skip inline maps to avoid inaccurate mappings.
211
246
 
212
247
  ## Environment Variables
213
248
 
@@ -805,4 +840,4 @@ Solution: port: 3000 instead of port: "3000"
805
840
 
806
841
  ---
807
842
 
808
- This configuration reference provides comprehensive coverage of all JSGUI3 Server configuration options. Remember that most options have sensible defaults, so you only need to specify what differs from the defaults for your use case.
843
+ This configuration reference provides comprehensive coverage of all JSGUI3 Server configuration options. Remember that most options have sensible defaults, so you only need to specify what differs from the defaults for your use case.
@@ -74,23 +74,44 @@ class My_Custom_Control extends Active_HTML_Document {
74
74
  }
75
75
 
76
76
  // Static CSS definition
77
- My_Custom_Control.css = `
78
- * { margin: 0; padding: 0; }
79
- body {
80
- overflow-x: hidden;
77
+ My_Custom_Control.css = `
78
+ * { margin: 0; padding: 0; }
79
+ body {
80
+ overflow-x: hidden;
81
81
  overflow-y: hidden;
82
82
  background-color: #E0E0E0;
83
83
  }
84
84
  .my-custom-control {
85
85
  padding: 20px;
86
86
  background: #f0f0f0;
87
- }
88
- `;
89
-
90
- // Register control globally
91
- controls.My_Custom_Control = My_Custom_Control;
92
- module.exports = jsgui;
93
- ```
87
+ }
88
+ `;
89
+
90
+ // Register control globally
91
+ controls.My_Custom_Control = My_Custom_Control;
92
+ module.exports = jsgui;
93
+ ```
94
+
95
+ Note: You can also define `My_Custom_Control.scss` or `My_Custom_Control.sass` using template literals. These are compiled to CSS during bundling and removed from the JS output, just like `.css`. CSS and SCSS blocks can be mixed in a control; the bundler preserves their order during compilation. If you mix indented `.sass` with `.scss`/`.css`, each block is compiled independently to preserve order. Inline CSS sourcemaps are emitted only when a single compilation pass is used; mixed syntax skips inline maps to keep them accurate.
96
+
97
+ To enable inline CSS sourcemaps for Sass/SCSS outputs, pass a `style` configuration when serving:
98
+
99
+ ```javascript
100
+ Server.serve({
101
+ ctrl: My_Custom_Control,
102
+ src_path_client_js: require.resolve('./client.js'),
103
+ debug: true,
104
+ style: {
105
+ sourcemaps: {
106
+ enabled: true,
107
+ inline: true,
108
+ include_sources: true
109
+ }
110
+ }
111
+ });
112
+ ```
113
+
114
+ E2E coverage for Sass/CSS controls (including sourcemaps) lives in `tests/sass-controls.e2e.test.js`.
94
115
 
95
116
  #### Control (Base)
96
117
 
@@ -114,20 +135,27 @@ module.exports = jsgui;
114
135
  - Child control containment
115
136
  - Positioning and sizing
116
137
 
117
- ```javascript
118
- const window = new controls.Window({
119
- context,
120
- title: 'My Window',
121
- pos: [100, 100], // [x, y] position
122
- size: [400, 300] // [width, height]
123
- });
124
-
125
- // Add content to window
126
- window.inner.add(childControl);
127
- this.body.add(window);
128
- ```
129
-
130
- #### Panel
138
+ ```javascript
139
+ const window = new controls.Window({
140
+ context,
141
+ title: 'My Window',
142
+ pos: [100, 100], // [x, y] position
143
+ size: [400, 300] // [width, height]
144
+ });
145
+
146
+ // Add content to window
147
+ window.inner.add(childControl);
148
+ this.body.add(window);
149
+ ```
150
+
151
+ Lab results (from `lab/results/window_examples_dom_audit.md`, generated 2025-12-19):
152
+
153
+ - Window example renders one window, a title bar, and three control buttons.
154
+ - Tabbed panel example renders two tabs with a default checked input.
155
+ - Checkbox example renders one checkbox input with the expected label.
156
+ - Date picker example renders a native date input on the server.
157
+
158
+ #### Panel
131
159
 
132
160
  **Purpose:** Basic container for grouping controls.
133
161
 
@@ -983,4 +1011,4 @@ Use browser developer tools to:
983
1011
 
984
1012
  ---
985
1013
 
986
- This guide provides the foundation for developing controls in JSGUI3. For specific control implementations, refer to the examples in the `examples/` directory and the base classes in `controls/`.
1014
+ This guide provides the foundation for developing controls in JSGUI3. For specific control implementations, refer to the examples in the `examples/` directory and the base classes in `controls/`.
@@ -104,16 +104,18 @@ const publisher = new HTTP_Function_Publisher({
104
104
 
105
105
  **Purpose:** Streams observable data to clients using Server-Sent Events (SSE).
106
106
 
107
- **Key Features:**
108
- - Real-time streaming of observable events
109
- - SSE protocol support (`text/event-stream`)
110
- - Chunked transfer encoding for long-running connections
111
- - Integration with `fnl` observables
112
-
113
- **Usage:**
114
- ```javascript
115
- const { observable } = require('fnl');
116
- const Observable_Publisher = require('jsgui3-server/publishers/http-observable-publisher');
107
+ **Key Features:**
108
+ - Real-time streaming of observable events
109
+ - SSE protocol support (`text/event-stream`)
110
+ - Chunked transfer encoding for long-running connections
111
+ - Integration with `fnl` observables
112
+ - Connection cleanup on client disconnect
113
+ - Optional `pause()`, `resume()`, `stop()` controls
114
+
115
+ **Usage:**
116
+ ```javascript
117
+ const { observable } = require('fnl');
118
+ const Observable_Publisher = require('jsgui3-server/publishers/http-observable-publisher');
117
119
 
118
120
  // Create a hot observable that emits continuously
119
121
  let tick_count = 0;
@@ -136,13 +138,28 @@ const publisher = new Observable_Publisher({
136
138
  obs: tick_stream
137
139
  });
138
140
 
139
- // Register with server router
140
- server.server_router.set_route('/api/stream', publisher, publisher.handle_http);
141
- ```
142
-
143
- **Client-Side Consumption:**
144
- ```javascript
145
- // In browser, use EventSource API
141
+ // Register with server router
142
+ server.server_router.set_route('/api/stream', publisher, publisher.handle_http);
143
+
144
+ // Optional: control the stream from server-side code
145
+ publisher.pause();
146
+ publisher.resume();
147
+ publisher.stop();
148
+ ```
149
+
150
+ **Optional Control (HTTP):**
151
+ ```javascript
152
+ // From browser or any HTTP client:
153
+ await fetch('/api/stream', {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ body: JSON.stringify({ action: 'pause' }) // 'resume' | 'stop' | 'status'
157
+ });
158
+ ```
159
+
160
+ **Client-Side Consumption:**
161
+ ```javascript
162
+ // In browser, use EventSource API
146
163
  const eventSource = new EventSource('/api/stream');
147
164
 
148
165
  eventSource.onmessage = (event) => {
@@ -159,20 +176,19 @@ eventSource.onerror = () => {
159
176
  };
160
177
  ```
161
178
 
162
- **SSE Protocol:**
163
- The publisher sends events in SSE format:
164
- ```
165
- HTTP/1.1 200 OK
166
- Content-Type: text/event-stream
167
- Transfer-Encoding: chunked
168
-
169
- OK
170
- event: message
171
- data:{"tick":1,"timestamp":1234567890,"message":"Server tick #1"}
172
-
173
- event: message
174
- data:{"tick":2,"timestamp":1234567891,"message":"Server tick #2"}
175
- ```
179
+ **SSE Protocol:**
180
+ The publisher sends events in SSE format:
181
+ ```
182
+ HTTP/1.1 200 OK
183
+ Content-Type: text/event-stream
184
+ Transfer-Encoding: chunked
185
+
186
+ data: OK
187
+
188
+ data: {"tick":1,"timestamp":1234567890,"message":"Server tick #1"}
189
+
190
+ data: {"tick":2,"timestamp":1234567891,"message":"Server tick #2"}
191
+ ```
176
192
 
177
193
  **See Also:** [Observable SSE Demo](../examples/controls/15)%20window,%20observable%20SSE/) for a complete working example.
178
194
 
@@ -386,4 +402,4 @@ Publishers provide comprehensive logging:
386
402
 
387
403
  ---
388
404
 
389
- This guide provides the foundation for understanding and extending the publisher system. For specific publisher implementations, refer to their individual source files in the `publishers/` directory.
405
+ This guide provides the foundation for understanding and extending the publisher system. For specific publisher implementations, refer to their individual source files in the `publishers/` directory.
@@ -197,13 +197,14 @@ input.js:1:0: ERROR: Expected identifier but found "}"
197
197
 
198
198
  2. **Verify CSS definition:**
199
199
  ```javascript
200
- MyControl.css = `
201
- .my-control {
202
- padding: 20px;
203
- background: #f0f0f0;
204
- }
205
- `;
206
- ```
200
+ MyControl.css = `
201
+ .my-control {
202
+ padding: 20px;
203
+ background: #f0f0f0;
204
+ }
205
+ `;
206
+ ```
207
+ You can also use `MyControl.scss` or `MyControl.sass` with template literals; these compile to CSS during bundling (ensure the `sass` dependency is installed). To see inline CSS sourcemaps in devtools, enable `style.sourcemaps` (or run with `debug: true`).
207
208
 
208
209
  3. **Check activation:**
209
210
  ```javascript
@@ -746,4 +747,4 @@ This minimal setup helps isolate whether the issue is with your specific code or
746
747
 
747
748
  ---
748
749
 
749
- Remember: Most issues can be resolved by carefully checking the console output, verifying file paths, and ensuring proper control lifecycle management. Start with the basics and work systematically through the possible causes.
750
+ Remember: Most issues can be resolved by carefully checking the console output, verifying file paths, and ensuring proper control lifecycle management. Start with the basics and work systematically through the possible causes.
@@ -52,23 +52,35 @@ node server.js
52
52
 
53
53
  2. **Reactive UI Updates**: The `Observable_Demo_UI` control updates progress bars, status text, and log entries in real-time as events arrive.
54
54
 
55
- ## The SSE Protocol
56
-
57
- Server-Sent Events use HTTP chunked transfer encoding:
58
-
59
- ```
60
- HTTP/1.1 200 OK
61
- Content-Type: text/event-stream
62
- Transfer-Encoding: chunked
63
-
64
- event: message
65
- data: {"progress": 10, "stage": "Loading..."}
66
-
67
- event: message
68
- data: {"progress": 20, "stage": "Processing..."}
69
-
70
- ...
71
- ```
55
+ ## The SSE Protocol
56
+
57
+ Server-Sent Events use HTTP chunked transfer encoding:
58
+
59
+ ```
60
+ HTTP/1.1 200 OK
61
+ Content-Type: text/event-stream
62
+ Transfer-Encoding: chunked
63
+
64
+ data: OK
65
+
66
+ data: {"progress": 10, "stage": "Loading..."}
67
+
68
+ data: {"progress": 20, "stage": "Processing..."}
69
+
70
+ ...
71
+ ```
72
+
73
+ ## Optional Pause/Resume/Stop Control
74
+
75
+ `HTTP_Observable_Publisher` also supports controlling the published observable:
76
+
77
+ ```javascript
78
+ await fetch('/api/tick-stream', {
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/json' },
81
+ body: JSON.stringify({ action: 'pause' }) // 'resume' | 'stop' | 'status'
82
+ });
83
+ ```
72
84
 
73
85
  ## Observable Pattern Benefits
74
86
 
package/lab/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Lab Experiments
2
+
3
+ This directory collects small, targeted experiments that validate specific UI behaviors or markup assumptions.
4
+
5
+ ## Experiments
6
+
7
+ - `lab/experiments/window_examples_dom_audit.js`
8
+ - Server-rendered DOM checks for selected window examples.
9
+ - Writes results to `lab/results/window_examples_dom_audit.json` and `.md`.
10
+
11
+ ## Running
12
+
13
+ ```bash
14
+ node lab/experiments/window_examples_dom_audit.js
15
+ ```
16
+
17
+ ## Results
18
+
19
+ Experiment outputs are stored under `lab/results/` for traceability and documentation updates.
@@ -0,0 +1,241 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const Server_Page_Context = require('../../page-context');
5
+
6
+ const repo_root_path = path.join(__dirname, '..', '..');
7
+ const examples_controls_root_path = path.join(repo_root_path, 'examples', 'controls');
8
+ const results_dir_path = path.join(repo_root_path, 'lab', 'results');
9
+
10
+ const count_occurrences = (haystack, needle) => {
11
+ let idx = 0;
12
+ let count = 0;
13
+ while (true) {
14
+ idx = haystack.indexOf(needle, idx);
15
+ if (idx === -1) return count;
16
+ count++;
17
+ idx += needle.length;
18
+ }
19
+ };
20
+
21
+ const count_class_occurrences = (html, class_name) => {
22
+ const class_regex = new RegExp(`class="[^"]*\\b${class_name}\\b`, 'g');
23
+ const matches = html.match(class_regex);
24
+ return matches ? matches.length : 0;
25
+ };
26
+
27
+ const count_regex_occurrences = (html, regex) => {
28
+ const matches = html.match(regex);
29
+ return matches ? matches.length : 0;
30
+ };
31
+
32
+ const render_example_html = ({ dir_name, ctrl_name }) => {
33
+ const example_dir_path = path.join(examples_controls_root_path, dir_name);
34
+ const example_client_path = path.join(example_dir_path, 'client.js');
35
+
36
+ const jsgui = require(example_client_path);
37
+ const ctrl = jsgui.controls && jsgui.controls[ctrl_name];
38
+ if (!ctrl) {
39
+ throw new Error(`Missing exported control jsgui.controls.${ctrl_name} in ${example_client_path}`);
40
+ }
41
+
42
+ const context = new Server_Page_Context();
43
+ const ctrl_instance = new ctrl({ context });
44
+ if (typeof ctrl_instance.all_html_render === 'function') {
45
+ return ctrl_instance.all_html_render();
46
+ }
47
+
48
+ if (typeof ctrl_instance.render === 'function') {
49
+ return ctrl_instance.render();
50
+ }
51
+
52
+ throw new Error(`Control ${ctrl_name} does not expose render methods`);
53
+ };
54
+
55
+ const make_count_check = (html, id, description, needle, expected) => {
56
+ const actual = count_occurrences(html, needle);
57
+ return {
58
+ id,
59
+ description,
60
+ expected,
61
+ actual,
62
+ pass: actual === expected
63
+ };
64
+ };
65
+
66
+ const make_class_count_check = (html, id, description, class_name, expected) => {
67
+ const actual = count_class_occurrences(html, class_name);
68
+ return {
69
+ id,
70
+ description,
71
+ expected,
72
+ actual,
73
+ pass: actual === expected
74
+ };
75
+ };
76
+
77
+ const make_contains_check = (html, id, description, needle) => {
78
+ const actual = html.includes(needle);
79
+ return {
80
+ id,
81
+ description,
82
+ expected: true,
83
+ actual,
84
+ pass: actual === true
85
+ };
86
+ };
87
+
88
+ const make_regex_count_check = (html, id, description, regex, expected) => {
89
+ const actual = count_regex_occurrences(html, regex);
90
+ return {
91
+ id,
92
+ description,
93
+ expected,
94
+ actual,
95
+ pass: actual === expected
96
+ };
97
+ };
98
+
99
+ const examples = [
100
+ {
101
+ dir_name: '1) window',
102
+ ctrl_name: 'Demo_UI',
103
+ checks: (html) => [
104
+ make_count_check(html, 'window_count', 'Window control count', 'data-jsgui-type="window"', 1),
105
+ make_contains_check(html, 'window_title_text', 'Window title text present', 'jsgui3-html Window Control'),
106
+ make_count_check(html, 'window_button_count', 'Window button count', 'data-jsgui-type="button"', 3)
107
+ ]
108
+ },
109
+ {
110
+ dir_name: '4) window, tabbed panel',
111
+ ctrl_name: 'Demo_UI',
112
+ checks: (html) => [
113
+ make_class_count_check(html, 'tab_label_count', 'Tab label count', 'tab-label', 2),
114
+ make_contains_check(html, 'tab_label_one', 'Tab 1 label text present', 'tab 1'),
115
+ make_contains_check(html, 'tab_label_two', 'Tab 2 label text present', 'tab 2'),
116
+ make_count_check(html, 'tab_input_checked', 'Default checked tab input count', 'checked="checked"', 1)
117
+ ]
118
+ },
119
+ {
120
+ dir_name: '8) window, checkbox/a)',
121
+ ctrl_name: 'Demo_UI',
122
+ checks: (html) => [
123
+ make_regex_count_check(
124
+ html,
125
+ 'checkbox_input_count',
126
+ 'Checkbox input count',
127
+ /\stype="checkbox"/g,
128
+ 1
129
+ ),
130
+ make_contains_check(html, 'checkbox_label_text', 'Checkbox label text present', 'A checkbox')
131
+ ]
132
+ },
133
+ {
134
+ dir_name: '9) window, date picker',
135
+ ctrl_name: 'Demo_UI',
136
+ checks: (html) => [
137
+ make_class_count_check(html, 'date_picker_present', 'Date picker container present', 'date-picker', 1),
138
+ make_regex_count_check(
139
+ html,
140
+ 'date_input_count',
141
+ 'Date input type count',
142
+ /\stype="date"/g,
143
+ 1
144
+ )
145
+ ]
146
+ }
147
+ ];
148
+
149
+ const build_results = () => {
150
+ const results = {
151
+ generated_at: new Date().toISOString(),
152
+ examples: []
153
+ };
154
+
155
+ for (const example of examples) {
156
+ const html = render_example_html(example);
157
+ const checks = example.checks(html);
158
+ const passed = checks.filter((check) => check.pass).length;
159
+ const failed = checks.length - passed;
160
+
161
+ results.examples.push({
162
+ dir_name: example.dir_name,
163
+ ctrl_name: example.ctrl_name,
164
+ checks,
165
+ summary: {
166
+ total: checks.length,
167
+ passed,
168
+ failed
169
+ }
170
+ });
171
+ }
172
+
173
+ results.summary = results.examples.reduce(
174
+ (acc, example) => {
175
+ acc.total += example.summary.total;
176
+ acc.passed += example.summary.passed;
177
+ acc.failed += example.summary.failed;
178
+ return acc;
179
+ },
180
+ { total: 0, passed: 0, failed: 0 }
181
+ );
182
+
183
+ return results;
184
+ };
185
+
186
+ const render_markdown = (results) => {
187
+ const lines = [];
188
+ lines.push('# Window Examples DOM Audit');
189
+ lines.push('');
190
+ lines.push(`Generated at: ${results.generated_at}`);
191
+ lines.push('');
192
+ lines.push(`Total checks: ${results.summary.total}`);
193
+ lines.push(`Passed: ${results.summary.passed}`);
194
+ lines.push(`Failed: ${results.summary.failed}`);
195
+ lines.push('');
196
+
197
+ for (const example of results.examples) {
198
+ lines.push(`## ${example.dir_name}`);
199
+ lines.push('');
200
+ lines.push(`Control: ${example.ctrl_name}`);
201
+ lines.push('');
202
+ lines.push('| Check | Expected | Actual | Pass |');
203
+ lines.push('| --- | --- | --- | --- |');
204
+ for (const check of example.checks) {
205
+ const expected = Array.isArray(check.expected) ? check.expected.join(', ') : String(check.expected);
206
+ const actual = Array.isArray(check.actual) ? check.actual.join(', ') : String(check.actual);
207
+ lines.push(`| ${check.description} | ${expected} | ${actual} | ${check.pass ? 'yes' : 'no'} |`);
208
+ }
209
+ lines.push('');
210
+ }
211
+
212
+ return lines.join('\n');
213
+ };
214
+
215
+ const write_results = (results) => {
216
+ fs.mkdirSync(results_dir_path, { recursive: true });
217
+ const json_path = path.join(results_dir_path, 'window_examples_dom_audit.json');
218
+ const md_path = path.join(results_dir_path, 'window_examples_dom_audit.md');
219
+
220
+ fs.writeFileSync(json_path, JSON.stringify(results, null, 2));
221
+ fs.writeFileSync(md_path, render_markdown(results));
222
+
223
+ return { json_path, md_path };
224
+ };
225
+
226
+ const main = () => {
227
+ const results = build_results();
228
+ const { json_path, md_path } = write_results(results);
229
+
230
+ console.log('Window examples DOM audit complete.');
231
+ console.log(`JSON: ${json_path}`);
232
+ console.log(`Markdown: ${md_path}`);
233
+
234
+ if (results.summary.failed > 0) {
235
+ process.exitCode = 1;
236
+ }
237
+ };
238
+
239
+ if (require.main === module) {
240
+ main();
241
+ }