jsgui3-server 0.0.147 → 0.0.149

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 (145) hide show
  1. package/.github/workflows/control-scan-manifest-check.yml +31 -0
  2. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +40 -0
  3. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +39 -0
  4. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +39 -0
  5. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +39 -0
  6. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +39 -0
  7. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +40 -0
  8. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +39 -0
  9. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +40 -0
  10. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +40 -0
  11. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +39 -0
  12. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +39 -0
  13. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +44 -0
  14. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +45 -0
  15. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +39 -0
  16. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +39 -0
  17. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +42 -0
  18. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +40 -0
  19. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +43 -0
  20. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +40 -0
  21. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +40 -0
  22. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +40 -0
  23. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +39 -0
  24. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +39 -0
  25. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +39 -0
  26. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +39 -0
  27. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +39 -0
  28. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +41 -0
  29. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +44 -0
  30. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +40 -0
  31. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +40 -0
  32. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +39 -0
  33. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +39 -0
  34. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +39 -0
  35. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +39 -0
  36. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +39 -0
  37. package/README.md +85 -3
  38. package/admin-ui/client.js +213 -0
  39. package/admin-ui/server.js +104 -0
  40. package/client/controls/auto-observable.js +207 -0
  41. package/dev-status.svg +139 -0
  42. package/docs/api-reference.md +301 -43
  43. package/docs/books/admin-ui/01-introduction.md +32 -0
  44. package/docs/books/admin-ui/02-architecture.md +92 -0
  45. package/docs/books/admin-ui/03-controls.md +194 -0
  46. package/docs/books/admin-ui/04-implementation-plan.md +62 -0
  47. package/docs/books/admin-ui/README.md +26 -0
  48. package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
  49. package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
  50. package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
  51. package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
  52. package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
  53. package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
  54. package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
  55. package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
  56. package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
  57. package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
  58. package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
  59. package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
  60. package/docs/bundling-system-deep-dive.md +9 -4
  61. package/docs/comprehensive-documentation.md +49 -18
  62. package/docs/configuration-reference.md +152 -27
  63. package/docs/core/README.md +19 -0
  64. package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
  65. package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
  66. package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
  67. package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
  68. package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
  69. package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
  70. package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
  71. package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
  72. package/docs/publishers-guide.md +59 -4
  73. package/docs/resources-guide.md +184 -35
  74. package/docs/simple-server-api-design.md +72 -17
  75. package/docs/system-architecture.md +18 -14
  76. package/examples/controls/15) window, observable SSE/server.js +6 -1
  77. package/examples/controls/19) window, auto observable ui/client.js +125 -0
  78. package/examples/controls/19) window, auto observable ui/server.js +73 -0
  79. package/examples/controls/20) window, task manager app/README.md +133 -0
  80. package/examples/controls/20) window, task manager app/client.js +797 -0
  81. package/examples/controls/20) window, task manager app/server.js +178 -0
  82. package/examples/controls/6) window, color_palette/client.js +165 -68
  83. package/examples/controls/9) window, date picker/client.js +362 -76
  84. package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
  85. package/examples/jsgui3-html/06) theming/client.js +22 -1
  86. package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
  87. package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
  88. package/lab/experiments/capture-color-controls.js +196 -0
  89. package/lab/results/screenshots/color-controls/full_page.png +0 -0
  90. package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
  91. package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
  92. package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
  93. package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
  94. package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
  95. package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
  96. package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
  97. package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
  98. package/lab/screenshot-utils.js +248 -0
  99. package/module.js +11 -4
  100. package/package.json +14 -4
  101. package/publishers/Publishers.js +4 -3
  102. package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
  103. package/publishers/http-observable-publisher.js +8 -0
  104. package/publishers/http-sse-publisher.js +341 -0
  105. package/publishers/http-webpage-publisher.js +13 -3
  106. package/publishers/http-webpageorsite-publisher.js +18 -0
  107. package/resources/process-resource.js +950 -0
  108. package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +164 -46
  109. package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
  110. package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
  111. package/resources/remote-process-resource.js +355 -0
  112. package/resources/server-resource-pool.js +354 -41
  113. package/serve-factory.js +441 -259
  114. package/server.js +161 -16
  115. package/tests/README.md +66 -4
  116. package/tests/admin-ui-render.test.js +24 -0
  117. package/tests/assigners.test.js +56 -40
  118. package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
  119. package/tests/configuration-validation.test.js +21 -18
  120. package/tests/content-analysis.test.js +7 -6
  121. package/tests/control-optimizer-cache-behavior.test.js +52 -0
  122. package/tests/control-scan-manifest-regression.test.js +144 -0
  123. package/tests/end-to-end.test.js +15 -14
  124. package/tests/error-handling.test.js +222 -179
  125. package/tests/fixtures/bundling-default-button-client.js +37 -0
  126. package/tests/fixtures/bundling-default-window-client.js +34 -0
  127. package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
  128. package/tests/fixtures/resource-monitor-client.js +319 -0
  129. package/tests/helpers/puppeteer-e2e-harness.js +317 -0
  130. package/tests/http-sse-publisher.test.js +136 -0
  131. package/tests/performance.test.js +69 -65
  132. package/tests/process-resource.test.js +138 -0
  133. package/tests/publishers.test.js +7 -7
  134. package/tests/remote-process-resource.test.js +160 -0
  135. package/tests/sass-controls.e2e.test.js +7 -1
  136. package/tests/serve-resources.test.js +270 -0
  137. package/tests/serve.test.js +120 -50
  138. package/tests/server-resource-pool.test.js +106 -0
  139. package/tests/small-controls-bundle-size.test.js +252 -0
  140. package/tests/test-runner.js +13 -1
  141. package/tests/window-examples.puppeteer.test.js +204 -1
  142. package/tests/window-resource-integration.puppeteer.test.js +585 -0
  143. package/tests/temp_invalid.js +0 -7
  144. package/tests/temp_invalid_utf8.js +0 -1
  145. package/tests/temp_malformed.js +0 -10
