jsgui3-server 0.0.149 → 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 (98) hide show
  1. package/.github/agents/Mobile Developer.agent.md +89 -0
  2. package/AGENTS.md +4 -0
  3. package/README.md +130 -0
  4. package/admin-ui/client.js +73 -43
  5. package/admin-ui/v1/admin_auth_service.js +197 -0
  6. package/admin-ui/v1/admin_user_store.js +71 -0
  7. package/admin-ui/v1/client.js +17 -0
  8. package/admin-ui/v1/controls/admin_shell.js +1399 -0
  9. package/admin-ui/v1/controls/group_box.js +84 -0
  10. package/admin-ui/v1/controls/stat_card.js +125 -0
  11. package/admin-ui/v1/server.js +658 -0
  12. package/admin-ui/v1/utils/formatters.js +68 -0
  13. package/docs/admin-extension-guide.md +345 -0
  14. package/docs/books/adaptive-control-improvements/01-control-candidate-matrix.md +122 -0
  15. package/docs/books/adaptive-control-improvements/02-tier-1-layout-playbooks.md +207 -0
  16. package/docs/books/adaptive-control-improvements/03-tier-2-navigation-form-overlay.md +140 -0
  17. package/docs/books/adaptive-control-improvements/04-cross-cutting-platform-functionality.md +141 -0
  18. package/docs/books/adaptive-control-improvements/05-styling-theming-density-upgrades.md +114 -0
  19. package/docs/books/adaptive-control-improvements/06-testing-quality-gates.md +97 -0
  20. package/docs/books/adaptive-control-improvements/07-delivery-roadmap-and-ownership.md +137 -0
  21. package/docs/books/adaptive-control-improvements/08-appendix-tier1-acceptance-and-pr-templates.md +261 -0
  22. package/docs/books/adaptive-control-improvements/README.md +66 -0
  23. package/docs/books/admin-ui-authentication/01-threat-model-and-goals.md +124 -0
  24. package/docs/books/admin-ui-authentication/02-session-model-and-token-model.md +75 -0
  25. package/docs/books/admin-ui-authentication/03-auth-middleware-patterns.md +77 -0
  26. package/docs/books/admin-ui-authentication/README.md +25 -0
  27. package/docs/books/creating-a-new-admin-ui/01-introduction-and-vision.md +130 -0
  28. package/docs/books/creating-a-new-admin-ui/02-architecture-and-data-flow.md +298 -0
  29. package/docs/books/creating-a-new-admin-ui/03-server-introspection.md +381 -0
  30. package/docs/books/creating-a-new-admin-ui/04-admin-module-adapter-layer.md +592 -0
  31. package/docs/books/creating-a-new-admin-ui/05-domain-controls-stat-cards-and-gauges.md +513 -0
  32. package/docs/books/creating-a-new-admin-ui/06-domain-controls-process-manager.md +544 -0
  33. package/docs/books/creating-a-new-admin-ui/07-domain-controls-resource-pool-inspector.md +493 -0
  34. package/docs/books/creating-a-new-admin-ui/08-domain-controls-route-table-and-api-explorer.md +586 -0
  35. package/docs/books/creating-a-new-admin-ui/09-domain-controls-log-viewer-and-activity-feed.md +490 -0
  36. package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md +526 -0
  37. package/docs/books/creating-a-new-admin-ui/11-domain-controls-configuration-panel.md +808 -0
  38. package/docs/books/creating-a-new-admin-ui/12-admin-shell-layout-sidebar-navigation.md +210 -0
  39. package/docs/books/creating-a-new-admin-ui/13-telemetry-integration.md +556 -0
  40. package/docs/books/creating-a-new-admin-ui/14-realtime-sse-observable-integration.md +485 -0
  41. package/docs/books/creating-a-new-admin-ui/15-styling-theming-aero-design-system.md +521 -0
  42. package/docs/books/creating-a-new-admin-ui/16-testing-and-quality-assurance.md +147 -0
  43. package/docs/books/creating-a-new-admin-ui/17-next-steps-process-resource-roadmap.md +356 -0
  44. package/docs/books/creating-a-new-admin-ui/README.md +68 -0
  45. package/docs/books/device-adaptive-composition/01-platform-feature-audit.md +177 -0
  46. package/docs/books/device-adaptive-composition/02-responsive-composition-model.md +187 -0
  47. package/docs/books/device-adaptive-composition/03-data-model-vs-view-model.md +231 -0
  48. package/docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md +234 -0
  49. package/docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md +193 -0
  50. package/docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md +346 -0
  51. package/docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md +265 -0
  52. package/docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md +250 -0
  53. package/docs/books/device-adaptive-composition/README.md +47 -0
  54. package/docs/comparison-report-express-plex-cpanel.md +549 -0
  55. package/docs/designs/server-admin-interface-aero.svg +611 -0
  56. package/docs/troubleshooting.md +84 -53
  57. package/module.js +16 -11
  58. package/package.json +1 -1
  59. package/serve-factory.js +1 -0
  60. package/server.js +199 -0
  61. package/tests/README.md +5 -0
  62. package/tests/admin-ui-jsgui-controls.test.js +581 -0
  63. package/tests/test-runner.js +1 -0
  64. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +0 -40
  65. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +0 -39
  66. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +0 -39
  67. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +0 -39
  68. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +0 -39
  69. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +0 -40
  70. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +0 -39
  71. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +0 -40
  72. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +0 -40
  73. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +0 -39
  74. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +0 -39
  75. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +0 -44
  76. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +0 -45
  77. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +0 -39
  78. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +0 -39
  79. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +0 -42
  80. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +0 -40
  81. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +0 -43
  82. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +0 -40
  83. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +0 -40
  84. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +0 -40
  85. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +0 -39
  86. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +0 -39
  87. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +0 -39
  88. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +0 -39
  89. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +0 -39
  90. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +0 -41
  91. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +0 -44
  92. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +0 -40
  93. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +0 -40
  94. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +0 -39
  95. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +0 -39
  96. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +0 -39
  97. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +0 -39
  98. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +0 -39
