jsgui3-server 0.0.140 → 0.0.141

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 (32) hide show
  1. package/.github/agents/jsgui3-server.agent.md +699 -0
  2. package/.github/instructions/copilot.instructions.md +180 -0
  3. package/.playwright-mcp/page-2025-11-29T23-39-18-629Z.png +0 -0
  4. package/.playwright-mcp/page-2025-11-29T23-39-31-903Z.png +0 -0
  5. package/.playwright-mcp/page-2025-11-30T00-33-56-265Z.png +0 -0
  6. package/.playwright-mcp/page-2025-11-30T00-34-06-619Z.png +0 -0
  7. package/docs/agent-development-guide.md +108 -4
  8. package/docs/api-reference.md +116 -0
  9. package/docs/controls-development.md +127 -0
  10. package/docs/css/luxuryObsidianCss.js +1203 -0
  11. package/docs/css/obsidian-scrollbars.css +370 -0
  12. package/docs/diagrams/jsgui3-stack.svg +568 -0
  13. package/docs/guides/JSGUI3_UI_ARCHITECTURE_GUIDE.md +2527 -0
  14. package/docs/guides/OBSIDIAN_LUXURY_DESIGN_GUIDE.md +847 -0
  15. package/docs/jsgui3-vs-express-comparison.svg +542 -0
  16. package/docs/jsgui3-vs-nestjs-comparison.svg +550 -0
  17. package/docs/publishers-guide.md +76 -0
  18. package/docs/troubleshooting.md +51 -0
  19. package/examples/controls/15) window, observable SSE/README.md +125 -0
  20. package/examples/controls/15) window, observable SSE/check.js +144 -0
  21. package/examples/controls/15) window, observable SSE/client.js +395 -0
  22. package/examples/controls/15) window, observable SSE/server.js +111 -0
  23. package/http/responders/static/Static_Route_HTTP_Responder.js +16 -16
  24. package/module.js +7 -0
  25. package/package.json +7 -6
  26. package/port-utils.js +112 -0
  27. package/serve-factory.js +27 -5
  28. package/tests/README.md +40 -26
  29. package/tests/examples-controls.e2e.test.js +164 -0
  30. package/tests/observable-sse.test.js +363 -0
  31. package/tests/port-utils.test.js +114 -0
  32. package/tests/test-runner.js +13 -12
@@ -100,6 +100,82 @@ const publisher = new HTTP_Function_Publisher({
100
100
  - Support for common image formats (JPEG, PNG, SVG, etc.)
101
101
  - Efficient streaming for large files
102
102
 
103
+ ### HTTP_Observable_Publisher
104
+
105
+ **Purpose:** Streams observable data to clients using Server-Sent Events (SSE).
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');
117
+
118
+ // Create a hot observable that emits continuously
119
+ let tick_count = 0;
120
+ const tick_stream = observable((next, complete, error) => {
121
+ const interval = setInterval(() => {
122
+ tick_count++;
123
+ next({
124
+ tick: tick_count,
125
+ timestamp: Date.now(),
126
+ message: `Server tick #${tick_count}`
127
+ });
128
+ }, 1000);
129
+
130
+ // Return cleanup function
131
+ return [() => clearInterval(interval)];
132
+ });
133
+
134
+ // Create the SSE publisher
135
+ const publisher = new Observable_Publisher({
136
+ obs: tick_stream
137
+ });
138
+
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
146
+ const eventSource = new EventSource('/api/stream');
147
+
148
+ eventSource.onmessage = (event) => {
149
+ if (event.data === 'OK') {
150
+ console.log('SSE handshake complete');
151
+ return;
152
+ }
153
+ const data = JSON.parse(event.data);
154
+ console.log('Received:', data);
155
+ };
156
+
157
+ eventSource.onerror = () => {
158
+ eventSource.close();
159
+ };
160
+ ```
161
+
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
+ ```
176
+
177
+ **See Also:** [Observable SSE Demo](../examples/controls/15)%20window,%20observable%20SSE/) for a complete working example.
178
+
103
179
  ## Publisher Architecture
104
180
 
105
181
  ### Base Publisher Class
@@ -216,6 +216,57 @@ input.js:1:0: ERROR: Expected identifier but found "}"
216
216
  }
