jsgui3-server 0.0.149 → 0.0.151

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 (106) hide show
  1. package/.github/agents/Mobile Developer.agent.md +89 -0
  2. package/.github/instructions/copilot.instructions.md +1 -0
  3. package/AGENTS.md +6 -0
  4. package/README.md +185 -0
  5. package/admin-ui/client.js +73 -43
  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/docs/admin-extension-guide.md +345 -0
  15. package/docs/api-reference.md +383 -303
  16. package/docs/books/adaptive-control-improvements/01-control-candidate-matrix.md +122 -0
  17. package/docs/books/adaptive-control-improvements/02-tier-1-layout-playbooks.md +207 -0
  18. package/docs/books/adaptive-control-improvements/03-tier-2-navigation-form-overlay.md +140 -0
  19. package/docs/books/adaptive-control-improvements/04-cross-cutting-platform-functionality.md +141 -0
  20. package/docs/books/adaptive-control-improvements/05-styling-theming-density-upgrades.md +114 -0
  21. package/docs/books/adaptive-control-improvements/06-testing-quality-gates.md +97 -0
  22. package/docs/books/adaptive-control-improvements/07-delivery-roadmap-and-ownership.md +137 -0
  23. package/docs/books/adaptive-control-improvements/08-appendix-tier1-acceptance-and-pr-templates.md +261 -0
  24. package/docs/books/adaptive-control-improvements/README.md +66 -0
  25. package/docs/books/admin-ui-authentication/01-threat-model-and-goals.md +124 -0
  26. package/docs/books/admin-ui-authentication/02-session-model-and-token-model.md +75 -0
  27. package/docs/books/admin-ui-authentication/03-auth-middleware-patterns.md +77 -0
  28. package/docs/books/admin-ui-authentication/README.md +25 -0
  29. package/docs/books/creating-a-new-admin-ui/01-introduction-and-vision.md +130 -0
  30. package/docs/books/creating-a-new-admin-ui/02-architecture-and-data-flow.md +298 -0
  31. package/docs/books/creating-a-new-admin-ui/03-server-introspection.md +381 -0
  32. package/docs/books/creating-a-new-admin-ui/04-admin-module-adapter-layer.md +592 -0
  33. package/docs/books/creating-a-new-admin-ui/05-domain-controls-stat-cards-and-gauges.md +513 -0
  34. package/docs/books/creating-a-new-admin-ui/06-domain-controls-process-manager.md +544 -0
  35. package/docs/books/creating-a-new-admin-ui/07-domain-controls-resource-pool-inspector.md +493 -0
  36. package/docs/books/creating-a-new-admin-ui/08-domain-controls-route-table-and-api-explorer.md +586 -0
  37. package/docs/books/creating-a-new-admin-ui/09-domain-controls-log-viewer-and-activity-feed.md +490 -0
  38. package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md +526 -0
  39. package/docs/books/creating-a-new-admin-ui/11-domain-controls-configuration-panel.md +808 -0
  40. package/docs/books/creating-a-new-admin-ui/12-admin-shell-layout-sidebar-navigation.md +210 -0
  41. package/docs/books/creating-a-new-admin-ui/13-telemetry-integration.md +556 -0
  42. package/docs/books/creating-a-new-admin-ui/14-realtime-sse-observable-integration.md +485 -0
  43. package/docs/books/creating-a-new-admin-ui/15-styling-theming-aero-design-system.md +521 -0
  44. package/docs/books/creating-a-new-admin-ui/16-testing-and-quality-assurance.md +147 -0
  45. package/docs/books/creating-a-new-admin-ui/17-next-steps-process-resource-roadmap.md +356 -0
  46. package/docs/books/creating-a-new-admin-ui/README.md +68 -0
  47. package/docs/books/device-adaptive-composition/01-platform-feature-audit.md +177 -0
  48. package/docs/books/device-adaptive-composition/02-responsive-composition-model.md +187 -0
  49. package/docs/books/device-adaptive-composition/03-data-model-vs-view-model.md +231 -0
  50. package/docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md +234 -0
  51. package/docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md +193 -0
  52. package/docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md +346 -0
  53. package/docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md +265 -0
  54. package/docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md +250 -0
  55. package/docs/books/device-adaptive-composition/README.md +47 -0
  56. package/docs/comparison-report-express-plex-cpanel.md +549 -0
  57. package/docs/comprehensive-documentation.md +220 -220
  58. package/docs/configuration-reference.md +227 -204
  59. package/docs/designs/server-admin-interface-aero.svg +611 -0
  60. package/docs/middleware-guide.md +236 -0
  61. package/docs/system-architecture.md +24 -18
  62. package/docs/troubleshooting.md +84 -53
  63. package/middleware/compression.js +217 -0
  64. package/middleware/index.js +15 -0
  65. package/module.js +19 -11
  66. package/package.json +1 -1
  67. package/serve-factory.js +29 -0
  68. package/server.js +280 -20
  69. package/tests/README.md +5 -0
  70. package/tests/admin-ui-jsgui-controls.test.js +581 -0
  71. package/tests/test-runner.js +1 -0
  72. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +0 -40
  73. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +0 -39
  74. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +0 -39
  75. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +0 -39
  76. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +0 -39
  77. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +0 -40
  78. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +0 -39
  79. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +0 -40
  80. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +0 -40
  81. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +0 -39
  82. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +0 -39
  83. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +0 -44
  84. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +0 -45
  85. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +0 -39
  86. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +0 -39
  87. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +0 -42
  88. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +0 -40
  89. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +0 -43
  90. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +0 -40
  91. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +0 -40
  92. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +0 -40
  93. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +0 -39
  94. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +0 -39
  95. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +0 -39
  96. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +0 -39
  97. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +0 -39
  98. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +0 -41
  99. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +0 -44
  100. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +0 -40
  101. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +0 -40
  102. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +0 -39
  103. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +0 -39
  104. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +0 -39
  105. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +0 -39
  106. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +0 -39