@@ -0,0 +1,586 @@
1
+ # Chapter 8: Domain Controls — Route Table & API Explorer
2
+
3
+ ## Overview
4
+
5
+ The Route Table displays every HTTP route registered in the server's router. Each row shows the HTTP method (as a colored badge), the path, the handler description, and the route type category. The design reference shows this within a tabbed panel ("Routes & API" tab), alongside a build status sidebar.
6
+
7
+ An API Explorer extension allows developers to test function-published endpoints directly from the admin UI.
8
+
9
+ ---
10
+
11
+ ## Route_Table Control
12
+
13
+ ### Spec
14
+
15
+ ```javascript
16
+ {
17
+ __type_name: 'route_table',
18
+ routes: [ // Data from /api/admin/routes
19
+ {
20
+ path: '/',
21
+ method: 'GET',
22
+ handler_type: 'Static_Route_HTTP_Responder',
23
+ handler_name: null,
24
+ category: 'auto',
25
+ description: 'renderControl → HTML'
26
+ }
27
+ ]
28
+ }
29
+ ```
30
+
31
+ ### Visual Anatomy
32
+
33
+ ```
34
+ METHOD PATH HANDLER TYPE
35
+ ──────────────────────────────────────────────────────────
36
+ [GET ] / renderControl → HTML [auto]
37
+ [GET ] /js/js.js Client JS bundle [auto]
38
+ [GET ] /css/css.css Extracted CSS bundle [auto]
39
+ [POST] /api/validateUser server.publish() [API ]
40
+ [POST] /api/register server.publish() [API ]
41
+ [GET ] /static/* Static file serving [static]
42
+ ──────────────────────────────────────────────────────────
43
+ + 1 additional static route
44
+ ```
45
+
46
+ ### Constructor
47
+
48
+ ```javascript
49
+ class Route_Table extends jsgui.Control {
50
+ constructor(spec = {}) {
51
+ spec.__type_name = spec.__type_name || 'route_table';
52
+ super(spec);
53
+ const { context } = this;
54
+ this._routes = spec.routes || [];
55
+
56
+ const compose = () => {
57
+ // Table header
58
+ const header = new controls.div({ context, class: 'route-table-header' });
59
+ this.add(header);
60
+
61
+ ['METHOD', 'PATH', 'HANDLER', 'TYPE'].forEach(col => {
62
+ const cell = new controls.span({ context, class: 'route-header-cell' });
63
+ cell.add(col);
64
+ header.add(cell);
65
+ });
66
+
67
+ // Table body
68
+ const body = new controls.div({ context, class: 'route-table-body' });
69
+ this.add(body);
70
+ this._body = body;
71
+
72
+ this._routes.forEach(route => {
73
+ const row = this._create_route_row(route);
74
+ body.add(row);
75
+ });
76
+
77
+ // Footer
78
+ const admin_count = this._routes.filter(r => r.category === 'admin').length;
79
+ if (admin_count > 0) {
80
+ const footer = new controls.div({ context, class: 'route-table-footer' });
81
+ footer.add(
82
+ `+ ${admin_count} admin route(s) · Router supports :param and error/not-found events`
83
+ );
84
+ this.add(footer);
85
+ }
86
+ };
87
+
88
+ if (!spec.el) { compose(); }
89
+ }
90
+
91
+ _create_route_row(route) {
92
+ const { context } = this;
93
+ const row = new controls.div({ context, class: 'route-row' });
94
+
95
+ // Method badge
96
+ const method_badge = new Method_Badge({
97
+ context,
98
+ method: this._infer_method(route)
99
+ });
100
+ row.add(method_badge);
101
+
102
+ // Path
103
+ const path_cell = new controls.span({ context, class: 'route-path' });
104
+ path_cell.add(route.path);
105
+ row.add(path_cell);
106
+
107
+ // Handler
108
+ const handler_cell = new controls.span({ context, class: 'route-handler' });
109
+ handler_cell.add(this._describe_handler(route));
110
+ row.add(handler_cell);
111
+
112
+ // Type badge
113
+ const type_badge = new controls.span({
114
+ context,
115
+ class: `route-type-badge type-${route.category}`
116
+ });
117
+ type_badge.add(route.category);
118
+ row.add(type_badge);
119
+
120
+ return row;
121
+ }
122
+
123
+ _infer_method(route) {
124
+ // API routes (function publishers) are typically POST
125
+ if (route.category === 'api') return 'POST';
126
+ // Observable and SSE routes are GET (with SSE accept)
127
+ if (route.category === 'observable' || route.category === 'sse') return 'GET';
128
+ // Everything else defaults to GET
129
+ return route.method || 'GET';
130
+ }
131
+
132
+ _describe_handler(route) {
133
+ switch (route.handler_type) {
134
+ case 'Static_Route_HTTP_Responder':
135
+ if (route.path.endsWith('.js')) return 'Client JS bundle';
136
+ if (route.path.endsWith('.css')) return 'Extracted CSS bundle';
137
+ if (route.path === '/') return 'renderControl → HTML';
138
+ return 'Static file serving';
139
+ case 'HTTP_Function_Publisher':
140
+ return 'server.publish()';
141
+ case 'Observable_Publisher':
142
+ return 'Observable → SSE stream';
143
+ case 'HTTP_SSE_Publisher':
144
+ return 'Event stream';
145
+ case 'HTTP_Website_Publisher':
146
+ return 'Website publisher';
147
+ default:
148
+ return route.handler_name || route.handler_type || 'Handler';
149
+ }
150
+ }
151
+
152
+ update(routes_data) {
153
+ this._routes = routes_data;
154
+ if (this._body && this._body.el) {
155
+ this._body.el.innerHTML = '';
156
+ routes_data.forEach(route => {
157
+ const row = this._create_route_row(route);
158
+ this._body.add(row);
159
+ });
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Method_Badge Control
168
+
169
+ A small colored badge displaying the HTTP method name.
170
+
171
+ ### Spec
172
+
173
+ ```javascript
174
+ {
175
+ __type_name: 'method_badge',
176
+ method: 'GET' // 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'
177
+ }
178
+ ```
179
+
180
+ ### Constructor
181
+
182
+ ```javascript
183
+ class Method_Badge extends jsgui.Control {
184
+ constructor(spec = {}) {
185
+ spec.__type_name = spec.__type_name || 'method_badge';
186
+ super(spec);
187
+ const { context } = this;
188
+ const method = (spec.method || 'GET').toUpperCase();
189
+
190
+ const compose = () => {
191
+ this.add(method);
192
+ };
193
+
194
+ if (!spec.el) { compose(); }
195
+
196
+ // Set class for color
197
+ if (typeof this.add_class === 'function') {
198
+ this.add_class(`method-${method.toLowerCase()}`);
199
+ }
200
+ }
201
+ }
202
+
203
+ Method_Badge.css = `
204
+ .method_badge {
205
+ display: inline-block;
206
+ font-size: 7px;
207
+ font-weight: 600;
208
+ padding: 2px 6px;
209
+ border-radius: 2px;
210
+ text-align: center;
211
+ min-width: 30px;
212
+ }
213
+
214
+ .method-get {
215
+ background: rgba(72, 184, 72, 0.15);
216
+ border: 0.5px solid #48B848;
217
+ color: #2A6A2A;
218
+ }
219
+
220
+ .method-post {
221
+ background: rgba(144, 112, 192, 0.15);
222
+ border: 0.5px solid #9070C0;
223
+ color: #5A3890;
224
+ }
225
+
226
+ .method-put {
227
+ background: rgba(68, 136, 204, 0.15);
228
+ border: 0.5px solid #4488CC;
229
+ color: #2A5A8A;
230
+ }
231
+
232
+ .method-delete {
233
+ background: rgba(204, 68, 68, 0.15);
234
+ border: 0.5px solid #CC4444;
235
+ color: #992222;
236
+ }
237
+
238
+ .method-patch {
239
+ background: rgba(216, 160, 32, 0.15);
240
+ border: 0.5px solid #D8A020;
241
+ color: #8A6A10;
242
+ }
243
+ `;
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Route Type Badges
249
+
250
+ ```css
251
+ .route-type-badge {
252
+ display: inline-block;
253
+ font-size: 7px;
254
+ padding: 2px 6px;
255
+ border-radius: 2px;
256
+ text-align: center;
257
+ }
258
+
259
+ .type-auto {
260
+ background: rgba(68, 136, 204, 0.12);
261
+ border: 0.5px solid #4488CC;
262
+ color: #3870A0;
263
+ }
264
+
265
+ .type-api {
266
+ background: rgba(144, 112, 192, 0.12);
267
+ border: 0.5px solid #9070C0;
268
+ color: #5A3890;
269
+ }
270
+
271
+ .type-static {
272
+ background: rgba(160, 140, 100, 0.12);
273
+ border: 0.5px solid #A08060;
274
+ color: #806040;
275
+ }
276
+
277
+ .type-observable {
278
+ background: rgba(72, 184, 72, 0.12);
279
+ border: 0.5px solid #48B848;
280
+ color: #2A6A2A;
281
+ }
282
+
283
+ .type-sse {
284
+ background: rgba(64, 152, 184, 0.12);
285
+ border: 0.5px solid #4098B8;
286
+ color: #205868;
287
+ }
288
+
289
+ .type-admin {
290
+ background: rgba(128, 128, 128, 0.12);
291
+ border: 0.5px solid #808080;
292
+ color: #606060;
293
+ }
294
+
295
+ .type-website {
296
+ background: rgba(68, 136, 204, 0.12);
297
+ border: 0.5px solid #4488CC;
298
+ color: #3870A0;
299
+ }
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Route Table CSS
305
+
306
+ ```css
307
+ .route-table-header {
308
+ display: grid;
309
+ grid-template-columns: 60px 2fr 2fr 60px;
310
+ padding: 6px 10px;
311
+ background: #E8E4DC;
312
+ border-top: 0.5px solid #C0B8A8;
313
+ border-bottom: 0.5px solid #C0B8A8;
314
+ gap: 8px;
315
+ }
316
+
317
+ .route-header-cell {
318
+ font-size: 8px;
319
+ font-weight: 600;
320
+ color: #606060;
321
+ }
322
+
323
+ .route-table-body {
324
+ display: flex;
325
+ flex-direction: column;
326
+ }
327
+
328
+ .route-row {
329
+ display: grid;
330
+ grid-template-columns: 60px 2fr 2fr 60px;
331
+ align-items: center;
332
+ gap: 8px;
333
+ padding: 4px 10px;
334
+ border-bottom: 0.5px solid #E8E4DC;
335
+ }
336
+
337
+ .route-row:hover {
338
+ background: rgba(68, 136, 204, 0.05);
339
+ }
340
+
341
+ .route-path {
342
+ font-size: 9px;
343
+ color: #2A4060;
344
+ font-family: 'Consolas', monospace;
345
+ }
346
+
347
+ .route-handler {
348
+ font-size: 9px;
349
+ color: #808080;
350
+ }
351
+
352
+ .route-table-footer {
353
+ font-size: 8px;
354
+ color: #908888;
355
+ padding: 8px 4px 0;
356
+ }
357
+ ```
358
+
359
+ ---
360
+
361
+ ## API Explorer (Phase 2)
362
+
363
+ The API Explorer extends the Route_Table with interactive testing for function-published endpoints.
364
+
365
+ ### Concept
366
+
367
+ When a user clicks on an API route row, an expansion panel appears below with:
368
+ 1. A request body editor (textarea or JSON input)
369
+ 2. A "Send" button
370
+ 3. A response viewer showing status and response body
371
+
372
+ ### API_Test_Panel Control
373
+
374
+ ```javascript
375
+ class API_Test_Panel extends jsgui.Control {
376
+ constructor(spec = {}) {
377
+ spec.__type_name = spec.__type_name || 'api_test_panel';
378
+ super(spec);
379
+ const { context } = this;
380
+
381
+ const compose = () => {
382
+ // Request section
383
+ const req_section = new controls.div({ context, class: 'api-test-request' });
384
+ this.add(req_section);
385
+
386
+ const req_label = new controls.div({ context, class: 'api-test-label' });
387
+ req_label.add('Request Body (JSON):');
388
+ req_section.add(req_label);
389
+
390
+ // Using a textarea for input
391
+ const req_input = new controls.textarea({
392
+ context,
393
+ class: 'api-test-input'
394
+ });
395
+ req_input.dom.attributes.rows = '4';
396
+ req_input.dom.attributes.placeholder = '{ "key": "value" }';
397
+ req_section.add(req_input);
398
+ this._input = req_input;
399
+
400
+ // Send button
401
+ const send_btn = new controls.Button({
402
+ context,
403
+ text: 'Send Request',
404
+ class: 'api-test-send'
405
+ });
406
+ req_section.add(send_btn);
407
+ this._send_btn = send_btn;
408
+
409
+ // Response section
410
+ const res_section = new controls.div({ context, class: 'api-test-response' });
411
+ this.add(res_section);
412
+
413
+ const res_label = new controls.div({ context, class: 'api-test-label' });
414
+ res_label.add('Response:');
415
+ res_section.add(res_label);
416
+
417
+ const res_output = new controls.div({ context, class: 'api-test-output' });
418
+ res_output.add('—');
419
+ res_section.add(res_output);
420
+ this._output = res_output;
421
+ };
422
+
423
+ if (!spec.el) { compose(); }
424
+ }
425
+
426
+ activate() {
427
+ if (!this.__active) {
428
+ super.activate();
429
+ const route = this.spec.route;
430
+ const method = this.spec.method || 'POST';
431
+
432
+ this._send_btn.el.addEventListener('click', async () => {
433
+ try {
434
+ const body = this._input.el.value;
435
+ const response = await fetch(route, {
436
+ method: method,
437
+ headers: { 'Content-Type': 'application/json' },
438
+ body: body || undefined
439
+ });
440
+ const text = await response.text();
441
+ let display = `Status: ${response.status}\n\n`;
442
+ try {
443
+ display += JSON.stringify(JSON.parse(text), null, 2);
444
+ } catch {
445
+ display += text;
446
+ }
447
+ this._output.el.innerText = display;
448
+ this._output.el.className = response.ok
449
+ ? 'api-test-output response-ok'
450
+ : 'api-test-output response-error';
451
+ } catch (err) {
452
+ this._output.el.innerText = `Error: ${err.message}`;
453
+ this._output.el.className = 'api-test-output response-error';
454
+ }
455
+ });
456
+ }
457
+ }
458
+ }
459
+
460
+ API_Test_Panel.css = `
461
+ .api_test_panel {
462
+ background: #F8F6F2;
463
+ border: 1px solid #D0C8B8;
464
+ border-radius: 3px;
465
+ padding: 12px;
466
+ margin: 6px 0;
467
+ }
468
+
469
+ .api-test-label {
470
+ font-size: 9px;
471
+ font-weight: 500;
472
+ color: #606060;
473
+ margin-bottom: 4px;
474
+ }
475
+
476
+ .api-test-input {
477
+ width: 100%;
478
+ font-family: 'Consolas', monospace;
479
+ font-size: 10px;
480
+ padding: 6px;
481
+ border: 1px solid #ACA899;
482
+ border-radius: 2px;
483
+ background: linear-gradient(to bottom, #E8E4DD 0%, #FFFFFF 3%, #FFFFFF 100%);
484
+ resize: vertical;
485
+ }
486
+
487
+ .api-test-send {
488
+ margin-top: 8px;
489
+ padding: 4px 16px;
490
+ font-size: 9px;
491
+ }
492
+
493
+ .api-test-response {
494
+ margin-top: 12px;
495
+ }
496
+
497
+ .api-test-output {
498
+ font-family: 'Consolas', monospace;
499
+ font-size: 9px;
500
+ padding: 8px;
501
+ border: 1px solid #D0C8B8;
502
+ border-radius: 2px;
503
+ background: #FFFFFF;
504
+ white-space: pre-wrap;
505
+ max-height: 200px;
506
+ overflow-y: auto;
507
+ color: #2A4060;
508
+ }
509
+
510
+ .response-ok { border-left: 3px solid #48B848; }
511
+ .response-error { border-left: 3px solid #CC4444; }
512
+ `;
513
+ ```
514
+
515
+ ---
516
+
517
+ ## Adapter — `get_routes_list()`
518
+
519
+ The Admin Module must track routes as they are registered. Since the Router doesn't natively expose a route list, we intercept `set_route`:
520
+
521
+ ```javascript
522
+ get_routes_list() {
523
+ // Filter out admin routes for cleaner display (optional)
524
+ const user_routes = this._route_registry.filter(
525
+ r => r.category !== 'admin'
526
+ );
527
+
528
+ return {
529
+ count: user_routes.length,
530
+ routes: user_routes.map(r => ({
531
+ path: r.path,
532
+ method: this._infer_method_for_route(r),
533
+ handler_type: r.handler_type,
534
+ handler_name: r.handler_name,
535
+ category: r.category,
536
+ description: this._describe_handler_for_route(r)
537
+ }))
538
+ };
539
+ }
540
+
541
+ _infer_method_for_route(route) {
542
+ if (route.category === 'api') return 'POST';
543
+ if (route.category === 'observable') return 'GET';
544
+ if (route.category === 'sse') return 'GET';
545
+ return 'GET';
546
+ }
547
+
548
+ _describe_handler_for_route(route) {
549
+ switch (route.handler_type) {
550
+ case 'Static_Route_HTTP_Responder':
551
+ if (route.path.endsWith('.js')) return 'Client JS bundle';
552
+ if (route.path.endsWith('.css')) return 'Extracted CSS bundle';
553
+ if (route.path === '/' || route.path === '/admin') return 'HTML page';
554
+ return 'Static file';
555
+ case 'HTTP_Function_Publisher':
556
+ return `server.publish('${route.handler_name || route.path}')`;
557
+ case 'Observable_Publisher':
558
+ return 'Observable → SSE';
559
+ case 'HTTP_SSE_Publisher':
560
+ return 'Server-Sent Events';
561
+ default:
562
+ return route.handler_type;
563
+ }
564
+ }
565
+ ```
566
+
567
+ ---
568
+
569
+ ## Route Filtering
570
+
571
+ The Route_Table supports a simple filter bar for large route sets:
572
+
573
+ ```javascript
574
+ // In activate():
575
+ const filter_input = document.querySelector('.route-filter-input');
576
+ if (filter_input) {
577
+ filter_input.addEventListener('input', (e) => {
578
+ const query = e.target.value.toLowerCase();
579
+ const rows = document.querySelectorAll('.route-row');
580
+ rows.forEach(row => {
581
+ const path = row.querySelector('.route-path')?.innerText?.toLowerCase() || '';
582
+ row.style.display = path.includes(query) ? '' : 'none';
583
+ });
584
+ });
585
+ }
586
+ ```