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,490 @@
1
+ # Chapter 9: Domain Controls — Log Viewer & Activity Feed
2
+
3
+ ## Overview
4
+
5
+ The Log Viewer is a scrolling, real-time log display that streams server activity — HTTP request/response entries, resource state changes, build events, and warnings. In the design reference, it appears as the "Recent Activity" group box and also as a dedicated "Logs" tab within the tabbed panel.
6
+
7
+ The control supports:
8
+ - Timestamped entries with color-coded severity
9
+ - Auto-scroll to bottom (with pause on manual scroll)
10
+ - Filtering by log level and source
11
+ - Search across visible entries
12
+
13
+ ---
14
+
15
+ ## Log_Viewer Control
16
+
17
+ ### Spec
18
+
19
+ ```javascript
20
+ {
21
+ __type_name: 'log_viewer',
22
+ title: 'Recent Activity',
23
+ max_lines: 500, // Maximum lines in buffer (FIFO)
24
+ auto_scroll: true, // Auto-scroll to bottom
25
+ show_timestamps: true, // Show timestamp column
26
+ initial_entries: [ // Pre-populated entries (from recent events)
27
+ {
28
+ timestamp: 1739567000000,
29
+ level: 'info',
30
+ source: 'request',
31
+ message: 'GET / — 200 OK (14ms)'
32
+ }
33
+ ]
34
+ }
35
+ ```
36
+
37
+ ### Visual Anatomy
38
+
39
+ ```
40
+ ┌─ Recent Activity ──────────────────────────────────────┐
41
+ │ │
42
+ │ 14:23:07 File change detected: client.js — rebuild │
43
+ │ 14:23:08 Bundle built successfully (245 KB, 1.2s) │
44
+ │ 14:23:08 CSS extracted (12 KB, 12 controls) │
45
+ │ 14:23:09 ⚠ Warning: unused variable in helpers.js:42 │
46
+ │ 14:22:45 GET / — 200 OK (14ms) │
47
+ │ 14:22:38 POST /api/validateUser — 200 OK (3ms) │
48
+ │ │
49
+ └────────────────────────────────────────────────────────┘
50
+ ```
51
+
52
+ ### Constructor
53
+
54
+ ```javascript
55
+ class Log_Viewer extends jsgui.Control {
56
+ constructor(spec = {}) {
57
+ spec.__type_name = spec.__type_name || 'log_viewer';
58
+ super(spec);
59
+ const { context } = this;
60
+
61
+ this._max_lines = spec.max_lines || 500;
62
+ this._auto_scroll = spec.auto_scroll !== false;
63
+ this._show_timestamps = spec.show_timestamps !== false;
64
+ this._entries = [];
65
+ this._paused = false; // User-scrolled pause
66
+
67
+ const compose = () => {
68
+ // Optional group box wrapper
69
+ if (spec.title) {
70
+ const group = new Group_Box({ context, title: spec.title });
71
+ this.add(group);
72
+ this._container = group;
73
+ } else {
74
+ this._container = this;
75
+ }
76
+
77
+ // Filter bar (collapsible)
78
+ if (spec.show_filter !== false) {
79
+ const filter_bar = new controls.div({ context, class: 'log-filter-bar' });
80
+ this._container.add(filter_bar);
81
+ this._filter_bar = filter_bar;
82
+
83
+ // Level filter chips
84
+ const levels = ['all', 'info', 'warn', 'error', 'debug'];
85
+ levels.forEach(level => {
86
+ const chip = new controls.span({
87
+ context,
88
+ class: `log-filter-chip ${level === 'all' ? 'chip-active' : ''}`
89
+ });
90
+ chip.dom.attributes['data-level'] = level;
91
+ chip.add(level);
92
+ filter_bar.add(chip);
93
+ });
94
+
95
+ // Search input
96
+ const search = new controls.div({ context, class: 'log-search' });
97
+ filter_bar.add(search);
98
+ }
99
+
100
+ // Log output area
101
+ const output = new controls.div({ context, class: 'log-output' });
102
+ this._container.add(output);
103
+ this._output = output;
104
+
105
+ // Pre-populate initial entries
106
+ if (spec.initial_entries) {
107
+ spec.initial_entries.forEach(entry => this._render_entry(entry));
108
+ }
109
+ };
110
+
111
+ if (!spec.el) { compose(); }
112
+ }
113
+
114
+ _render_entry(entry) {
115
+ const { context } = this;
116
+ const line = new controls.div({
117
+ context,
118
+ class: `log-line log-${entry.level || 'info'}`
119
+ });
120
+
121
+ // Timestamp
122
+ if (this._show_timestamps) {
123
+ const time = new controls.span({ context, class: 'log-timestamp' });
124
+ time.add(format_time(entry.timestamp));
125
+ line.add(time);
126
+ }
127
+
128
+ // Message
129
+ const msg = new controls.span({ context, class: 'log-message' });
130
+ msg.add(entry.message || '');
131
+ line.add(msg);
132
+
133
+ this._output.add(line);
134
+ this._entries.push(entry);
135
+
136
+ // Trim if over max
137
+ if (this._entries.length > this._max_lines) {
138
+ this._entries.shift();
139
+ if (this._output.el && this._output.el.firstChild) {
140
+ this._output.el.removeChild(this._output.el.firstChild);
141
+ }
142
+ }
143
+ }
144
+
145
+ // Client-side: append a new log entry
146
+ append(entry) {
147
+ this._render_entry(entry);
148
+
149
+ // Auto-scroll to bottom
150
+ if (this._auto_scroll && !this._paused && this._output.el) {
151
+ this._output.el.scrollTop = this._output.el.scrollHeight;
152
+ }
153
+ }
154
+
155
+ // Client-side: append from SSE data
156
+ append_request(req_data) {
157
+ const status_color = req_data.status >= 400 ? 'error'
158
+ : req_data.status >= 300 ? 'warn' : 'info';
159
+
160
+ this.append({
161
+ timestamp: req_data.timestamp,
162
+ level: status_color,
163
+ source: 'request',
164
+ message: `${req_data.method} ${req_data.url} — ${req_data.status} (${req_data.duration_ms}ms)`
165
+ });
166
+ }
167
+
168
+ append_resource_event(event_data) {
169
+ const level = event_data.to === 'crashed' ? 'error'
170
+ : event_data.to === 'unhealthy' ? 'warn' : 'info';
171
+
172
+ this.append({
173
+ timestamp: event_data.timestamp || Date.now(),
174
+ level: level,
175
+ source: 'resource',
176
+ message: `Resource "${event_data.resourceName}": ${event_data.from || '?'} → ${event_data.to || '?'}`
177
+ });
178
+ }
179
+
180
+ append_build_event(build_data) {
181
+ const js_item = build_data.items?.find(i => i.type === 'js');
182
+ const css_item = build_data.items?.find(i => i.type === 'css');
183
+
184
+ this.append({
185
+ timestamp: build_data.built_at || Date.now(),
186
+ level: 'info',
187
+ source: 'build',
188
+ message: `Bundle built: JS ${format_bytes(js_item?.size_identity || 0)}, CSS ${format_bytes(css_item?.size_identity || 0)}`
189
+ });
190
+ }
191
+
192
+ clear() {
193
+ this._entries = [];
194
+ if (this._output && this._output.el) {
195
+ this._output.el.innerHTML = '';
196
+ }
197
+ }
198
+
199
+ activate() {
200
+ if (!this.__active) {
201
+ super.activate();
202
+
203
+ // Pause auto-scroll when user scrolls up
204
+ if (this._output && this._output.el) {
205
+ this._output.el.addEventListener('scroll', () => {
206
+ const el = this._output.el;
207
+ const at_bottom = el.scrollHeight - el.scrollTop - el.clientHeight < 30;
208
+ this._paused = !at_bottom;
209
+ });
210
+ }
211
+
212
+ // Filter chip click handling
213
+ const chips = document.querySelectorAll('.log-filter-chip');
214
+ chips.forEach(chip => {
215
+ chip.addEventListener('click', () => {
216
+ chips.forEach(c => c.classList.remove('chip-active'));
217
+ chip.classList.add('chip-active');
218
+ const level = chip.getAttribute('data-level');
219
+ this._apply_filter(level);
220
+ });
221
+ });
222
+ }
223
+ }
224
+
225
+ _apply_filter(level) {
226
+ if (!this._output || !this._output.el) return;
227
+ const lines = this._output.el.querySelectorAll('.log-line');
228
+ lines.forEach(line => {
229
+ if (level === 'all') {
230
+ line.style.display = '';
231
+ } else {
232
+ line.style.display = line.classList.contains(`log-${level}`) ? '' : 'none';
233
+ }
234
+ });
235
+ }
236
+ }
237
+ ```
238
+
239
+ ### CSS
240
+
241
+ ```css
242
+ .log_viewer {
243
+ display: flex;
244
+ flex-direction: column;
245
+ }
246
+
247
+ .log-filter-bar {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 4px;
251
+ padding: 6px 8px;
252
+ border-bottom: 1px solid #E0DCD4;
253
+ background: #F4F2EC;
254
+ flex-wrap: wrap;
255
+ }
256
+
257
+ .log-filter-chip {
258
+ font-size: 8px;
259
+ padding: 2px 8px;
260
+ border-radius: 10px;
261
+ cursor: pointer;
262
+ background: transparent;
263
+ color: #808080;
264
+ border: 1px solid transparent;
265
+ transition: all 0.15s;
266
+ text-transform: uppercase;
267
+ letter-spacing: 0.5px;
268
+ }
269
+
270
+ .log-filter-chip:hover {
271
+ background: rgba(68, 136, 204, 0.1);
272
+ color: #4488CC;
273
+ }
274
+
275
+ .log-filter-chip.chip-active {
276
+ background: rgba(68, 136, 204, 0.15);
277
+ border-color: #4488CC;
278
+ color: #2A5A8A;
279
+ }
280
+
281
+ .log-output {
282
+ font-family: 'Consolas', 'Courier New', monospace;
283
+ font-size: 8.5px;
284
+ line-height: 1.8;
285
+ overflow-y: auto;
286
+ max-height: 300px;
287
+ padding: 4px 8px;
288
+ }
289
+
290
+ .log-line {
291
+ display: flex;
292
+ gap: 12px;
293
+ padding: 1px 0;
294
+ border-bottom: 0.5px solid transparent;
295
+ }
296
+
297
+ .log-line:hover {
298
+ background: rgba(0, 0, 0, 0.02);
299
+ }
300
+
301
+ .log-timestamp {
302
+ color: #808080;
303
+ min-width: 60px;
304
+ flex-shrink: 0;
305
+ }
306
+
307
+ .log-message {
308
+ flex: 1;
309
+ word-break: break-word;
310
+ }
311
+
312
+ /* Level-based coloring */
313
+ .log-info .log-message { color: #2A4060; }
314
+ .log-warn .log-message { color: #D8A020; }
315
+ .log-error .log-message { color: #CC4444; }
316
+ .log-debug .log-message { color: #808080; }
317
+
318
+ .log-info .log-timestamp { color: #808080; }
319
+ .log-warn .log-timestamp { color: #B08800; }
320
+ .log-error .log-timestamp { color: #AA3333; }
321
+
322
+ /* Green highlights for success messages */
323
+ .log-line.log-success .log-message { color: #2A6A2A; }
324
+ ```
325
+
326
+ ---
327
+
328
+ ## Activity Feed Entries
329
+
330
+ The log viewer receives entries from multiple SSE event types:
331
+
332
+ ### Request Entries
333
+
334
+ From SSE event `request`:
335
+ ```javascript
336
+ event_source.addEventListener('request', (e) => {
337
+ const data = JSON.parse(e.data);
338
+ log_viewer.append_request(data);
339
+ });
340
+ ```
341
+
342
+ Rendered as:
343
+ ```
344
+ 14:22:45 GET / — 200 OK (14ms)
345
+ 14:22:38 POST /api/validateUser — 200 OK (3ms)
346
+ ```
347
+
348
+ ### Resource State Change Entries
349
+
350
+ From SSE event `resource_state_change`:
351
+ ```javascript
352
+ event_source.addEventListener('resource_state_change', (e) => {
353
+ const data = JSON.parse(e.data);
354
+ log_viewer.append_resource_event(data);
355
+ });
356
+ ```
357
+
358
+ Rendered as:
359
+ ```
360
+ 14:20:12 Resource "Bundle Builder": stopped → running
361
+ ```
362
+
363
+ ### Build Entries
364
+
365
+ From SSE event `build_complete`:
366
+ ```javascript
367
+ event_source.addEventListener('build_complete', (e) => {
368
+ const data = JSON.parse(e.data);
369
+ log_viewer.append_build_event(data);
370
+ });
371
+ ```
372
+
373
+ Rendered as:
374
+ ```
375
+ 14:23:08 Bundle built: JS 245 KB, CSS 12 KB
376
+ ```
377
+
378
+ ### Warning/Error Entries
379
+
380
+ From SSE events `unhealthy`, `crashed`:
381
+ ```javascript
382
+ event_source.addEventListener('crashed', (e) => {
383
+ const data = JSON.parse(e.data);
384
+ log_viewer.append({
385
+ timestamp: data.timestamp,
386
+ level: 'error',
387
+ source: 'resource',
388
+ message: `Resource "${data.resourceName}" crashed (exit code: ${data.code})`
389
+ });
390
+ });
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Full Log View
396
+
397
+ When the user navigates to the "Logs" sidebar item, a dedicated full-page Log_Viewer is shown with:
398
+ - Larger output area (full content height)
399
+ - Search bar
400
+ - Export button
401
+ - Source filter (request, resource, build, all)
402
+
403
+ ```javascript
404
+ const full_log_viewer = new Log_Viewer({
405
+ context,
406
+ max_lines: 2000,
407
+ show_timestamps: true,
408
+ auto_scroll: true,
409
+ show_filter: true
410
+ });
411
+ ```
412
+
413
+ ### Search
414
+
415
+ The search function filters visible entries by text match:
416
+
417
+ ```javascript
418
+ _search(query) {
419
+ if (!this._output || !this._output.el) return;
420
+ const lines = this._output.el.querySelectorAll('.log-line');
421
+ const q = query.toLowerCase();
422
+ lines.forEach(line => {
423
+ const msg = line.querySelector('.log-message');
424
+ if (!msg) return;
425
+ const text = msg.innerText.toLowerCase();
426
+ line.style.display = text.includes(q) ? '' : 'none';
427
+ // Highlight match (simple approach)
428
+ if (q && text.includes(q)) {
429
+ line.style.borderBottom = '0.5px solid rgba(68, 136, 204, 0.3)';
430
+ } else {
431
+ line.style.borderBottom = '';
432
+ }
433
+ });
434
+ }
435
+ ```
436
+
437
+ ---
438
+
439
+ ## Log Entry Schema
440
+
441
+ All log entries follow a consistent internal schema:
442
+
443
+ ```typescript
444
+ interface Log_Entry {
445
+ timestamp: number; // Unix milliseconds
446
+ level: 'info' | 'warn' | 'error' | 'debug' | 'success';
447
+ source: 'request' | 'resource' | 'build' | 'system' | 'user';
448
+ message: string; // Human-readable message
449
+ metadata?: { // Optional structured data
450
+ method?: string;
451
+ url?: string;
452
+ status?: number;
453
+ duration_ms?: number;
454
+ resource_name?: string;
455
+ [key: string]: any;
456
+ };
457
+ }
458
+ ```
459
+
460
+ This schema ensures consistent rendering regardless of the entry source.
461
+
462
+ ---
463
+
464
+ ## Performance Considerations
465
+
466
+ - **Circular buffer**: Entries beyond `max_lines` are dropped from both the array and the DOM. Default: 500 lines for the dashboard widget, 2000 for the full log view.
467
+ - **DOM recycling**: When entries exceed the max, the first child is removed from the DOM before appending a new one. This prevents unbounded DOM growth.
468
+ - **Throttling**: When SSE events arrive rapidly (e.g., during load testing), the log viewer batches DOM updates with `requestAnimationFrame` to avoid layout thrashing.
469
+ - **Auto-scroll pause**: Auto-scrolling is paused when the user scrolls up, to allow reading historical entries without being yanked to the bottom.
470
+
471
+ ```javascript
472
+ // Throttled append for high-frequency updates
473
+ _throttled_append(entry) {
474
+ this._pending_entries = this._pending_entries || [];
475
+ this._pending_entries.push(entry);
476
+
477
+ if (!this._flush_scheduled) {
478
+ this._flush_scheduled = true;
479
+ requestAnimationFrame(() => {
480
+ this._pending_entries.forEach(e => this._render_entry(e));
481
+ this._pending_entries = [];
482
+ this._flush_scheduled = false;
483
+
484
+ if (this._auto_scroll && !this._paused && this._output.el) {
485
+ this._output.el.scrollTop = this._output.el.scrollHeight;
486
+ }
487
+ });
488
+ }
489
+ }
490
+ ```