217
217
  ```
218
218
 
219
+ ### Text Content Not Rendering
220
+
221
+ **Symptoms:**
222
+ - HTML elements appear but are empty
223
+ - Buttons have text but divs, spans, h2 don't
224
+ - Server-side rendered HTML shows empty tags
225
+
226
+ **Cause:** Using `text` property instead of `.add()` method for HTML elements.
227
+
228
+ **Solutions:**
229
+
230
+ 1. **Use `.add()` for HTML elements (div, span, h2, etc.):**
231
+ ```javascript
232
+ // ❌ WRONG - text won't render
233
+ const title = new controls.h2({ context, text: 'My Title' });
234
+
235
+ // ❌ WRONG - text property won't render
236
+ const div = new controls.div({ context });
237
+ div.text = 'Content';
238
+
239
+ // ✅ CORRECT - use .add() method
240
+ const title = new controls.h2({ context });
241
+ title.add('My Title');
242
+
243
+ const div = new controls.div({ context });
244
+ div.add('Content');
245
+ ```
246
+
247
+ 2. **Composite controls (Button, Checkbox) DO support text property:**
248
+ ```javascript
249
+ // ✅ CORRECT - Button handles text internally
250
+ const button = new controls.Button({ context, text: 'Click Me' });
251
+ ```
252
+
253
+ 3. **Test rendering without server:**
254
+ Create a `check.js` file to verify text renders:
255
+ ```javascript
256
+ const jsgui = require('./client');
257
+ const { MyControl } = jsgui.controls;
258
+ const Server_Page_Context = require('jsgui3-server/page-context');
259
+
260
+ const context = new Server_Page_Context();
261
+ const control = new MyControl({ context });
262
+ const html = control.all_html_render();
263
+
264
+ // Check if expected text is in HTML
265
+ console.log(html.includes('Expected Text') ? 'PASS' : 'FAIL');
266
+ ```
267
+
268
+ **Why this happens:** HTML element controls (`div`, `h2`, `span`) are thin wrappers around DOM elements. Text is a child node, not a property. Composite controls like `Button` have explicit code to handle the `text` property.
269
+
219
270
  ### CSS Not Loading
220
271
 
221
272
  **Symptoms:**
@@ -0,0 +1,125 @@
1
+ # Observable SSE Demo
2
+
3
+ This example demonstrates the **HTTP_Observable_Publisher** with Server-Sent Events (SSE), showing how jsgui3-server's observable-first architecture enables real-time streaming from server to client.
4
+
5
+ ![Demo showing real-time tick counter updating via SSE](../../.playwright-mcp/page-2025-11-29T23-39-31-903Z.png)
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ cd "examples/controls/15) window, observable SSE"
11
+ node server.js
12
+ # Open http://localhost:52015
13
+ ```
14
+
15
+ ## What This Demonstrates
16
+
17
+ ### Server Side (`server.js`)
18
+
19
+ 1. **Observable Creation** with `fnl`:
20
+ ```javascript
21
+ const {obs} = require('fnl');
22
+
23
+ const progress_observable = obs((next, complete, error) => {
24
+ // next(data) - emit intermediate values
25
+ // complete(result) - signal completion
26
+ // error(err) - signal failure
27
+ return [cleanup_fn]; // cleanup functions
28
+ });
29
+ ```
30
+
31
+ 2. **HTTP_Observable_Publisher** setup:
32
+ ```javascript
33
+ const Observable_Publisher = require('jsgui3-server/publishers/http-observable-publisher');
34
+
35
+ const publisher = new Observable_Publisher({ obs: progress_observable });
36
+ server.server_router.set_route('/api/progress-stream', publisher, publisher.handle_http);
37
+ ```
38
+
39
+ 3. **SSE Transport**: The publisher automatically serves `text/event-stream` content type with chunked transfer encoding.
40
+
41
+ ### Client Side (`client.js`)
42
+
43
+ 1. **EventSource API** for consuming SSE:
44
+ ```javascript
45
+ const event_source = new EventSource('/api/progress-stream');
46
+
47
+ event_source.onmessage = (event) => {
48
+ const data = JSON.parse(event.data);
49
+ update_ui(data);
50
+ };
51
+ ```
52
+
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
+
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
+ ```
72
+
73
+ ## Observable Pattern Benefits
74
+
75
+ | Approach | When to Use |
76
+ |----------|-------------|
77
+ | **Observable + SSE** | Long-running operations, real-time progress, streaming data |
78
+ | **Promise** | Single async result (most API calls) |
79
+ | **Sync return** | Immediate data, no I/O |
80
+
81
+ ## Architecture
82
+
83
+ ```
84
+ ┌─────────────────────────────────────────────────────────────┐
85
+ │ Server │
86
+ │ ┌──────────────────┐ ┌────────────────────────────────┐ │
87
+ │ │ Observable │───►│ HTTP_Observable_Publisher │ │
88
+ │ │ obs((next,...)=>{│ │ • Sets text/event-stream │ │
89
+ │ │ next(progress) │ │ • Writes SSE format │ │
90
+ │ │ }) │ │ • Keeps connection open │ │
91
+ │ └──────────────────┘ └───────────────┬────────────────┘ │
92
+ └──────────────────────────────────────────┼──────────────────┘
93
+ │ HTTP
94
+
95
+ ┌──────────────────────────────────────────┴──────────────────┐
96
+ │ Browser │
97
+ │ ┌────────────────────────────────────────────────────────┐ │
98
+ │ │ EventSource('/api/progress-stream') │ │
99
+ │ │ .onmessage = (event) => { │ │
100
+ │ │ update_ui(JSON.parse(event.data)); │ │
101
+ │ │ } │ │
102
+ │ └────────────────────────────────────────────────────────┘ │
103
+ └─────────────────────────────────────────────────────────────┘
104
+ ```
105
+
106
+ ## Files
107
+
108
+ - `client.js` - Control with SSE consumer and reactive UI
109
+ - `server.js` - Server with observable publisher setup
110
+ - `README.md` - This documentation
111
+
112
+ ## Future Direction
113
+
114
+ The strategic goal is to auto-detect observable returns in function publishers:
115
+
116
+ ```javascript
117
+ // Future: This should "just work" with SSE transport
118
+ server.publish('/api/progress', () => {
119
+ return obs((next, complete, error) => {
120
+ // Long-running operation...
121
+ });
122
+ });
123
+ ```
124
+
125
+ See `.github/agents/jsgui3-server.agent.md` for the full observable-first strategy.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * check.js - Verify that all text renders properly in the Observable SSE Demo control
3
+ *
4
+ * This script instantiates the control server-side and checks that all expected
5
+ * text content is present in the rendered HTML output. No server required.
6
+ *
7
+ * Run with: node check.js
8
+ */
9
+
10
+ const jsgui = require('./client');
11
+ const {Observable_Demo_UI} = jsgui.controls;
12
+
13
+ // Create a server-side page context for rendering
14
+ const Server_Page_Context = require('../../../page-context');
15
+ const context = new Server_Page_Context();
16
+
17
+ console.log('Observable SSE Demo - Text Rendering Check');
18
+ console.log('='.repeat(50));
19
+ console.log('');
20
+
21
+ // Instantiate the control (no spec.el, so compose() will run)
22
+ const demo_ui = new Observable_Demo_UI({
23
+ context: context
24
+ });
25
+
26
+ // Render to HTML
27
+ const html = demo_ui.all_html_render();
28
+
29
+ // Define all expected text content
30
+ const expected_texts = [
31
+ // Header
32
+ { text: 'Observable SSE Demo - Real-Time Tick Stream', description: 'Page title (h2)' },
33
+
34
+ // Status section
35
+ { text: 'Status: Not connected', description: 'Initial status text' },
36
+
37
+ // Tick display
38
+ { text: '--', description: 'Initial tick count placeholder' },
39
+ { text: 'Server Ticks', description: 'Tick label' },
40
+
41
+ // Event log label
42
+ { text: 'Event Log (SSE messages):', description: 'Log section label' },
43
+
44
+ // Buttons
45
+ { text: 'Connect to SSE', description: 'Connect button text' },
46
+ { text: 'Disconnect', description: 'Disconnect button text' },
47
+ { text: 'Clear Log', description: 'Clear Log button text' },
48
+ ];
49
+
50
+ // Check each expected text
51
+ let pass_count = 0;
52
+ let fail_count = 0;
53
+
54
+ console.log('Checking rendered HTML for expected text content:');
55
+ console.log('');
56
+
57
+ for (const {text, description} of expected_texts) {
58
+ const found = html.includes(text);
59
+ const status = found ? '✓ PASS' : '✗ FAIL';
60
+ const color = found ? '\x1b[32m' : '\x1b[31m';
61
+ const reset = '\x1b[0m';
62
+
63
+ console.log(` ${color}${status}${reset} "${text}"`);
64
+ console.log(` └─ ${description}`);
65
+
66
+ if (found) {
67
+ pass_count++;
68
+ } else {
69
+ fail_count++;
70
+ }
71
+ }
72
+
73
+ console.log('');
74
+ console.log('-'.repeat(50));
75
+ console.log(`Results: ${pass_count} passed, ${fail_count} failed`);
76
+ console.log('');
77
+
78
+ // Also check for important HTML structure
79
+ console.log('Additional structural checks:');
80
+ console.log('');
81
+
82
+ const structural_checks = [
83
+ { pattern: 'id="status-label"', description: 'Status label has ID' },
84
+ { pattern: 'id="tick-count"', description: 'Tick count has ID' },
85
+ { pattern: 'id="event-log"', description: 'Event log has ID' },
86
+ { pattern: 'id="connect-btn"', description: 'Connect button has ID' },
87
+ { pattern: 'id="disconnect-btn"', description: 'Disconnect button has ID' },
88
+ { pattern: 'id="clear-btn"', description: 'Clear button has ID' },
89
+ { pattern: '<button', description: 'Button elements present' },
90
+ { pattern: '<h2', description: 'H2 heading present' },
91
+ { pattern: 'class="sse-container"', description: 'SSE container class' },
92
+ { pattern: 'class="tick-display"', description: 'Tick display class' },
93
+ { pattern: 'class="button-container"', description: 'Button container class' },
94
+ ];
95
+
96
+ let struct_pass = 0;
97
+ let struct_fail = 0;
98
+
99
+ for (const {pattern, description} of structural_checks) {
100
+ const found = html.includes(pattern);
101
+ const status = found ? '✓ PASS' : '✗ FAIL';
102
+ const color = found ? '\x1b[32m' : '\x1b[31m';
103
+ const reset = '\x1b[0m';
104
+
105
+ console.log(` ${color}${status}${reset} ${pattern}`);
106
+ console.log(` └─ ${description}`);
107
+
108
+ if (found) {
109
+ struct_pass++;
110
+ } else {
111
+ struct_fail++;
112
+ }
113
+ }
114
+
115
+ console.log('');
116
+ console.log('-'.repeat(50));
117
+ console.log(`Structural: ${struct_pass} passed, ${struct_fail} failed`);
118
+ console.log('');
119
+
120
+ // Final summary
121
+ const total_pass = pass_count + struct_pass;
122
+ const total_fail = fail_count + struct_fail;
123
+ const total = total_pass + total_fail;
124
+
125
+ console.log('='.repeat(50));
126
+ if (total_fail === 0) {
127
+ console.log('\x1b[32m✓ ALL CHECKS PASSED\x1b[0m');
128
+ console.log(` ${total_pass}/${total} checks successful`);
129
+ } else {
130
+ console.log('\x1b[31m✗ SOME CHECKS FAILED\x1b[0m');
131
+ console.log(` ${total_pass}/${total} checks passed, ${total_fail} failed`);
132
+ }
133
+ console.log('='.repeat(50));
134
+
135
+ // Optionally output the full HTML for debugging
136
+ if (process.argv.includes('--html') || process.argv.includes('-h')) {
137
+ console.log('');
138
+ console.log('Rendered HTML:');
139
+ console.log('-'.repeat(50));
140
+ console.log(html);
141
+ }
142
+
143
+ // Exit with appropriate code
144
+ process.exit(total_fail > 0 ? 1 : 0);