jsgui3-server 0.0.150 → 0.0.152

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 (86) hide show
  1. package/.github/instructions/copilot.instructions.md +1 -0
  2. package/AGENTS.md +2 -0
  3. package/README.md +89 -13
  4. package/admin-ui/v1/controls/admin_shell.js +702 -669
  5. package/admin-ui/v1/server.js +14 -1
  6. package/docs/api-reference.md +504 -306
  7. package/docs/books/creating-a-new-admin-ui/README.md +20 -20
  8. package/docs/books/website-design/01-introduction.md +73 -0
  9. package/docs/books/website-design/02-current-state.md +195 -0
  10. package/docs/books/website-design/03-base-class.md +181 -0
  11. package/docs/books/website-design/04-webpage.md +307 -0
  12. package/docs/books/website-design/05-website.md +456 -0
  13. package/docs/books/website-design/06-pages-storage.md +170 -0
  14. package/docs/books/website-design/07-api-layer.md +285 -0
  15. package/docs/books/website-design/08-server-integration.md +271 -0
  16. package/docs/books/website-design/09-cross-agent-review.md +190 -0
  17. package/docs/books/website-design/10-open-questions.md +196 -0
  18. package/docs/books/website-design/11-converged-recommendation.md +205 -0
  19. package/docs/books/website-design/12-content-model.md +395 -0
  20. package/docs/books/website-design/13-webpage-module-spec.md +404 -0
  21. package/docs/books/website-design/14-website-module-spec.md +541 -0
  22. package/docs/books/website-design/15-multi-repo-plan.md +275 -0
  23. package/docs/books/website-design/16-minimal-first.md +203 -0
  24. package/docs/books/website-design/17-implementation-report-codex.md +81 -0
  25. package/docs/books/website-design/README.md +43 -0
  26. package/docs/comprehensive-documentation.md +220 -220
  27. package/docs/configuration-reference.md +281 -204
  28. package/docs/middleware-guide.md +236 -0
  29. package/docs/proposals/jsgui3-website-and-webpage-design-jsgui3-server-support.md +257 -0
  30. package/docs/proposals/jsgui3-website-and-webpage-design-review.md +73 -0
  31. package/docs/proposals/jsgui3-website-and-webpage-design.md +732 -0
  32. package/docs/swagger.md +316 -0
  33. package/docs/system-architecture.md +24 -18
  34. package/examples/controls/1) window/server.js +6 -1
  35. package/examples/controls/21) mvvm and declarative api/check.js +94 -0
  36. package/examples/controls/21) mvvm and declarative api/check_output.txt +25 -0
  37. package/examples/controls/21) mvvm and declarative api/check_output_2.txt +27 -0
  38. package/examples/controls/21) mvvm and declarative api/client.js +241 -0
  39. declarative api/e2e-screenshot-1-name-change.png +0 -0
  40. declarative api/e2e-screenshot-2-toggled.png +0 -0
  41. declarative api/e2e-screenshot-3-final.png +0 -0
  42. declarative api/e2e-screenshot-final.png +0 -0
  43. package/examples/controls/21) mvvm and declarative api/e2e-test.js +175 -0
  44. package/examples/controls/21) mvvm and declarative api/out.html +1 -0
  45. package/examples/controls/21) mvvm and declarative api/page_out.html +1 -0
  46. package/examples/controls/21) mvvm and declarative api/server.js +18 -0
  47. package/examples/data-views/01) query-endpoint/server.js +61 -0
  48. package/labs/website-design/001-base-class-overhead/check.js +162 -0
  49. package/labs/website-design/002-pages-storage/check.js +244 -0
  50. package/labs/website-design/002-pages-storage/results.txt +0 -0
  51. package/labs/website-design/003-type-detection/check.js +193 -0
  52. package/labs/website-design/003-type-detection/results.txt +0 -0
  53. package/labs/website-design/004-two-stage-validation/check.js +314 -0
  54. package/labs/website-design/004-two-stage-validation/results.txt +0 -0
  55. package/labs/website-design/005-normalize-input/check.js +303 -0
  56. package/labs/website-design/006-serve-website-spike/check.js +290 -0
  57. package/labs/website-design/README.md +34 -0
  58. package/labs/website-design/manifest.json +68 -0
  59. package/labs/website-design/run-all.js +60 -0
  60. package/middleware/compression.js +217 -0
  61. package/middleware/index.js +15 -0
  62. package/middleware/json-body.js +126 -0
  63. package/module.js +3 -0
  64. package/openapi.js +474 -0
  65. package/package.json +11 -8
  66. package/publishers/Publishers.js +6 -5
  67. package/publishers/http-function-publisher.js +135 -126
  68. package/publishers/http-webpage-publisher.js +89 -11
  69. package/publishers/query-publisher.js +116 -0
  70. package/publishers/swagger-publisher.js +203 -0
  71. package/publishers/swagger-ui.js +578 -0
  72. package/resources/adapters/array-adapter.js +143 -0
  73. package/resources/query-resource.js +131 -0
  74. package/serve-factory.js +756 -18
  75. package/server.js +502 -123
  76. package/tests/README.md +23 -1
  77. package/tests/admin-ui-jsgui-controls.test.js +16 -1
  78. package/tests/helpers/playwright-e2e-harness.js +326 -0
  79. package/tests/openapi.test.js +319 -0
  80. package/tests/playwright-smoke.test.js +134 -0
  81. package/tests/publish-enhancements.test.js +673 -0
  82. package/tests/query-publisher.test.js +430 -0
  83. package/tests/quick-json-body-test.js +169 -0
  84. package/tests/serve.test.js +425 -122
  85. package/tests/swagger-publisher.test.js +1076 -0
  86. package/tests/test-runner.js +1 -0