@@ -0,0 +1,526 @@
1
+ # Chapter 10: Domain Controls — Build Status & Bundle Inspector
2
+
3
+ ## Overview
4
+
5
+ The Build Status panel surfaces information about the JavaScript and CSS bundles produced by jsgui3-server's ESBuild bundling pipeline. As documented in Chapter 3 (Server Introspection), the publisher system generates bundles and exposes some size/path data through the adapter layer.
6
+
7
+ In the design reference this appears as the "BUILD OUTPUT" group box containing file sizes and timing.
8
+
9
+ ---
10
+
11
+ ## Available Build Data
12
+
13
+ From Chapter 3, the following build information is available or capturable:
14
+
15
+ | Datum | Source | Available Now |
16
+ |-------|--------|:------------:|
17
+ | JS bundle path | `HTTP_Webpage_Publisher` `js_output_path` | ✅ |
18
+ | CSS bundle path | `HTTP_Webpage_Publisher` `css_output_path` | ✅ |
19
+ | JS file size (identity) | `fs.statSync` on output path | ✅ |
20
+ | CSS file size (identity) | `fs.statSync` on output path | ✅ |
21
+ | JS file size (gzipped) | `zlib.gzipSync` + measure | Adapter |
22
+ | CSS file size (gzipped) | `zlib.gzipSync` + measure | Adapter |
23
+ | Build timestamp | Captured on publisher `ready` | Adapter |
24
+ | Build duration | Captured via timing wrapper | Adapter |
25
+ | Module count | ESBuild `metafile.inputs` | Adapter |
26
+ | Source map status | Server config / publisher options | ✅ |
27
+ | Entry point | `src_path_client_js` | ✅ |
28
+ | Output directory | `build_path` or derived | ✅ |
29
+ | Compression ratios | Computed from identity/gzipped sizes | Adapter |
30
+
31
+ ---
32
+
33
+ ## Adapter Enhancement
34
+
35
+ The `Admin_Module_V1` adapter captures build data when the publisher fires its `ready` event. This was specified in Chapter 4 (`capture_bundle_info`):
36
+
37
+ ```javascript
38
+ // Inside Admin_Module_V1.init()
39
+ capture_bundle_info(server) {
40
+ const publishers = server.publishers || [];
41
+
42
+ publishers.forEach(pub => {
43
+ if (pub instanceof HTTP_Webpage_Publisher) {
44
+ const capture = () => {
45
+ const info = { publisher_name: pub.name || 'default', items: [] };
46
+
47
+ if (pub.js_output_path && fs.existsSync(pub.js_output_path)) {
48
+ const js_stat = fs.statSync(pub.js_output_path);
49
+ const js_buf = fs.readFileSync(pub.js_output_path);
50
+ const js_gzip = zlib.gzipSync(js_buf);
51
+
52
+ info.items.push({
53
+ type: 'js',
54
+ path: pub.js_output_path,
55
+ size_identity: js_stat.size,
56
+ size_gzip: js_gzip.length,
57
+ compression_ratio: (js_gzip.length / js_stat.size * 100).toFixed(1)
58
+ });
59
+ }
60
+
61
+ if (pub.css_output_path && fs.existsSync(pub.css_output_path)) {
62
+ const css_stat = fs.statSync(pub.css_output_path);
63
+ const css_buf = fs.readFileSync(pub.css_output_path);
64
+ const css_gzip = zlib.gzipSync(css_buf);
65
+
66
+ info.items.push({
67
+ type: 'css',
68
+ path: pub.css_output_path,
69
+ size_identity: css_stat.size,
70
+ size_gzip: css_gzip.length,
71
+ compression_ratio: (css_gzip.length / css_stat.size * 100).toFixed(1)
72
+ });
73
+ }
74
+
75
+ info.built_at = Date.now();
76
+ this._build_info = info;
77
+ };
78
+
79
+ if (pub._ready) {
80
+ capture();
81
+ }
82
+ pub.on('ready', capture);
83
+ }
84
+ });
85
+ }
86
+ ```
87
+
88
+ ### API Endpoint
89
+
90
+ `GET /api/admin/v1/build`
91
+
92
+ Response:
93
+ ```json
94
+ {
95
+ "publisher_name": "default",
96
+ "items": [
97
+ {
98
+ "type": "js",
99
+ "path": "/tmp/jsgui-build/bundle.js",
100
+ "size_identity": 254892,
101
+ "size_gzip": 68420,
102
+ "compression_ratio": "26.8"
103
+ },
104
+ {
105
+ "type": "css",
106
+ "path": "/tmp/jsgui-build/bundle.css",
107
+ "size_identity": 12480,
108
+ "size_gzip": 3120,
109
+ "compression_ratio": "25.0"
110
+ }
111
+ ],
112
+ "built_at": 1739567000000,
113
+ "entry_point": "/home/user/app/client.js",
114
+ "source_maps": true,
115
+ "build_path": "/tmp/jsgui-build"
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Build_Status Control
122
+
123
+ ### Spec
124
+
125
+ ```javascript
126
+ {
127
+ __type_name: 'build_status',
128
+ title: 'Build Output'
129
+ }
130
+ ```
131
+
132
+ ### Visual Anatomy
133
+
134
+ ```
135
+ ┌─ BUILD OUTPUT ─────────────────────────────────────────┐
136
+ │ │
137
+ │ JavaScript Bundle │
138
+ │ ━━━━━━━━━━━━━━━━━━━━━━████████ 254 KB → 68 KB (27%) │
139
+ │ │
140
+ │ CSS Bundle │
141
+ │ ━━━━━█████████████████████████ 12 KB → 3 KB (25%) │
142
+ │ │
143
+ │ ┌──────────────────────────────────────────────┐ │
144
+ │ │ Entry: client.js │ │
145
+ │ │ Output: /tmp/jsgui-build/ │ │
146
+ │ │ Maps: Enabled ✓ │ │
147
+ │ │ Built: 2 minutes ago │ │
148
+ │ └──────────────────────────────────────────────┘ │
149
+ │ │
150
+ └────────────────────────────────────────────────────────┘
151
+ ```
152
+
153
+ ### Constructor
154
+
155
+ ```javascript
156
+ class Build_Status extends jsgui.Control {
157
+ constructor(spec = {}) {
158
+ spec.__type_name = spec.__type_name || 'build_status';
159
+ super(spec);
160
+ const { context } = this;
161
+
162
+ const compose = () => {
163
+ const group = new Group_Box({ context, title: spec.title || 'Build Output' });
164
+ this.add(group);
165
+ this._group = group;
166
+
167
+ // Bundle bars container
168
+ const bars_container = new controls.div({ context, class: 'build-bars' });
169
+ group.add(bars_container);
170
+ this._bars_container = bars_container;
171
+
172
+ // Details summary
173
+ const details = new controls.div({ context, class: 'build-details' });
174
+ group.add(details);
175
+ this._details = details;
176
+
177
+ // Entry point row
178
+ const entry_row = this._make_detail_row('Entry', '—');
179
+ details.add(entry_row);
180
+ this._entry_label = entry_row._value;
181
+
182
+ // Output directory row
183
+ const output_row = this._make_detail_row('Output', '—');
184
+ details.add(output_row);
185
+ this._output_label = output_row._value;
186
+
187
+ // Source maps row
188
+ const maps_row = this._make_detail_row('Maps', '—');
189
+ details.add(maps_row);
190
+ this._maps_label = maps_row._value;
191
+
192
+ // Built timestamp row
193
+ const built_row = this._make_detail_row('Built', '—');
194
+ details.add(built_row);
195
+ this._built_label = built_row._value;
196
+ };
197
+
198
+ if (!spec.el) { compose(); }
199
+ }
200
+
201
+ _make_detail_row(label, value) {
202
+ const { context } = this;
203
+ const row = new controls.div({ context, class: 'build-detail-row' });
204
+ const lbl = new controls.span({ context, class: 'build-detail-label' });
205
+ lbl.add(label + ':');
206
+ const val = new controls.span({ context, class: 'build-detail-value' });
207
+ val.add(value);
208
+ row.add(lbl);
209
+ row.add(val);
210
+ row._value = val;
211
+ return row;
212
+ }
213
+
214
+ update(build_data) {
215
+ if (!build_data) return;
216
+
217
+ // Clear existing bars
218
+ if (this._bars_container && this._bars_container.el) {
219
+ this._bars_container.el.innerHTML = '';
220
+ }
221
+
222
+ // Render bundle bars
223
+ if (build_data.items) {
224
+ build_data.items.forEach(item => {
225
+ this._render_bundle_bar(item);
226
+ });
227
+ }
228
+
229
+ // Update details
230
+ if (build_data.entry_point && this._entry_label) {
231
+ const basename = build_data.entry_point.split(/[/\\]/).pop();
232
+ this._entry_label.el.innerText = basename;
233
+ }
234
+ if (build_data.build_path && this._output_label) {
235
+ this._output_label.el.innerText = build_data.build_path;
236
+ }
237
+ if (this._maps_label) {
238
+ this._maps_label.el.innerText = build_data.source_maps ? 'Enabled ✓' : 'Disabled';
239
+ }
240
+ if (build_data.built_at && this._built_label) {
241
+ this._built_label.el.innerText = format_relative_time(build_data.built_at);
242
+ }
243
+ }
244
+
245
+ _render_bundle_bar(item) {
246
+ const { context } = this;
247
+ const bar_group = new controls.div({ context, class: `build-bar-group build-bar-${item.type}` });
248
+
249
+ // Label
250
+ const label = new controls.div({ context, class: 'build-bar-label' });
251
+ const type_name = item.type === 'js' ? 'JavaScript Bundle' : 'CSS Bundle';
252
+ label.add(type_name);
253
+ bar_group.add(label);
254
+
255
+ // Bar + sizes
256
+ const bar_row = new controls.div({ context, class: 'build-bar-row' });
257
+ bar_group.add(bar_row);
258
+
259
+ // Visual bar
260
+ const bar_track = new controls.div({ context, class: 'build-bar-track' });
261
+ const bar_fill = new controls.div({ context, class: 'build-bar-fill' });
262
+ bar_track.add(bar_fill);
263
+ bar_row.add(bar_track);
264
+
265
+ // Set fill width proportional to compression ratio
266
+ if (item.compression_ratio) {
267
+ bar_fill.dom.attributes.style = `width: ${item.compression_ratio}%`;
268
+ }
269
+
270
+ // Sizes text
271
+ const sizes = new controls.span({ context, class: 'build-bar-sizes' });
272
+ sizes.add(
273
+ `${format_bytes(item.size_identity)} → ${format_bytes(item.size_gzip)} (${item.compression_ratio}%)`
274
+ );
275
+ bar_row.add(sizes);
276
+
277
+ if (this._bars_container.el) {
278
+ // Client-side: direct DOM
279
+ const temp = document.createElement('div');
280
+ temp.innerHTML = bar_group.html();
281
+ while (temp.firstChild) {
282
+ this._bars_container.el.appendChild(temp.firstChild);
283
+ }
284
+ } else {
285
+ this._bars_container.add(bar_group);
286
+ }
287
+ }
288
+
289
+ activate() {
290
+ if (!this.__active) {
291
+ super.activate();
292
+ // Fetch initial build data
293
+ this._fetch_build_data();
294
+ }
295
+ }
296
+
297
+ async _fetch_build_data() {
298
+ try {
299
+ const res = await fetch('/api/admin/v1/build');
300
+ if (res.ok) {
301
+ const data = await res.json();
302
+ this.update(data);
303
+ }
304
+ } catch (err) {
305
+ console.warn('Build status fetch failed:', err);
306
+ }
307
+ }
308
+ }
309
+ ```
310
+
311
+ ### CSS
312
+
313
+ ```css
314
+ .build_status {
315
+ min-width: 280px;
316
+ }
317
+
318
+ .build-bars {
319
+ padding: 8px 12px;
320
+ }
321
+
322
+ .build-bar-group {
323
+ margin-bottom: 12px;
324
+ }
325
+
326
+ .build-bar-label {
327
+ font-size: 8.5px;
328
+ font-weight: 600;
329
+ color: #2A4060;
330
+ margin-bottom: 4px;
331
+ text-transform: uppercase;
332
+ letter-spacing: 0.3px;
333
+ }
334
+
335
+ .build-bar-row {
336
+ display: flex;
337
+ align-items: center;
338
+ gap: 8px;
339
+ }
340
+
341
+ .build-bar-track {
342
+ flex: 1;
343
+ height: 8px;
344
+ background: #E8E4DC;
345
+ border-radius: 4px;
346
+ overflow: hidden;
347
+ }
348
+
349
+ .build-bar-fill {
350
+ height: 100%;
351
+ border-radius: 4px;
352
+ transition: width 0.3s ease;
353
+ }
354
+
355
+ .build-bar-js .build-bar-fill {
356
+ background: linear-gradient(90deg, #4488CC, #66AAEE);
357
+ }
358
+
359
+ .build-bar-css .build-bar-fill {
360
+ background: linear-gradient(90deg, #66AA66, #88CC88);
361
+ }
362
+
363
+ .build-bar-sizes {
364
+ font-family: 'Consolas', 'Courier New', monospace;
365
+ font-size: 8px;
366
+ color: #666;
367
+ white-space: nowrap;
368
+ }
369
+
370
+ /* Details section */
371
+ .build-details {
372
+ border-top: 1px solid #E0DCD4;
373
+ padding: 8px 12px;
374
+ margin-top: 4px;
375
+ }
376
+
377
+ .build-detail-row {
378
+ display: flex;
379
+ padding: 2px 0;
380
+ font-size: 8.5px;
381
+ }
382
+
383
+ .build-detail-label {
384
+ min-width: 60px;
385
+ color: #808080;
386
+ font-weight: 500;
387
+ }
388
+
389
+ .build-detail-value {
390
+ color: #2A4060;
391
+ font-family: 'Consolas', 'Courier New', monospace;
392
+ font-size: 8px;
393
+ }
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Bundle Size Visualization
399
+
400
+ The compression bar communicates two things at a glance:
401
+
402
+ 1. **Bar fill width** = the gzipped size as a percentage of the original (the compression ratio)
403
+ 2. **Text** = the exact sizes and percentage
404
+
405
+ A heavily compressed file (e.g., 250 KB → 65 KB = 26%) shows a narrow fill bar, indicating efficient compression. A less compressible file shows a wider bar.
406
+
407
+ **Color coding:**
408
+ - JavaScript: blue gradient (`#4488CC` → `#66AAEE`) — matches the server information theme
409
+ - CSS: green gradient (`#66AA66` → `#88CC88`) — matches the style/rendering theme
410
+
411
+ ---
412
+
413
+ ## Bundle Detail Expansion (Phase 2)
414
+
415
+ In Phase 2, clicking a bundle bar expands to show:
416
+ - Module list (from ESBuild metafile)
417
+ - Individual module sizes
418
+ - Import tree visualization
419
+ - Largest modules sorted by size
420
+
421
+ This requires the adapter to capture ESBuild's `metafile` option:
422
+
423
+ ```javascript
424
+ // Enhanced adapter: capture metafile during build
425
+ async _build_with_metafile(entry_point, output_path) {
426
+ const result = await esbuild.build({
427
+ entryPoints: [entry_point],
428
+ outfile: output_path,
429
+ bundle: true,
430
+ metafile: true // Request module information
431
+ // ... other options
432
+ });
433
+
434
+ this._metafile = result.metafile;
435
+ return result;
436
+ }
437
+ ```
438
+
439
+ ### Module List Response (Phase 2)
440
+
441
+ `GET /api/admin/v1/build/modules`
442
+
443
+ ```json
444
+ {
445
+ "total_modules": 47,
446
+ "modules": [
447
+ {
448
+ "path": "node_modules/jsgui3-html/controls/div.js",
449
+ "size": 12480,
450
+ "percentage": 4.9
451
+ },
452
+ {
453
+ "path": "client/dashboard.js",
454
+ "size": 8200,
455
+ "percentage": 3.2
456
+ }
457
+ ],
458
+ "largest": [
459
+ { "path": "node_modules/jsgui3-html/core/control.js", "size": 45200 }
460
+ ]
461
+ }
462
+ ```
463
+
464
+ ---
465
+
466
+ ## Rebuild Trigger (Phase 2)
467
+
468
+ A "Rebuild" button in the Build Status panel triggers a manual rebundle:
469
+
470
+ `POST /api/admin/v1/build/rebuild`
471
+
472
+ ```json
473
+ {
474
+ "status": "ok",
475
+ "duration_ms": 1240,
476
+ "items": [
477
+ { "type": "js", "size_identity": 254892, "size_gzip": 68420 },
478
+ { "type": "css", "size_identity": 12480, "size_gzip": 3120 }
479
+ ]
480
+ }
481
+ ```
482
+
483
+ This uses the existing publisher's rebuild mechanism if available, or invokes esbuild directly with the same configuration.
484
+
485
+ ---
486
+
487
+ ## Integration with Dashboard
488
+
489
+ On the dashboard overview, the Build Status control is placed in the lower-right quadrant alongside the Route Table. It provides an at-a-glance view of the current bundle state.
490
+
491
+ ```javascript
492
+ // In Admin_Dashboard compose()
493
+ const build_status = new Build_Status({ context, title: 'Build Output' });
494
+ content_grid.add(build_status);
495
+ ```
496
+
497
+ When the SSE channel delivers a `build_complete` event, the dashboard's Build_Status control auto-refreshes:
498
+
499
+ ```javascript
500
+ // In Admin_Dashboard activate()
501
+ event_source.addEventListener('build_complete', (e) => {
502
+ const data = JSON.parse(e.data);
503
+ build_status.update(data);
504
+ });
505
+ ```
506
+
507
+ ---
508
+
509
+ ## Utility Functions
510
+
511
+ ```javascript
512
+ function format_bytes(bytes) {
513
+ if (bytes === 0) return '0 B';
514
+ const units = ['B', 'KB', 'MB', 'GB'];
515
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
516
+ return (bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0) + ' ' + units[i];
517
+ }
518
+
519
+ function format_relative_time(timestamp) {
520
+ const diff = Date.now() - timestamp;
521
+ if (diff < 60000) return 'just now';
522
+ if (diff < 3600000) return Math.floor(diff / 60000) + ' minutes ago';
523
+ if (diff < 86400000) return Math.floor(diff / 3600000) + ' hours ago';
524
+ return new Date(timestamp).toLocaleString();
525
+ }
526
+ ```