jsgui3-server 0.0.148 → 0.0.150

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 (154) hide show
  1. package/.github/agents/Mobile Developer.agent.md +89 -0
  2. package/.github/workflows/control-scan-manifest-check.yml +31 -0
  3. package/AGENTS.md +4 -0
  4. package/README.md +215 -3
  5. package/admin-ui/client.js +81 -51
  6. package/admin-ui/v1/admin_auth_service.js +197 -0
  7. package/admin-ui/v1/admin_user_store.js +71 -0
  8. package/admin-ui/v1/client.js +17 -0
  9. package/admin-ui/v1/controls/admin_shell.js +1399 -0
  10. package/admin-ui/v1/controls/group_box.js +84 -0
  11. package/admin-ui/v1/controls/stat_card.js +125 -0
  12. package/admin-ui/v1/server.js +658 -0
  13. package/admin-ui/v1/utils/formatters.js +68 -0
  14. package/dev-status.svg +139 -0
  15. package/docs/admin-extension-guide.md +345 -0
  16. package/docs/api-reference.md +301 -43
  17. package/docs/books/adaptive-control-improvements/01-control-candidate-matrix.md +122 -0
  18. package/docs/books/adaptive-control-improvements/02-tier-1-layout-playbooks.md +207 -0
  19. package/docs/books/adaptive-control-improvements/03-tier-2-navigation-form-overlay.md +140 -0
  20. package/docs/books/adaptive-control-improvements/04-cross-cutting-platform-functionality.md +141 -0
  21. package/docs/books/adaptive-control-improvements/05-styling-theming-density-upgrades.md +114 -0
  22. package/docs/books/adaptive-control-improvements/06-testing-quality-gates.md +97 -0
  23. package/docs/books/adaptive-control-improvements/07-delivery-roadmap-and-ownership.md +137 -0
  24. package/docs/books/adaptive-control-improvements/08-appendix-tier1-acceptance-and-pr-templates.md +261 -0
  25. package/docs/books/adaptive-control-improvements/README.md +66 -0
  26. package/docs/books/admin-ui-authentication/01-threat-model-and-goals.md +124 -0
  27. package/docs/books/admin-ui-authentication/02-session-model-and-token-model.md +75 -0
  28. package/docs/books/admin-ui-authentication/03-auth-middleware-patterns.md +77 -0
  29. package/docs/books/admin-ui-authentication/README.md +25 -0
  30. package/docs/books/creating-a-new-admin-ui/01-introduction-and-vision.md +130 -0
  31. package/docs/books/creating-a-new-admin-ui/02-architecture-and-data-flow.md +298 -0
  32. package/docs/books/creating-a-new-admin-ui/03-server-introspection.md +381 -0
  33. package/docs/books/creating-a-new-admin-ui/04-admin-module-adapter-layer.md +592 -0
  34. package/docs/books/creating-a-new-admin-ui/05-domain-controls-stat-cards-and-gauges.md +513 -0
  35. package/docs/books/creating-a-new-admin-ui/06-domain-controls-process-manager.md +544 -0
  36. package/docs/books/creating-a-new-admin-ui/07-domain-controls-resource-pool-inspector.md +493 -0
  37. package/docs/books/creating-a-new-admin-ui/08-domain-controls-route-table-and-api-explorer.md +586 -0
  38. package/docs/books/creating-a-new-admin-ui/09-domain-controls-log-viewer-and-activity-feed.md +490 -0
  39. package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md +526 -0
  40. package/docs/books/creating-a-new-admin-ui/11-domain-controls-configuration-panel.md +808 -0
  41. package/docs/books/creating-a-new-admin-ui/12-admin-shell-layout-sidebar-navigation.md +210 -0
  42. package/docs/books/creating-a-new-admin-ui/13-telemetry-integration.md +556 -0
  43. package/docs/books/creating-a-new-admin-ui/14-realtime-sse-observable-integration.md +485 -0
  44. package/docs/books/creating-a-new-admin-ui/15-styling-theming-aero-design-system.md +521 -0
  45. package/docs/books/creating-a-new-admin-ui/16-testing-and-quality-assurance.md +147 -0
  46. package/docs/books/creating-a-new-admin-ui/17-next-steps-process-resource-roadmap.md +356 -0
  47. package/docs/books/creating-a-new-admin-ui/README.md +68 -0
  48. package/docs/books/device-adaptive-composition/01-platform-feature-audit.md +177 -0
  49. package/docs/books/device-adaptive-composition/02-responsive-composition-model.md +187 -0
  50. package/docs/books/device-adaptive-composition/03-data-model-vs-view-model.md +231 -0
  51. package/docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md +234 -0
  52. package/docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md +193 -0
  53. package/docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md +346 -0
  54. package/docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md +265 -0
  55. package/docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md +250 -0
  56. package/docs/books/device-adaptive-composition/README.md +47 -0
  57. package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
  58. package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
  59. package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
  60. package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
  61. package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
  62. package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
  63. package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
  64. package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
  65. package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
  66. package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
  67. package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
  68. package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
  69. package/docs/bundling-system-deep-dive.md +9 -4
  70. package/docs/comparison-report-express-plex-cpanel.md +549 -0
  71. package/docs/comprehensive-documentation.md +49 -18
  72. package/docs/configuration-reference.md +152 -27
  73. package/docs/core/README.md +19 -0
  74. package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
  75. package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
  76. package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
  77. package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
  78. package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
  79. package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
  80. package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
  81. package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
  82. package/docs/designs/server-admin-interface-aero.svg +611 -0
  83. package/docs/publishers-guide.md +59 -4
  84. package/docs/resources-guide.md +184 -35
  85. package/docs/simple-server-api-design.md +72 -17
  86. package/docs/system-architecture.md +18 -14
  87. package/docs/troubleshooting.md +84 -53
  88. package/examples/controls/15) window, observable SSE/server.js +6 -1
  89. package/examples/controls/19) window, auto observable ui/server.js +9 -0
  90. package/examples/controls/20) window, task manager app/README.md +133 -0
  91. package/examples/controls/20) window, task manager app/client.js +797 -0
  92. package/examples/controls/20) window, task manager app/server.js +178 -0
  93. package/examples/controls/6) window, color_palette/client.js +165 -68
  94. package/examples/controls/9) window, date picker/client.js +362 -76
  95. package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
  96. package/examples/jsgui3-html/06) theming/client.js +22 -1
  97. package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
  98. package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
  99. package/lab/experiments/capture-color-controls.js +196 -0
  100. package/lab/results/screenshots/color-controls/full_page.png +0 -0
  101. package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
  102. package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
  103. package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
  104. package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
  105. package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
  106. package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
  107. package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
  108. package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
  109. package/lab/screenshot-utils.js +248 -0
  110. package/module.js +12 -0
  111. package/package.json +12 -2
  112. package/publishers/Publishers.js +4 -3
  113. package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
  114. package/publishers/http-sse-publisher.js +341 -0
  115. package/resources/process-resource.js +950 -0
  116. package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +129 -33
  117. package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
  118. package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
  119. package/resources/remote-process-resource.js +355 -0
  120. package/resources/server-resource-pool.js +354 -41
  121. package/serve-factory.js +442 -259
  122. package/server.js +288 -13
  123. package/tests/README.md +71 -4
  124. package/tests/admin-ui-jsgui-controls.test.js +581 -0
  125. package/tests/admin-ui-render.test.js +24 -0
  126. package/tests/assigners.test.js +56 -40
  127. package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
  128. package/tests/configuration-validation.test.js +21 -18
  129. package/tests/content-analysis.test.js +7 -6
  130. package/tests/control-optimizer-cache-behavior.test.js +52 -0
  131. package/tests/control-scan-manifest-regression.test.js +144 -0
  132. package/tests/end-to-end.test.js +15 -14
  133. package/tests/error-handling.test.js +222 -179
  134. package/tests/fixtures/bundling-default-button-client.js +37 -0
  135. package/tests/fixtures/bundling-default-window-client.js +34 -0
  136. package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
  137. package/tests/fixtures/resource-monitor-client.js +319 -0
  138. package/tests/helpers/puppeteer-e2e-harness.js +317 -0
  139. package/tests/http-sse-publisher.test.js +136 -0
  140. package/tests/performance.test.js +69 -65
  141. package/tests/process-resource.test.js +138 -0
  142. package/tests/publishers.test.js +7 -7
  143. package/tests/remote-process-resource.test.js +160 -0
  144. package/tests/sass-controls.e2e.test.js +7 -1
  145. package/tests/serve-resources.test.js +270 -0
  146. package/tests/serve.test.js +120 -50
  147. package/tests/server-resource-pool.test.js +106 -0
  148. package/tests/small-controls-bundle-size.test.js +252 -0
  149. package/tests/test-runner.js +14 -1
  150. package/tests/window-examples.puppeteer.test.js +204 -1
  151. package/tests/window-resource-integration.puppeteer.test.js +585 -0
  152. package/tests/temp_invalid.js +0 -7
  153. package/tests/temp_invalid_utf8.js +0 -1
  154. package/tests/temp_malformed.js +0 -10