@@ -1,8 +1,144 @@
1
1
  const jsgui = require('jsgui3-client');
2
2
  const Active_HTML_Document = require('../../../controls/Active_HTML_Document');
3
- const { BindingDebugTools } = require('jsgui3-html/html-core/BindingDebugger');
3
+ const jsgui3_html = require('jsgui3-html');
4
4
  const { Data_Object } = jsgui;
5
5
 
6
+ const snapshot_model_values = (model) => {
7
+ if (!model || typeof model !== 'object') return {};
8
+ const raw_model = model._ && typeof model._ === 'object' ? model._ : model;
9
+ const result = {};
10
+ Object.keys(raw_model).forEach((key) => {
11
+ if (key.startsWith('_')) return;
12
+ const value = raw_model[key];
13
+ if (typeof value === 'function') return;
14
+ result[key] = value;
15
+ });
16
+ return result;
17
+ };
18
+
19
+ class Fallback_Binding_Debugger {
20
+ constructor(control) {
21
+ this.control = control;
22
+ this.logs = [];
23
+ this.enabled = false;
24
+ }
25
+
26
+ enable() {
27
+ this.enabled = true;
28
+ this.log('Debugging enabled');
29
+ }
30
+
31
+ disable() {
32
+ this.enabled = false;
33
+ this.log('Debugging disabled');
34
+ }
35
+
36
+ log(message) {
37
+ const entry = {
38
+ timestamp: new Date().toISOString(),
39
+ message
40
+ };
41
+ this.logs.push(entry);
42
+ if (this.logs.length > 100) {
43
+ this.logs.shift();
44
+ }
45
+ if (this.enabled) {
46
+ console.log(`[BindingDebugger:fallback] ${message}`);
47
+ }
48
+ }
49
+
50
+ getBindingSummary() {
51
+ const binding_manager = this.control && this.control._binding_manager;
52
+ if (!binding_manager || typeof binding_manager.inspect !== 'function') {
53
+ return {
54
+ totalBinders: 0,
55
+ totalComputed: 0,
56
+ totalWatchers: 0,
57
+ details: {
58
+ binders: [],
59
+ computed: [],
60
+ watchers: []
61
+ }
62
+ };
63
+ }
64
+
65
+ const details = binding_manager.inspect();
66
+ const binders = Array.isArray(details.binders) ? details.binders : [];
67
+ const computed = Array.isArray(details.computed) ? details.computed : [];
68
+ const watchers = Array.isArray(details.watchers) ? details.watchers : [];
69
+
70
+ return {
71
+ totalBinders: binders.length,
72
+ totalComputed: computed.length,
73
+ totalWatchers: watchers.length,
74
+ details
75
+ };
76
+ }
77
+
78
+ snapshotModels() {
79
+ const control = this.control || {};
80
+ return {
81
+ timestamp: new Date().toISOString(),
82
+ dataModel: snapshot_model_values(control.data && control.data.model),
83
+ viewDataModel: snapshot_model_values(control.view && control.view.data && control.view.data.model),
84
+ viewModel: snapshot_model_values(control.view && control.view.model)
85
+ };
86
+ }
87
+
88
+ compareSnapshots(snapshot_a = {}, snapshot_b = {}) {
89
+ const differences = [];
90
+ const compare_group = (group_name) => {
91
+ const before_group = snapshot_a[group_name] || {};
92
+ const after_group = snapshot_b[group_name] || {};
93
+ const key_set = new Set([...Object.keys(before_group), ...Object.keys(after_group)]);
94
+ key_set.forEach((key) => {
95
+ if (before_group[key] === after_group[key]) return;
96
+ differences.push({
97
+ path: `${group_name}.${key}`,
98
+ oldValue: before_group[key],
99
+ newValue: after_group[key]
100
+ });
101
+ });
102
+ };
103
+
104
+ compare_group('dataModel');
105
+ compare_group('viewDataModel');
106
+ compare_group('viewModel');
107
+ return differences;
108
+ }
109
+ }
110
+
111
+ const create_fallback_binding_debug_tools = () => {
112
+ const debugger_by_control = new WeakMap();
113
+ const get_debugger = (control) => {
114
+ if (!debugger_by_control.has(control)) {
115
+ debugger_by_control.set(control, new Fallback_Binding_Debugger(control));
116
+ }
117
+ return debugger_by_control.get(control);
118
+ };
119
+
120
+ return {
121
+ getDebugger: get_debugger,
122
+ enableFor(control) {
123
+ const debugger_instance = get_debugger(control);
124
+ debugger_instance.enable();
125
+ return debugger_instance;
126
+ },
127
+ disableFor(control) {
128
+ const debugger_instance = get_debugger(control);
129
+ debugger_instance.disable();
130
+ }
131
+ };
132
+ };
133
+
134
+ const BindingDebugTools = (
135
+ jsgui3_html
136
+ && jsgui3_html.BindingDebugTools
137
+ && typeof jsgui3_html.BindingDebugTools.getDebugger === 'function'
138
+ )
139
+ ? jsgui3_html.BindingDebugTools
140
+ : create_fallback_binding_debug_tools();
141
+
6
142
  class Binding_Debugger_Control extends jsgui.Control {
7
143
  constructor(spec = {}) {
8
144
  spec.__type_name = spec.__type_name || 'binding_debugger_control';
@@ -14,7 +14,7 @@ const HTTP_Responder = require('../HTTP_Responder');
14
14
 
15
15
 
16
16
 
17
- class Static_Route_HTTP_Responder extends HTTP_Responder {
17
+ class Static_Route_HTTP_Responder extends HTTP_Responder {
18
18
  constructor(spec) {
19
19
  super(spec);
20
20
 
@@ -37,7 +37,7 @@ class Static_Route_HTTP_Responder extends HTTP_Responder {
37
37
  // Need to call it with the correct context.
38
38
  // Seems like jsgui3-html Router and Routing_Tree need some more fixes.
39
39
 
40
- const {type, extension, text, route, response_buffers, response_headers} = this;
40
+ const {type, extension, text, route, response_buffers, response_headers} = this;
41
41
 
42
42
 
43
43
  //console.log('accept_encoding', accept_encoding);
@@ -55,50 +55,68 @@ class Static_Route_HTTP_Responder extends HTTP_Responder {
55
55
 
56
56
  if (typeof accept_encoding === 'string' && accept_encoding.includes('br')) supported_encodings.br = true;
57
57
 
58
- const has_br = response_buffers && response_buffers.br;
59
- const has_gzip = response_buffers && response_buffers.gzip;
58
+ const safe_response_buffers = response_buffers || {};
59
+ const safe_response_headers = response_headers || {};
60
+
61
+ const identity_buffer = safe_response_buffers.identity || Buffer.from(text || '', 'utf8');
62
+ const has_br = safe_response_buffers.br;
63
+ const has_gzip = safe_response_buffers.gzip;
60
64
  const use_br = supported_encodings.br === true && has_br;
61
65
  const use_gzip = supported_encodings.gzip === true && has_gzip;
66
+
67
+ let selected_encoding = 'identity';
68
+ let selected_buffer = identity_buffer;
69
+ if (use_br) {
70
+ selected_encoding = 'br';
71
+ selected_buffer = safe_response_buffers.br;
72
+ } else if (use_gzip) {
73
+ selected_encoding = 'gzip';
74
+ selected_buffer = safe_response_buffers.gzip;
75
+ }
76
+
77
+ if (!Buffer.isBuffer(selected_buffer)) {
78
+ selected_buffer = Buffer.from(String(selected_buffer || ''), 'utf8');
79
+ }
80
+
81
+ const selected_headers = safe_response_headers[selected_encoding] || safe_response_headers.identity || {};
62
82
 
63
83
  //console.log('supported_encodings', supported_encodings);
64
84
 
65
- if (use_br) {
66
-
67
- for (const key in response_headers.br) {
68
- const value = response_headers.br[key];
69
- //console.log('[key, value]', [key, value]);
70
- res.setHeader(key, value);
85
+ const has_header = (header_name) => {
86
+ if (typeof res.hasHeader === 'function') {
87
+ return res.hasHeader(header_name);
88
+ }
89
+ if (typeof res.getHeader === 'function') {
90
+ return typeof res.getHeader(header_name) !== 'undefined';
71
91
  }
92
+ return false;
93
+ };
72
94
 
73
- } else if (use_gzip) {
74
- //console.log('should write headers for gzipped buffer...');
95
+ if (typeof res.setHeader === 'function') {
96
+ for (const key in selected_headers) {
97
+ const value = selected_headers[key];
98
+ res.setHeader(key, value);
99
+ }
75
100
 
76
- for (const key in response_headers.gzip) {
77
- const value = response_headers.gzip[key];
78
- //console.log('[key, value]', [key, value]);
79
- res.setHeader(key, value);
80
- }
81
- } else {
82
- for (const key in response_headers.identity) {
83
- const value = response_headers.identity[key];
84
- //console.log('[key, value]', [key, value]);
85
- res.setHeader(key, value);
86
- }
87
- }
101
+ if (!has_header('Content-Type')) {
102
+ if (extension === 'css') res.setHeader('Content-Type', 'text/css; charset=utf-8');
103
+ if (extension === 'js') res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
104
+ if (extension === 'html') res.setHeader('Content-Type', 'text/html; charset=utf-8');
105
+ }
106
+ if (!has_header('Content-Length')) {
107
+ res.setHeader('Content-Length', selected_buffer.length);
108
+ }
109
+ }
88
110
 
89
111
  // Then write the (hopefully compressed) response bodies...
90
112
 
91
- if (use_br) {
113
+ if (typeof res.write === 'function') {
114
+ res.write(selected_buffer);
115
+ }
92
116
 
93
- res.write(response_buffers.br);
94
- } else if (use_gzip) {
95
- //console.log('should write gzipped buffer...');
96
- res.write(response_buffers.gzip);
97
- } else {
98
- res.write(response_buffers.identity);
99
- }
100
-
101
- res.end();
117
+ if (typeof res.end === 'function') {
118
+ res.end();
119
+ }
102
120
 
103
121
  //console.trace();
104
122
  //throw 'NYI';
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Capture Color Controls Screenshots
3
+ *
4
+ * Starts the jsgui3-html color controls demo server and uses Puppeteer
5
+ * to capture screenshots of each section for visual verification.
6
+ *
7
+ * Usage:
8
+ * node lab/experiments/capture-color-controls.js
9
+ *
10
+ * Prerequisites:
11
+ * npm install (puppeteer is a devDependency)
12
+ * jsgui3-html repo must be at ../jsgui3-html (relative to jsgui3-server)
13
+ */
14
+
15
+ const { spawn } = require('child_process');
16
+ const path = require('path');
17
+ const fs = require('fs');
18
+ const { launch_browser, capture_url_screenshots, close_browser } = require('../screenshot-utils');
19
+
20
+ const JSGUI3_HTML_PATH = path.resolve(__dirname, '..', '..', '..', 'jsgui3-html');
21
+ const DEMO_SERVER_SCRIPT = path.join(JSGUI3_HTML_PATH, 'lab', 'color_controls_demo_server.js');
22
+ const DEMO_URL = 'http://localhost:3600';
23
+ const OUTPUT_DIR = path.join(__dirname, '..', 'results', 'screenshots', 'color-controls');
24
+
25
+ /**
26
+ * Start the color controls demo server as a child process.
27
+ */
28
+ const start_demo_server = () => {
29
+ return new Promise((resolve, reject) => {
30
+ console.log(`Starting demo server: ${DEMO_SERVER_SCRIPT}`);
31
+
32
+ if (!fs.existsSync(DEMO_SERVER_SCRIPT)) {
33
+ reject(new Error(`Demo server script not found: ${DEMO_SERVER_SCRIPT}`));
34
+ return;
35
+ }
36
+
37
+ const child = spawn('node', [DEMO_SERVER_SCRIPT], {
38
+ cwd: JSGUI3_HTML_PATH,
39
+ stdio: ['pipe', 'pipe', 'pipe'],
40
+ env: { ...process.env, HOME: process.env.USERPROFILE || process.env.HOME }
41
+ });
42
+
43
+ let started = false;
44
+ const timeout = setTimeout(() => {
45
+ if (!started) {
46
+ reject(new Error('Demo server startup timeout (15s)'));
47
+ }
48
+ }, 15000);
49
+
50
+ child.stdout.on('data', (data) => {
51
+ const text = data.toString();
52
+ process.stdout.write(` [demo-server] ${text}`);
53
+ if (text.includes('running at') && !started) {
54
+ started = true;
55
+ clearTimeout(timeout);
56
+ // Give it a moment to settle
57
+ setTimeout(() => resolve(child), 500);
58
+ }
59
+ });
60
+
61
+ child.stderr.on('data', (data) => {
62
+ process.stderr.write(` [demo-server-err] ${data.toString()}`);
63
+ });
64
+
65
+ child.on('error', (err) => {
66
+ clearTimeout(timeout);
67
+ reject(err);
68
+ });
69
+
70
+ child.on('exit', (code) => {
71
+ if (!started) {
72
+ clearTimeout(timeout);
73
+ reject(new Error(`Demo server exited with code ${code} before starting`));
74
+ }
75
+ });
76
+ });
77
+ };
78
+
79
+ /**
80
+ * Stop the demo server child process.
81
+ */
82
+ const stop_demo_server = (child) => {
83
+ return new Promise((resolve) => {
84
+ if (!child || child.killed) {
85
+ resolve();
86
+ return;
87
+ }
88
+
89
+ child.on('exit', () => resolve());
90
+ child.kill('SIGTERM');
91
+
92
+ // Force kill after 3s
93
+ setTimeout(() => {
94
+ if (!child.killed) {
95
+ child.kill('SIGKILL');
96
+ }
97
+ resolve();
98
+ }, 3000);
99
+ });
100
+ };
101
+
102
+ const format_bytes = (bytes) => {
103
+ if (bytes < 1024) return `${bytes} B`;
104
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
105
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
106
+ };
107
+
108
+ // ============================================
109
+ // Main
110
+ // ============================================
111
+ const main = async () => {
112
+ console.log('╔══════════════════════════════════════════════╗');
113
+ console.log('║ Color Controls Screenshot Capture ║');
114
+ console.log('╚══════════════════════════════════════════════╝\n');
115
+
116
+ let demo_server;
117
+
118
+ try {
119
+ // 1. Start demo server
120
+ demo_server = await start_demo_server();
121
+ console.log(`✓ Demo server started (PID: ${demo_server.pid})\n`);
122
+
123
+ // 2. Launch Puppeteer browser
124
+ console.log('Launching Puppeteer browser...');
125
+ await launch_browser();
126
+ console.log('✓ Browser launched\n');
127
+
128
+ // 3. Capture screenshots
129
+ console.log(`Capturing screenshots to: ${OUTPUT_DIR}\n`);
130
+
131
+ const results = await capture_url_screenshots(DEMO_URL, OUTPUT_DIR, {
132
+ width: 1280,
133
+ height: 900,
134
+ wait_ms: 2000,
135
+ section_selectors: [
136
+ '.demo-section:nth-child(2)', // Section 1: Color_Grid 12x12
137
+ '.demo-section:nth-child(3)', // Section 2: Color_Grid 4x2
138
+ '.demo-section:nth-child(4)', // Section 3: Color_Palette
139
+ '.demo-section:nth-child(5)', // Section 4: Palette Comparison
140
+ '.demo-section:nth-child(6)', // Section 5: Raw HTML Swatches
141
+ '.demo-section:nth-child(7)', // Section 6: Optimized Crayola
142
+ '.demo-section:nth-child(8)', // Section 7: Pastel Palette
143
+ '.demo-section:nth-child(9)' // Section 8: Extended 144
144
+ ],
145
+ section_names: [
146
+ 'section_1_color_grid_12x12',
147
+ 'section_2_color_grid_4x2',
148
+ 'section_3_color_palette',
149
+ 'section_4_palette_comparison',
150
+ 'section_5_raw_swatches',
151
+ 'section_6_optimized_crayola',
152
+ 'section_7_pastel_palette',
153
+ 'section_8_extended_144'
154
+ ]
155
+ });
156
+
157
+ // 4. Print summary
158
+ console.log('\n════════════════════════════════════════════════');
159
+ console.log(' Screenshot Capture Summary');
160
+ console.log('════════════════════════════════════════════════\n');
161
+
162
+ const table_data = results.map(r => ({
163
+ Name: r.name,
164
+ Dimensions: r.error ? 'FAILED' : `${r.width}×${r.height}`,
165
+ Size: r.error ? r.error : format_bytes(r.size_bytes),
166
+ Path: r.error ? '' : path.basename(r.path)
167
+ }));
168
+
169
+ console.table(table_data);
170
+
171
+ const success_count = results.filter(r => !r.error).length;
172
+ const fail_count = results.filter(r => r.error).length;
173
+
174
+ console.log(`\n✓ ${success_count} screenshots captured`);
175
+ if (fail_count > 0) {
176
+ console.log(`⚠ ${fail_count} captures failed`);
177
+ }
178
+ console.log(`\nOutput directory: ${OUTPUT_DIR}`);
179
+
180
+ } catch (err) {
181
+ console.error(`\n✗ Error: ${err.message}`);
182
+ if (err.stack) console.error(err.stack);
183
+ process.exitCode = 1;
184
+ } finally {
185
+ // Cleanup
186
+ console.log('\nCleaning up...');
187
+ await close_browser().catch(() => { });
188
+ if (demo_server) {
189
+ await stop_demo_server(demo_server);
190
+ console.log('✓ Demo server stopped');
191
+ }
192
+ console.log('Done.');
193
+ }
194
+ };
195
+
196
+ main();