@@ -0,0 +1,404 @@
1
+ # Chapter 13: jsgui3-webpage Module Specification
2
+
3
+ This chapter is a complete implementation blueprint for the `jsgui3-webpage` package. An agent should be able to implement the module from this spec alone.
4
+
5
+ ---
6
+
7
+ ## 13.1 Package Identity
8
+
9
+ | Field | Value |
10
+ |-------|-------|
11
+ | **npm name** | `jsgui3-webpage` |
12
+ | **Current version** | 0.0.8 (skeleton: `Object.assign(this, spec)`) |
13
+ | **Target version** | 0.3.0 (full-spec track; see Ch.16 for v0.1 minimal track) |
14
+ | **License** | MIT (align with current package + repo conventions) |
15
+ | **Repository** | Separate repo (same GitHub org as jsgui3-server) |
16
+
17
+ ---
18
+
19
+ ## 13.2 Dependencies
20
+
21
+ | Dependency | Type | Rationale |
22
+ |-----------|------|-----------|
23
+ | `jsgui3-html` | **production** | Provides `Evented_Class` as base class (confirmed by Lab 001: acceptable overhead) |
24
+
25
+ No other dependencies. Keep this package minimal — it's a data container, not a framework.
26
+
27
+ ### Why depend on jsgui3-html?
28
+
29
+ `Evented_Class` is needed for `on()`/`raise()` lifecycle events (`finalized`, `content-changed`). Lab 001 confirmed the overhead is acceptable (~100 bytes/instance, <3ms for 10k constructions). The alternative — bundling a standalone event emitter — would duplicate ecosystem functionality.
30
+
31
+ ---
32
+
33
+ ## 13.3 File Layout
34
+
35
+ ```
36
+ jsgui3-webpage/
37
+ ├── Webpage.js # Main class
38
+ ├── index.js # Re-exports Webpage (for require('jsgui3-webpage'))
39
+ ├── package.json
40
+ ├── README.md # Usage documentation
41
+ ├── LICENSE
42
+ └── test/
43
+ └── webpage.test.js # Unit tests
44
+ ```
45
+
46
+ ### index.js
47
+
48
+ ```js
49
+ module.exports = require('./Webpage');
50
+ ```
51
+
52
+ This preserves the current `require('jsgui3-webpage')` behavior.
53
+
54
+ ---
55
+
56
+ ## 13.4 Constructor Signature
57
+
58
+ ```js
59
+ const page = new Webpage(spec);
60
+ ```
61
+
62
+ Where `spec` is an optional plain object:
63
+
64
+ ```js
65
+ {
66
+ // ── Routing ──
67
+ path: String, // URL path (normalized to leading slash)
68
+
69
+ // ── Identity ──
70
+ name: String, // Machine-readable identifier
71
+ title: String | i18n, // Human-readable page title (translatable)
72
+
73
+ // ── Content ──
74
+ ctrl: Function, // Control constructor (the UI component)
75
+ content: Object | Function, // Structured content. If Function and ctrl unset: legacy ctrl alias (deprecated).
76
+ render_mode: String, // 'static' | 'dynamic' (optional, server can infer)
77
+
78
+ // ── Assets ──
79
+ scripts: String[], // Client JS file paths
80
+ stylesheets: String[], // CSS file paths
81
+
82
+ // ── Metadata ──
83
+ meta: Object, // SEO: { description, keywords, og:* }
84
+ }
85
+ ```
86
+
87
+ ### i18n values
88
+
89
+ Any string field can alternatively be an object keyed by locale:
90
+
91
+ ```js
92
+ title: 'About Us' // plain string
93
+ title: { en: 'About Us', fr: 'À propos' } // translatable
94
+ ```
95
+
96
+ ### Migration compatibility contract (`ctrl` vs `content`)
97
+
98
+ Canonical model:
99
+
100
+ 1. `ctrl` is the renderer.
101
+ 2. `content` is structured page data/i18n payload.
102
+
103
+ Legacy compatibility (for current server ecosystem):
104
+
105
+ 1. If `spec.ctrl` is missing and `spec.content` is a `function`, treat it as renderer input.
106
+ 2. Normalize internally to `ctrl`, and treat structured `content` as unset.
107
+ 3. Emit a deprecation warning for legacy `content: Function` shape (remove no earlier than v0.4.x).
108
+
109
+ ---
110
+
111
+ ## 13.5 Property Reference
112
+
113
+ ### Core Properties
114
+
115
+ | Property | Type | Default | Description |
116
+ |----------|------|---------|-------------|
117
+ | `path` | `string \| undefined` | `undefined` | URL path, auto-normalized to leading `/` |
118
+ | `name` | `string \| undefined` | `undefined` | Machine identifier |
119
+ | `title` | `string \| object \| undefined` | `undefined` | Translatable page title |
120
+ | `ctrl` | `function \| undefined` | `undefined` | Control constructor for rendering |
121
+ | `content` | `object \| undefined` | `undefined` | Named strings / structured data (post-normalization) |
122
+ | `render_mode` | `string \| undefined` | `undefined` | `'static'` or `'dynamic'` |
123
+ | `scripts` | `string[]` | `[]` | Client-side JS paths |
124
+ | `stylesheets` | `string[]` | `[]` | CSS paths |
125
+ | `meta` | `object` | `{}` | SEO/OpenGraph metadata |
126
+
127
+ ### Computed Properties (getters)
128
+
129
+ | Property | Type | Description |
130
+ |----------|------|-------------|
131
+ | `has_ctrl` | `boolean` | `true` if `ctrl` is set |
132
+ | `has_content` | `boolean` | `true` if `content` is set and non-empty |
133
+ | `is_dynamic` | `boolean` | `true` if `ctrl` is a function |
134
+ | `finalized` | `boolean` | `true` after `finalize()` called |
135
+ | `locales` | `string[]` | Unique locale keys found across title + content |
136
+
137
+ ### Private Properties
138
+
139
+ | Property | Type | Description |
140
+ |----------|------|-------------|
141
+ | `_finalized` | `boolean` | Internal finalization flag |
142
+
143
+ ---
144
+
145
+ ## 13.6 Method Reference
146
+
147
+ ### `finalize() → this`
148
+
149
+ Mark the page as ready for publication. Performs strict validation:
150
+
151
+ **Throws** if:
152
+ - `path` is not set
153
+ - `ctrl` is not set (page needs a renderer, after legacy alias normalization)
154
+ - `render_mode` is set but not `'static'` or `'dynamic'`
155
+
156
+ **Does not throw** if:
157
+ - `content` is missing (some pages are purely interactive)
158
+ - `title` is missing (some pages don't need titles)
159
+ - Already finalized (idempotent)
160
+
161
+ **Raises** `'finalized'` event after validation passes.
162
+
163
+ ---
164
+
165
+ ### `get_string(key, locale?) → string | undefined`
166
+
167
+ Resolve a content string by dotted path, with optional locale:
168
+
169
+ ```js
170
+ page.get_string('hero.heading', 'fr');
171
+ ```
172
+
173
+ **Resolution order:**
174
+ 1. Walk `content` using dotted key path
175
+ 2. If value is a string → return it
176
+ 3. If value is an i18n object → resolve locale with fallback chain
177
+ 4. If value is not found → return `undefined`
178
+
179
+ **Locale fallback:**
180
+ ```
181
+ exact match → language-only → first available
182
+ 'en-GB' → 'en' → Object.keys(value)[0]
183
+ ```
184
+
185
+ ---
186
+
187
+ ### `get_title(locale?) → string | undefined`
188
+
189
+ Resolve the page title with locale fallback. Same resolution logic as `get_string` but operates on `this.title` directly.
190
+
191
+ ---
192
+
193
+ ### `resolve_content(locale?) → object`
194
+
195
+ Return a copy of `content` with all i18n values resolved to the requested locale:
196
+
197
+ ```js
198
+ page.resolve_content('fr');
199
+ // → { heading: 'À propos', body: 'Nous construisons...' }
200
+ ```
201
+
202
+ Controls receive this resolved object — they never see locale keys.
203
+
204
+ ---
205
+
206
+ ### `toJSON() → object`
207
+
208
+ Return an admin-friendly summary:
209
+
210
+ ```js
211
+ {
212
+ path: '/about',
213
+ name: 'about',
214
+ title: { en: 'About Us', fr: 'À propos' },
215
+ has_ctrl: true,
216
+ has_content: true,
217
+ content_keys: ['heading', 'body', 'cta'],
218
+ locales: ['en', 'fr'],
219
+ is_dynamic: true,
220
+ finalized: false,
221
+ render_mode: undefined,
222
+ meta: {}
223
+ }
224
+ ```
225
+
226
+ Note: full content values are **not** included in `toJSON()` — only keys and locale summary. Access `page.content` directly for full data.
227
+
228
+ ---
229
+
230
+ ## 13.7 Type Marker
231
+
232
+ For cross-install detection (see Lab 003):
233
+
234
+ ```js
235
+ get [Symbol.for('jsgui3.webpage')]() { return true; }
236
+ ```
237
+
238
+ Detection function (exported as a utility):
239
+
240
+ ```js
241
+ function isWebpage(obj) {
242
+ if (obj == null || typeof obj !== 'object') return false;
243
+ if (obj[Symbol.for('jsgui3.webpage')] === true) return true;
244
+ return typeof obj.path === 'string'
245
+ && typeof obj.finalize === 'function'
246
+ && typeof obj.toJSON === 'function';
247
+ }
248
+ ```
249
+
250
+ ---
251
+
252
+ ## 13.8 Events
253
+
254
+ | Event | Payload | When |
255
+ |-------|---------|------|
256
+ | `'finalized'` | `undefined` | After `finalize()` completes |
257
+
258
+ Future events (not for v0.3.0):
259
+ - `'content-changed'` — when content is set or updated
260
+ - `'path-changed'` — when path is modified
261
+
262
+ ---
263
+
264
+ ## 13.9 Validation Rules
265
+
266
+ ### Stage 1: Construction time
267
+
268
+ | Condition | Behavior |
269
+ |-----------|----------|
270
+ | `path` is set and not a string | **throw** `TypeError` |
271
+ | `path` is `null` or `undefined` | allowed (set later) |
272
+ | `ctrl` is set and not a function/object | **throw** `TypeError` |
273
+ | `content` is a function and `ctrl` is unset | accepted as legacy alias; normalized to `ctrl` |
274
+ | `content` is set and is neither object nor function | **throw** `TypeError` |
275
+ | Everything else | allowed — the page is a work-in-progress |
276
+
277
+ ### Stage 2: `finalize()`
278
+
279
+ | Condition | Behavior |
280
+ |-----------|----------|
281
+ | `path` is not set | **throw** Error |
282
+ | `ctrl` is not set | **throw** Error |
283
+ | `render_mode` is set but invalid | **throw** Error |
284
+ | `content` is not set | allowed |
285
+ | Already finalized | no-op (return `this`) |
286
+
287
+ ---
288
+
289
+ ## 13.10 Path Normalization
290
+
291
+ Applied at construction time if `path` is a string:
292
+
293
+ ```js
294
+ this.path = spec.path.startsWith('/') ? spec.path : '/' + spec.path;
295
+ ```
296
+
297
+ No further normalization (no trailing slash stripping, no query string parsing). The path is an opaque route key.
298
+
299
+ ---
300
+
301
+ ## 13.11 Backward Compatibility
302
+
303
+ The current package (v0.0.8) has the contract:
304
+
305
+ ```js
306
+ class Webpage {
307
+ constructor(spec) { Object.assign(this, spec); }
308
+ }
309
+ ```
310
+
311
+ The full-spec version (v0.3.0) changes:
312
+
313
+ | Aspect | v0.0.8 | v0.3.0 |
314
+ |--------|--------|--------|
315
+ | Base class | None | `Evented_Class` |
316
+ | Properties | `Object.assign` (anything) | Explicit known fields + extras |
317
+ | Methods | None | `finalize()`, `get_string()`, `toJSON()`, etc. |
318
+ | Validation | None | Two-stage |
319
+
320
+ **Breaking changes:**
321
+ 1. Properties not in the known list are no longer auto-assigned. Use `meta` for arbitrary extras.
322
+ 2. The instance is now an `Evented_Class`, so `instanceof Evented_Class` will be `true`.
323
+
324
+ **Compatibility bridge included:**
325
+ 1. Legacy `content: Function` input is still accepted as renderer alias during migration.
326
+
327
+ **Migration path:** Code that only reads properties set in the constructor (`page.path`, `page.title`) will continue to work. Code that relies on arbitrary spec properties being copied needs updating.
328
+
329
+ ---
330
+
331
+ ## 13.12 Usage Examples
332
+
333
+ ### Minimal page
334
+
335
+ ```js
336
+ const Webpage = require('jsgui3-webpage');
337
+ const page = new Webpage({ path: '/', ctrl: HomeCtrl });
338
+ ```
339
+
340
+ ### Page with content
341
+
342
+ ```js
343
+ const page = new Webpage({
344
+ path: '/about',
345
+ title: 'About Us',
346
+ ctrl: AboutCtrl,
347
+ content: {
348
+ heading: 'About Our Company',
349
+ body: 'We build tools for developers.',
350
+ team_size: '50+'
351
+ }
352
+ });
353
+ ```
354
+
355
+ ### Multi-language page
356
+
357
+ ```js
358
+ const page = new Webpage({
359
+ path: '/about',
360
+ title: { en: 'About Us', fr: 'À propos', de: 'Über uns' },
361
+ ctrl: AboutCtrl,
362
+ content: {
363
+ heading: { en: 'About Our Company', fr: 'À propos de notre entreprise' },
364
+ body: { en: 'We build tools...', fr: 'Nous construisons...' }
365
+ }
366
+ });
367
+
368
+ page.get_title('fr'); // → 'À propos'
369
+ page.get_string('heading', 'fr'); // → 'À propos de notre entreprise'
370
+ page.resolve_content('fr'); // → { heading: '...', body: '...' }
371
+ ```
372
+
373
+ ### Incremental composition
374
+
375
+ ```js
376
+ const page = new Webpage({});
377
+ page.path = '/pricing';
378
+ page.ctrl = PricingCtrl;
379
+ page.content = await loadContent('pricing');
380
+ page.finalize(); // validates everything is set
381
+ ```
382
+
383
+ ---
384
+
385
+ ## 13.13 Test Plan
386
+
387
+ The following test areas should be covered:
388
+
389
+ 1. **Construction**: empty spec, partial spec, full spec, bad types throw
390
+ 2. **Path normalization**: leading slash added, root path, already-normalized
391
+ 3. **Content access**: `get_string` with dotted paths, plain strings, i18n objects
392
+ 4. **Locale resolution**: exact match, language fallback, first-available fallback
393
+ 5. **`resolve_content`**: resolves nested i18n, preserves plain strings
394
+ 6. **`get_title`**: plain string, i18n object, undefined
395
+ 7. **Computed properties**: `has_ctrl`, `has_content`, `is_dynamic`, `locales`
396
+ 8. **`finalize()`**: validates path+ctrl, rejects bad render_mode, idempotent
397
+ 9. **Events**: `'finalized'` event fires
398
+ 10. **`toJSON()`**: correct shape, content_keys, locales, no raw content values
399
+ 11. **Type marker**: `Symbol.for('jsgui3.webpage')` is `true`
400
+ 12. **Backward compatibility**: setting common properties works as before
401
+
402
+ ---
403
+
404
+ *Next: [Chapter 14](14-website-module-spec.md) defines the `jsgui3-website` module specification.*