@@ -0,0 +1,485 @@
1
+ # Chapter 14: Real-Time Updates — SSE & Observable Integration
2
+
3
+ ## Overview
4
+
5
+ The Admin UI uses **Server-Sent Events (SSE)** as its primary real-time channel. jsgui3-server provides two publisher types that support SSE:
6
+
7
+ 1. **`HTTP_SSE_Publisher`** — Multi-client broadcast channel with event history
8
+ 2. **`HTTP_Observable_Publisher`** — Streams an observable's emissions to SSE clients
9
+
10
+ The Admin UI uses `HTTP_SSE_Publisher` for the admin event channel (`/api/admin/v1/events`), with the adapter broadcasting events from various server subsystems into this single channel.
11
+
12
+ ---
13
+
14
+ ## SSE Architecture
15
+
16
+ ```
17
+ ┌─────────────────────────────────────────────────────────────────┐
18
+ │ Server │
19
+ │ │
20
+ │ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐ │
21
+ │ │ Request Handler │ │ Resource Pool │ │ Build System │ │
22
+ │ │ (instrumented) │ │ (events) │ │ (ready event) │ │
23
+ │ └────────┬────────┘ └────────┬────────┘ └───────┬────────┘ │
24
+ │ │ │ │ │
25
+ │ ▼ ▼ ▼ │
26
+ │ ┌────────────────────────────────────────────────────────────┐ │
27
+ │ │ Admin_Module_V1._broadcast() │ │
28
+ │ │ │ │
29
+ │ │ Normalizes event data → JSON → SSE_Publisher.broadcast() │ │
30
+ │ └──────────────────────────┬─────────────────────────────────┘ │
31
+ │ │ │
32
+ │ ┌──────────────────────────▼─────────────────────────────────┐ │
33
+ │ │ HTTP_SSE_Publisher │ │
34
+ │ │ /api/admin/v1/events │ │
35
+ │ │ │ │
36
+ │ │ clients: Map<id, {res, connected_at}> │ │
37
+ │ │ event_history: Array (last 100) │ │
38
+ │ └──────┬────────────┬────────────┬───────────────────────────┘ │
39
+ │ │ │ │ │
40
+ └─────────┼────────────┼────────────┼──────────────────────────────┘
41
+ │ │ │
42
+ ▼ ▼ ▼
43
+ Browser 1 Browser 2 Browser 3
44
+ EventSource EventSource EventSource
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Event Types
50
+
51
+ The SSE channel emits the following event types:
52
+
53
+ | Event Name | Source | Frequency | Data Shape |
54
+ |------------|--------|-----------|------------|
55
+ | `heartbeat` | Timer (5s) | Periodic | `{ uptime, pid, memory, request_count, requests_per_minute, pool_summary }` |
56
+ | `request` | Request handler | Per-request | `{ method, url, status, duration_ms, timestamp }` |
57
+ | `resource_state_change` | Resource pool | On change | `{ resourceName, from, to, timestamp }` |
58
+ | `crashed` | Resource pool | On crash | `{ resourceName, code, signal, timestamp }` |
59
+ | `unhealthy` | Resource pool | On unhealthy | `{ resourceName, timestamp, details }` |
60
+ | `recovered` | Resource pool | On recovery | `{ resourceName, timestamp }` |
61
+ | `process_state_change` | Process resources | On change | `{ name, pid, from, to, timestamp }` |
62
+ | `process_health` | Process resources | On check | `{ name, pid, healthy, memory_usage, timestamp }` |
63
+ | `build_complete` | Publisher | On build | `{ items: [{type, size_identity, size_gzip}], built_at }` |
64
+
65
+ ---
66
+
67
+ ## Client-Side SSE Connection
68
+
69
+ ### EventSource Setup
70
+
71
+ ```javascript
72
+ // In admin-ui/v1/utils/sse_client.js
73
+
74
+ class SSE_Client {
75
+ constructor(url, options = {}) {
76
+ this._url = url;
77
+ this._handlers = {};
78
+ this._reconnect_delay = options.reconnect_delay || 3000;
79
+ this._max_reconnect_delay = options.max_reconnect_delay || 30000;
80
+ this._current_delay = this._reconnect_delay;
81
+ this._connected = false;
82
+ this._event_source = null;
83
+ }
84
+
85
+ connect() {
86
+ if (this._event_source) {
87
+ this._event_source.close();
88
+ }
89
+
90
+ this._event_source = new EventSource(this._url);
91
+
92
+ this._event_source.addEventListener('open', () => {
93
+ this._connected = true;
94
+ this._current_delay = this._reconnect_delay; // Reset backoff
95
+ this._emit_internal('connected');
96
+ });
97
+
98
+ this._event_source.addEventListener('error', () => {
99
+ this._connected = false;
100
+ this._emit_internal('disconnected');
101
+ // EventSource handles reconnection automatically,
102
+ // but we track state for the UI
103
+ });
104
+
105
+ // Register all event handlers
106
+ for (const event_name of Object.keys(this._handlers)) {
107
+ if (event_name.startsWith('_')) continue; // Skip internal events
108
+ this._event_source.addEventListener(event_name, (e) => {
109
+ try {
110
+ const data = JSON.parse(e.data);
111
+ this._handlers[event_name].forEach(fn => fn(data));
112
+ } catch (err) {
113
+ console.warn(`SSE parse error for ${event_name}:`, err);
114
+ }
115
+ });
116
+ }
117
+ }
118
+
119
+ on(event_name, handler) {
120
+ if (!this._handlers[event_name]) {
121
+ this._handlers[event_name] = [];
122
+
123
+ // If already connected, register the listener dynamically
124
+ if (this._event_source) {
125
+ this._event_source.addEventListener(event_name, (e) => {
126
+ try {
127
+ const data = JSON.parse(e.data);
128
+ this._handlers[event_name].forEach(fn => fn(data));
129
+ } catch (err) {
130
+ console.warn(`SSE parse error for ${event_name}:`, err);
131
+ }
132
+ });
133
+ }
134
+ }
135
+ this._handlers[event_name].push(handler);
136
+ }
137
+
138
+ off(event_name, handler) {
139
+ if (this._handlers[event_name]) {
140
+ this._handlers[event_name] = this._handlers[event_name].filter(fn => fn !== handler);
141
+ }
142
+ }
143
+
144
+ _emit_internal(event_name) {
145
+ const key = `_${event_name}`;
146
+ if (this._handlers[key]) {
147
+ this._handlers[key].forEach(fn => fn());
148
+ }
149
+ }
150
+
151
+ on_connected(handler) {
152
+ this.on('_connected', handler);
153
+ }
154
+
155
+ on_disconnected(handler) {
156
+ this.on('_disconnected', handler);
157
+ }
158
+
159
+ get connected() {
160
+ return this._connected;
161
+ }
162
+
163
+ close() {
164
+ if (this._event_source) {
165
+ this._event_source.close();
166
+ this._event_source = null;
167
+ }
168
+ this._connected = false;
169
+ }
170
+ }
171
+
172
+ module.exports = SSE_Client;
173
+ ```
174
+
175
+ ### Integration with Admin_Shell
176
+
177
+ ```javascript
178
+ // In Admin_Shell.activate()
179
+ _connect_sse() {
180
+ const sse = new SSE_Client('/api/admin/v1/events');
181
+
182
+ // Connection state
183
+ sse.on_connected(() => this._set_connection_status('connected'));
184
+ sse.on_disconnected(() => this._set_connection_status('disconnected'));
185
+
186
+ // Event routing
187
+ sse.on('heartbeat', (data) => this._on_heartbeat(data));
188
+ sse.on('request', (data) => this._on_request_event(data));
189
+ sse.on('resource_state_change', (data) => this._on_resource_event(data));
190
+ sse.on('crashed', (data) => this._on_resource_event(data));
191
+ sse.on('unhealthy', (data) => this._on_resource_event(data));
192
+ sse.on('recovered', (data) => this._on_resource_event(data));
193
+ sse.on('process_state_change', (data) => this._on_process_event(data));
194
+ sse.on('build_complete', (data) => this._on_build_event(data));
195
+
196
+ sse.connect();
197
+ this._sse = sse;
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Event Routing to Views
204
+
205
+ The Admin_Shell routes SSE events to whichever view is currently active:
206
+
207
+ ```javascript
208
+ _on_heartbeat(data) {
209
+ // Status bar — always updated
210
+ this._update_status_bar(data);
211
+
212
+ // Dashboard stat cards — updated if dashboard is visible
213
+ if (this._current_view === 'dashboard' && this._dashboard_controls) {
214
+ const dc = this._dashboard_controls;
215
+
216
+ dc.uptime_card.update({
217
+ value: format_uptime(data.uptime),
218
+ detail: format_bytes(data.memory?.rss || 0)
219
+ });
220
+
221
+ dc.rps_card.update({
222
+ value: data.requests_per_minute || 0,
223
+ detail: `${data.request_count || 0} total`
224
+ });
225
+
226
+ if (data.pool_summary) {
227
+ dc.pool_card.update({
228
+ value: `${data.pool_summary.running || 0}/${data.pool_summary.total || 0}`,
229
+ detail: 'running'
230
+ });
231
+ }
232
+
233
+ dc.routes_card.update({
234
+ value: data.route_count || 0,
235
+ detail: 'registered'
236
+ });
237
+ }
238
+ }
239
+
240
+ _on_request_event(data) {
241
+ // Log viewer — always receives events (for any active log viewer)
242
+ if (this._current_view === 'dashboard' && this._dashboard_controls?.activity_log) {
243
+ this._dashboard_controls.activity_log.append_request(data);
244
+ }
245
+
246
+ if (this._current_view === 'logs' && this._logs_viewer) {
247
+ this._logs_viewer.append_request(data);
248
+ }
249
+ }
250
+
251
+ _on_resource_event(data) {
252
+ // Resource table — update if visible
253
+ if (this._current_view === 'dashboard' && this._dashboard_controls?.resource_table) {
254
+ this._dashboard_controls.resource_table.refresh();
255
+ }
256
+
257
+ if (this._current_view === 'resources' && this._resources_table) {
258
+ this._resources_table.refresh();
259
+ }
260
+
261
+ // Log viewer — resource events are also logged
262
+ if (this._current_view === 'dashboard' && this._dashboard_controls?.activity_log) {
263
+ this._dashboard_controls.activity_log.append_resource_event(data);
264
+ }
265
+
266
+ if (this._current_view === 'logs' && this._logs_viewer) {
267
+ this._logs_viewer.append_resource_event(data);
268
+ }
269
+
270
+ // Status view — update health indicator
271
+ if (this._current_view === 'status') {
272
+ this._update_health_status(data);
273
+ }
274
+ }
275
+
276
+ _on_process_event(data) {
277
+ if (this._current_view === 'dashboard' && this._dashboard_controls?.process_panel) {
278
+ this._dashboard_controls.process_panel.refresh();
279
+ }
280
+
281
+ if (this._current_view === 'processes' && this._process_panel) {
282
+ this._process_panel.refresh();
283
+ }
284
+ }
285
+
286
+ _on_build_event(data) {
287
+ if (this._current_view === 'dashboard' && this._dashboard_controls?.build_status) {
288
+ this._dashboard_controls.build_status.update(data);
289
+ }
290
+
291
+ if (this._current_view === 'build' && this._build_status) {
292
+ this._build_status.update(data);
293
+ }
294
+
295
+ // Log the build event
296
+ if (this._current_view === 'dashboard' && this._dashboard_controls?.activity_log) {
297
+ this._dashboard_controls.activity_log.append_build_event(data);
298
+ }
299
+ }
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Server-Side: HTTP_SSE_Publisher Usage
305
+
306
+ ### Broadcast Method
307
+
308
+ The `HTTP_SSE_Publisher` from jsgui3-server provides a `broadcast(event_name, data)` method that sends to all connected clients:
309
+
310
+ ```javascript
311
+ // From publishers/http-sse-publisher.js
312
+ // The publisher handles:
313
+ // - Client connection registration
314
+ // - Client disconnection cleanup
315
+ // - Event history for replay on reconnect
316
+ // - JSON serialization of data
317
+ // - SSE format: "event: name\ndata: json\n\n"
318
+ ```
319
+
320
+ ### Client Tracking
321
+
322
+ The publisher maintains a `clients` Map:
323
+ ```javascript
324
+ {
325
+ client_id: {
326
+ res: http.ServerResponse,
327
+ connected_at: timestamp
328
+ }
329
+ }
330
+ ```
331
+
332
+ The admin adapter can query this to show active SSE connections:
333
+ ```javascript
334
+ get_sse_client_count() {
335
+ if (this._sse_publisher && this._sse_publisher.clients) {
336
+ return this._sse_publisher.clients.size;
337
+ }
338
+ return 0;
339
+ }
340
+ ```
341
+
342
+ ---
343
+
344
+ ## Event History & Replay
345
+
346
+ When a client reconnects (browser refresh, network hiccup), it receives the last N events from the history buffer. This prevents the dashboard from showing empty data after a reconnect.
347
+
348
+ The `HTTP_SSE_Publisher` stores up to `history_size` events (configured as 100 in the admin channel). On reconnect, the client's `Last-Event-ID` header is used to determine which events to replay:
349
+
350
+ ```javascript
351
+ // Server behavior on client connect:
352
+ // 1. Send any events after Last-Event-ID
353
+ // 2. Each broadcast includes an auto-incrementing ID
354
+ // 3. Client EventSource automatically sends Last-Event-ID on reconnect
355
+ ```
356
+
357
+ This is handled by the publisher internally — the admin module doesn't need to implement replay logic.
358
+
359
+ ---
360
+
361
+ ## Observable Publisher Integration
362
+
363
+ For existing `HTTP_Observable_Publisher` instances registered with the server, the admin UI can display their status:
364
+
365
+ ```javascript
366
+ // Adapter: enumerate observable publishers
367
+ get_observables_status(server) {
368
+ const result = [];
369
+ const publishers = server._publishers || [];
370
+
371
+ publishers.forEach(pub => {
372
+ if (pub.constructor.name === 'HTTP_Observable_Publisher' ||
373
+ pub.active_sse_connections !== undefined) {
374
+ result.push({
375
+ name: pub.name || pub.path || 'unnamed',
376
+ path: pub.path || null,
377
+ active_connections: pub.active_sse_connections || 0,
378
+ paused: pub._paused || false,
379
+ total_emitted: pub._emission_count || 0
380
+ });
381
+ }
382
+ });
383
+
384
+ return result;
385
+ }
386
+ ```
387
+
388
+ This information is displayed in the Resources view under a separate "Observable Streams" section.
389
+
390
+ ---
391
+
392
+ ## Throttling & Backpressure
393
+
394
+ ### Server-Side Throttling
395
+
396
+ High-frequency events (requests under load) could overwhelm SSE clients. The adapter applies event-level throttling:
397
+
398
+ ```javascript
399
+ // In Admin_Module_V1
400
+ _broadcast_request(data) {
401
+ // Throttle request events to max 10 per second
402
+ const now = Date.now();
403
+ if (!this._last_request_broadcast) {
404
+ this._last_request_broadcast = now;
405
+ this._request_broadcast_count = 0;
406
+ }
407
+
408
+ if (now - this._last_request_broadcast > 1000) {
409
+ // Reset window
410
+ this._last_request_broadcast = now;
411
+ this._request_broadcast_count = 0;
412
+ }
413
+
414
+ this._request_broadcast_count++;
415
+ if (this._request_broadcast_count <= 10) {
416
+ this._broadcast('request', data);
417
+ }
418
+ // Requests beyond the limit are still counted in telemetry
419
+ // but not broadcast individually
420
+ }
421
+ ```
422
+
423
+ ### Client-Side Batching
424
+
425
+ As described in Chapter 9, the Log_Viewer uses `requestAnimationFrame` to batch DOM updates when events arrive rapidly:
426
+
427
+ ```javascript
428
+ // In Log_Viewer — batched rendering
429
+ _throttled_append(entry) {
430
+ this._pending_entries.push(entry);
431
+ if (!this._flush_scheduled) {
432
+ this._flush_scheduled = true;
433
+ requestAnimationFrame(() => {
434
+ this._pending_entries.forEach(e => this._render_entry(e));
435
+ this._pending_entries = [];
436
+ this._flush_scheduled = false;
437
+ });
438
+ }
439
+ }
440
+ ```
441
+
442
+ ---
443
+
444
+ ## Connection Resilience
445
+
446
+ ### Automatic Reconnection
447
+
448
+ The browser's `EventSource` API automatically reconnects on connection loss. The SSE_Client wrapper tracks state and updates the UI accordingly:
449
+
450
+ ```
451
+ Connected ──── Connection lost ──── Reconnecting ──── Connected
452
+ ● ○ ◐ ●
453
+ green red amber green
454
+ ```
455
+
456
+ ### Stale Data Handling
457
+
458
+ After reconnection, the dashboard fetches a fresh snapshot via `GET /api/admin/v1/status` to ensure stat cards show current values, not stale data from before the disconnection:
459
+
460
+ ```javascript
461
+ // In SSE_Client
462
+ sse.on_connected(() => {
463
+ this._set_connection_status('connected');
464
+ // Refresh all snapshot data
465
+ this._fetch_initial_data();
466
+ });
467
+ ```
468
+
469
+ ---
470
+
471
+ ## SSE vs WebSocket — Why SSE
472
+
473
+ jsgui3-server already has SSE infrastructure (`HTTP_SSE_Publisher`, `HTTP_Observable_Publisher`). WebSocket support would require:
474
+ - New dependency or custom implementation
475
+ - Different publisher type
476
+ - Client-side WebSocket handling
477
+
478
+ SSE is the right choice because:
479
+ 1. **Already built** — No new infrastructure needed
480
+ 2. **Unidirectional** — Admin telemetry flows server → client only
481
+ 3. **Auto-reconnect** — Browser EventSource handles reconnection
482
+ 4. **HTTP compatible** — Works through proxies and load balancers
483
+ 5. **Event history** — The publisher already supports replay
484
+
485
+ The only case where WebSocket would be preferred is bidirectional communication (e.g., interactive terminal). For the Admin UI's Phase 1 read-only dashboard, SSE is sufficient and optimal.