jsgui3-server 0.0.151 → 0.0.155
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.
- package/README.md +21 -0
- package/admin-ui/v1/controls/admin_shell.js +33 -0
- package/admin-ui/v1/server.js +14 -1
- package/docs/agi/skills/README.md +23 -0
- package/docs/agi/skills/agent-output-control/SKILL.md +56 -0
- package/docs/agi/skills/ai-deep-research/SKILL.md +52 -0
- package/docs/agi/skills/autonomous-ui-inspection/SKILL.md +102 -0
- package/docs/agi/skills/deep-research/SKILL.md +156 -0
- package/docs/agi/skills/endurance/SKILL.md +53 -0
- package/docs/agi/skills/exploring-other-codebases/SKILL.md +56 -0
- package/docs/agi/skills/instruction-adherence/SKILL.md +73 -0
- package/docs/agi/skills/jsgui3-activation-debug/SKILL.md +94 -0
- package/docs/agi/skills/jsgui3-context-menu-patterns/SKILL.md +94 -0
- package/docs/agi/skills/puppeteer-efficient-ui-verification/SKILL.md +65 -0
- package/docs/agi/skills/runaway-process-guard/SKILL.md +49 -0
- package/docs/agi/skills/session-discipline/SKILL.md +40 -0
- package/docs/agi/skills/skill-writing/SKILL.md +211 -0
- package/docs/agi/skills/static-analysis/SKILL.md +58 -0
- package/docs/agi/skills/targeted-testing/SKILL.md +63 -0
- package/docs/agi/skills/understanding-jsgui3/SKILL.md +85 -0
- package/docs/api-reference.md +120 -2
- package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +1 -0
- package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +33 -0
- package/docs/books/website-design/01-introduction.md +73 -0
- package/docs/books/website-design/02-current-state.md +195 -0
- package/docs/books/website-design/03-base-class.md +181 -0
- package/docs/books/website-design/04-webpage.md +307 -0
- package/docs/books/website-design/05-website.md +456 -0
- package/docs/books/website-design/06-pages-storage.md +170 -0
- package/docs/books/website-design/07-api-layer.md +285 -0
- package/docs/books/website-design/08-server-integration.md +271 -0
- package/docs/books/website-design/09-cross-agent-review.md +190 -0
- package/docs/books/website-design/10-open-questions.md +196 -0
- package/docs/books/website-design/11-converged-recommendation.md +205 -0
- package/docs/books/website-design/12-content-model.md +395 -0
- package/docs/books/website-design/13-webpage-module-spec.md +404 -0
- package/docs/books/website-design/14-website-module-spec.md +541 -0
- package/docs/books/website-design/15-multi-repo-plan.md +275 -0
- package/docs/books/website-design/16-minimal-first.md +203 -0
- package/docs/books/website-design/17-implementation-report-codex.md +81 -0
- package/docs/books/website-design/README.md +43 -0
- package/docs/bundling-system-deep-dive.md +112 -3
- package/docs/configuration-reference.md +84 -0
- package/docs/proposals/jsgui3-website-and-webpage-design-jsgui3-server-support.md +257 -0
- package/docs/proposals/jsgui3-website-and-webpage-design-review.md +73 -0
- package/docs/proposals/jsgui3-website-and-webpage-design.md +732 -0
- package/docs/swagger.md +316 -0
- package/examples/controls/1) window/server.js +6 -1
- package/examples/controls/21) mvvm and declarative api/check.js +94 -0
- package/examples/controls/21) mvvm and declarative api/check_output.txt +25 -0
- package/examples/controls/21) mvvm and declarative api/check_output_2.txt +27 -0
- package/examples/controls/21) mvvm and declarative api/client.js +241 -0
- declarative api/e2e-screenshot-1-name-change.png +0 -0
- declarative api/e2e-screenshot-2-toggled.png +0 -0
- declarative api/e2e-screenshot-3-final.png +0 -0
- declarative api/e2e-screenshot-final.png +0 -0
- package/examples/controls/21) mvvm and declarative api/e2e-test.js +175 -0
- package/examples/controls/21) mvvm and declarative api/out.html +1 -0
- package/examples/controls/21) mvvm and declarative api/page_out.html +1 -0
- package/examples/controls/21) mvvm and declarative api/server.js +18 -0
- package/examples/data-views/01) query-endpoint/server.js +61 -0
- package/labs/website-design/001-base-class-overhead/check.js +162 -0
- package/labs/website-design/002-pages-storage/check.js +244 -0
- package/labs/website-design/002-pages-storage/results.txt +0 -0
- package/labs/website-design/003-type-detection/check.js +193 -0
- package/labs/website-design/003-type-detection/results.txt +0 -0
- package/labs/website-design/004-two-stage-validation/check.js +314 -0
- package/labs/website-design/004-two-stage-validation/results.txt +0 -0
- package/labs/website-design/005-normalize-input/check.js +303 -0
- package/labs/website-design/006-serve-website-spike/check.js +290 -0
- package/labs/website-design/README.md +34 -0
- package/labs/website-design/manifest.json +68 -0
- package/labs/website-design/run-all.js +60 -0
- package/middleware/json-body.js +126 -0
- package/openapi.js +474 -0
- package/package.json +13 -7
- package/publishers/Publishers.js +6 -5
- package/publishers/http-function-publisher.js +135 -126
- package/publishers/http-webpage-publisher.js +89 -11
- package/publishers/query-publisher.js +116 -0
- package/publishers/swagger-publisher.js +203 -0
- package/publishers/swagger-ui.js +578 -0
- package/resources/adapters/array-adapter.js +143 -0
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +90 -22
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +50 -14
- package/resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild.js +48 -14
- package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +396 -44
- package/resources/query-resource.js +131 -0
- package/serve-factory.js +677 -18
- package/server.js +585 -167
- package/tests/README.md +86 -2
- package/tests/admin-ui-jsgui-controls.test.js +16 -1
- package/tests/bundling-default-control-elimination.puppeteer.test.js +32 -1
- package/tests/control-elimination-root-feature-pruning.test.js +440 -0
- package/tests/control-elimination-static-bracket-access.test.js +245 -0
- package/tests/control-scan-manifest-regression.test.js +2 -0
- package/tests/end-to-end.test.js +22 -21
- package/tests/fixtures/control_scan_manifest_expectations.json +4 -2
- package/tests/helpers/playwright-e2e-harness.js +326 -0
- package/tests/helpers/puppeteer-e2e-harness.js +62 -1
- package/tests/openapi.test.js +319 -0
- package/tests/playwright-smoke.test.js +134 -0
- package/tests/project-local-controls-bundling.puppeteer.test.js +462 -0
- package/tests/publish-enhancements.test.js +673 -0
- package/tests/query-publisher.test.js +430 -0
- package/tests/quick-json-body-test.js +169 -0
- package/tests/serve.test.js +425 -122
- package/tests/swagger-publisher.test.js +1076 -0
- package/tests/test-runner.js +4 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Chapter 3: The Base Class Question
|
|
2
|
+
|
|
3
|
+
The most foundational design decision for both `Webpage` and `Website` is what they inherit from. This choice shapes everything that follows — how properties work, whether events exist, what ecosystem patterns are available.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Option 1: Plain Class
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
class Webpage {
|
|
11
|
+
constructor(spec = {}) {
|
|
12
|
+
this.name = spec.name;
|
|
13
|
+
this.title = spec.title;
|
|
14
|
+
this.path = spec.path;
|
|
15
|
+
this.content = spec.content;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### The case for it
|
|
21
|
+
|
|
22
|
+
- **Maximum simplicity** — anyone reading it knows exactly what it does
|
|
23
|
+
- **Zero dependencies** — doesn't import anything from jsgui3-html
|
|
24
|
+
- **Easy to test** — no setup, no teardown, no event system to mock
|
|
25
|
+
- **Familiar** — looks like any plain JavaScript class
|
|
26
|
+
|
|
27
|
+
### The case against it
|
|
28
|
+
|
|
29
|
+
- **Inconsistent with the ecosystem** — nearly every non-trivial object in jsgui3 extends `Evented_Class`. Controls do. The server does. Resources, publishers, routers — all evented. A plain `Webpage` would be the odd one out.
|
|
30
|
+
- **Can't add events later without breaking** — if you later want `Webpage` to emit events (e.g. `'ready'`), changing the base class is a breaking change for any subclasses.
|
|
31
|
+
- **No `.on()` / `.raise()` pattern** — these are the fundamental building blocks of jsgui3's event system, widely used for lifecycle coordination.
|
|
32
|
+
|
|
33
|
+
### When it makes sense
|
|
34
|
+
|
|
35
|
+
When the object is purely a data structure with no lifecycle, no reactivity, and no expectation that it will ever need events. A config object. A DTO.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Option 2: `obext` Properties
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
const { prop } = require('obext');
|
|
43
|
+
|
|
44
|
+
class Webpage {
|
|
45
|
+
constructor(spec = {}) {
|
|
46
|
+
prop(this, 'name', spec.name);
|
|
47
|
+
prop(this, 'title', spec.title);
|
|
48
|
+
prop(this, 'path', spec.path);
|
|
49
|
+
prop(this, 'content', spec.content);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### The case for it
|
|
55
|
+
|
|
56
|
+
- **Consistent with jsgui3-server** — `server.js` uses `obext` extensively for its own properties (`disk_path_client_js`, `Ctrl`, `name`)
|
|
57
|
+
- **Encapsulation** — properties go through getter/setter, so behavior can be added later without changing the public API
|
|
58
|
+
- **Already in the dependency tree** — `jsgui3-html` depends on `obext`, so it's available at zero cost
|
|
59
|
+
|
|
60
|
+
### The case against it
|
|
61
|
+
|
|
62
|
+
- **Not a base class decision** — `obext` is a property mechanism, not a class hierarchy choice. You can use `obext` properties *and* extend `Evented_Class`. They're orthogonal.
|
|
63
|
+
- **Adds a layer of indirection** — every property access goes through a getter/setter. Usually negligible, but it's hidden complexity.
|
|
64
|
+
- **Not widely understood** — `obext` is a jsgui3 ecosystem tool, not a standard JS pattern. New contributors need to learn it.
|
|
65
|
+
|
|
66
|
+
### When it makes sense
|
|
67
|
+
|
|
68
|
+
When you want encapsulated properties but don't need (or aren't sure about) full event support. Good for a "grow into it" strategy.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Option 3: Extend `Evented_Class`
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
const { Evented_Class } = require('jsgui3-html');
|
|
76
|
+
|
|
77
|
+
class Webpage extends Evented_Class {
|
|
78
|
+
constructor(spec = {}) {
|
|
79
|
+
super();
|
|
80
|
+
this.name = spec.name;
|
|
81
|
+
this.title = spec.title;
|
|
82
|
+
this.path = spec.path;
|
|
83
|
+
this.content = spec.content;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### The case for it
|
|
89
|
+
|
|
90
|
+
- **It's the ecosystem standard** — nearly everything in jsgui3 extends `Evented_Class`. Using it here means `Webpage` and `Website` participate in the same patterns as controls, resources, and the server itself.
|
|
91
|
+
- **The capability is free** — extending `Evented_Class` doesn't force you to fire events on every property. You get `.on()` and `.raise()` as available tools. If nobody listens, the cost is a few extra bytes of prototype chain.
|
|
92
|
+
- **Future-proof** — once you ship without `Evented_Class`, adding it later is a breaking change. Starting with it costs almost nothing.
|
|
93
|
+
- **Real use cases exist** — a Website could raise `'page-added'`, `'ready'`, or `'error'`. A Webpage could raise `'content-changed'` for live reload. The admin UI already watches resources via events.
|
|
94
|
+
- **Doesn't require per-property events** — this is a crucial distinction. Extending `Evented_Class` does NOT mean wrapping every property in a change-event-firing setter. You can have simple `this.name = spec.name` assignments and only raise events for meaningful lifecycle moments.
|
|
95
|
+
|
|
96
|
+
### The case against it
|
|
97
|
+
|
|
98
|
+
- **Couples to jsgui3-html** — `Evented_Class` comes from `jsgui3-html`. If someone wanted to use `Webpage` without `jsgui3-html`, they couldn't.
|
|
99
|
+
- **Perceived complexity** — someone reading the code might think "events? for a webpage?" and assume it's over-engineered, even if no events are actually used.
|
|
100
|
+
- **Boilerplate concern** — if you do decide to fire change events on properties, each one needs a manual getter/setter with `this.raise()`.
|
|
101
|
+
|
|
102
|
+
### When it makes sense
|
|
103
|
+
|
|
104
|
+
When the object participates in a lifecycle, when other parts of the system might want to observe it, or when you're building within an ecosystem where `Evented_Class` is the norm.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Option 4: Combined — Evented_Class + obext
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
const { Evented_Class } = require('jsgui3-html');
|
|
112
|
+
const { prop } = require('obext');
|
|
113
|
+
|
|
114
|
+
class Webpage extends Evented_Class {
|
|
115
|
+
constructor(spec = {}) {
|
|
116
|
+
super();
|
|
117
|
+
prop(this, 'name', spec.name);
|
|
118
|
+
prop(this, 'title', spec.title);
|
|
119
|
+
prop(this, 'path', spec.path);
|
|
120
|
+
prop(this, 'content', spec.content);
|
|
121
|
+
prop(this, 'meta', spec.meta || {});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### The case for it
|
|
127
|
+
|
|
128
|
+
- **Best of both** — event system for lifecycle, encapsulated properties for cleanliness
|
|
129
|
+
- **Matches server.js exactly** — the server itself uses `Evented_Class` as a base and `obext` for properties
|
|
130
|
+
- **Maximum future flexibility** — events AND property interception available
|
|
131
|
+
|
|
132
|
+
### The case against it
|
|
133
|
+
|
|
134
|
+
- **Most complex option** — two dependency systems in play
|
|
135
|
+
- **Is the combination actually needed?** — if nobody is observing property changes on webpages, `obext` adds machinery without a use case
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Discussion: Boilerplate vs. Capability
|
|
140
|
+
|
|
141
|
+
A common concern with `Evented_Class` is the boilerplate needed for per-property change events:
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
get title() { return this._title; }
|
|
145
|
+
set title(v) {
|
|
146
|
+
const old = this._title;
|
|
147
|
+
this._title = v;
|
|
148
|
+
this.raise('change', { field: 'title', old, value: v });
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
But this is a **false dilemma**. You don't have to choose between "no events" and "events on every property." The pragmatic approach:
|
|
153
|
+
|
|
154
|
+
1. **Extend `Evented_Class`** — get the capability for free
|
|
155
|
+
2. **Use simple property assignment** — `this.title = spec.title`
|
|
156
|
+
3. **Raise events only for meaningful actions** — `this.raise('page-added', page)`, `this.raise('ready')`
|
|
157
|
+
|
|
158
|
+
This is exactly how the server works. It extends `Evented_Class` and raises `'ready'`, `'listening'`, `'starting'` — not per-property change events.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Comparison Table
|
|
163
|
+
|
|
164
|
+
| Criterion | Plain | obext | Evented | Combined |
|
|
165
|
+
|---|:---:|:---:|:---:|:---:|
|
|
166
|
+
| Simplicity | ★★★ | ★★☆ | ★★☆ | ★★☆ |
|
|
167
|
+
| Ecosystem consistency | ★☆☆ | ★★☆ | ★★★ | ★★★ |
|
|
168
|
+
| Zero jsgui3-html coupling | ★★★ | ★★☆ | ☆☆☆ | ☆☆☆ |
|
|
169
|
+
| Future event support | ☆☆☆ | ★☆☆ | ★★★ | ★★★ |
|
|
170
|
+
| Property encapsulation | ☆☆☆ | ★★★ | ★☆☆ | ★★★ |
|
|
171
|
+
| Matches server.js pattern | ☆☆☆ | ★★☆ | ★★☆ | ★★★ |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## The Author's View
|
|
176
|
+
|
|
177
|
+
Both modules will depend on `jsgui3-html` (this is a stated requirement). Given that, the coupling argument against `Evented_Class` disappears — you're already importing `jsgui3-html`.
|
|
178
|
+
|
|
179
|
+
In an ecosystem where `Evented_Class` is the norm, not using it requires justification. The justification would need to be "this object will never participate in lifecycle events" — but a Website with pages, API endpoints, and a publishing pipeline absolutely will.
|
|
180
|
+
|
|
181
|
+
The recommended starting point is **Option 3** (Evented_Class with simple properties), with the option to add `obext` for specific properties if encapsulation needs arise.
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Chapter 4: Designing the Webpage
|
|
2
|
+
|
|
3
|
+
A `Webpage` represents a single page in the abstract. It holds everything needed to describe what the page *is*, without knowing how to serve it. This chapter explores three approaches with increasing richness.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What properties should a Webpage have?
|
|
8
|
+
|
|
9
|
+
Before choosing an implementation, we need to agree on what a page knows about itself.
|
|
10
|
+
|
|
11
|
+
### Essential properties
|
|
12
|
+
|
|
13
|
+
| Property | Type | Purpose |
|
|
14
|
+
|----------|------|---------|
|
|
15
|
+
| `name` | `string` | Human-readable identifier |
|
|
16
|
+
| `title` | `string` | HTML `<title>` tag content |
|
|
17
|
+
| `path` | `string` | URL route (e.g. `/about`) |
|
|
18
|
+
| `content` | `Control` or constructor | The page body |
|
|
19
|
+
| `meta` | `object` | SEO and social metadata |
|
|
20
|
+
|
|
21
|
+
### Extended properties
|
|
22
|
+
|
|
23
|
+
| Property | Type | Purpose |
|
|
24
|
+
|----------|------|---------|
|
|
25
|
+
| `client_js` | `string` | Path to client-side JS entry point |
|
|
26
|
+
| `favicon` | `string` | Page-specific favicon |
|
|
27
|
+
| `scripts` | `string[]` | Additional script tags |
|
|
28
|
+
| `stylesheets` | `string[]` | Additional stylesheet links |
|
|
29
|
+
|
|
30
|
+
### Debatable properties
|
|
31
|
+
|
|
32
|
+
| Property | Type | Discussion |
|
|
33
|
+
|----------|------|-----------|
|
|
34
|
+
| `render_mode` | `'static'` or `'dynamic'` | See discussion below |
|
|
35
|
+
| `cache_policy` | `object` | Cache headers — abstraction or server detail? |
|
|
36
|
+
| `route_priority` | `number` | Ordering hint — useful or over-engineering? |
|
|
37
|
+
|
|
38
|
+
### Should `render_mode` live on the page?
|
|
39
|
+
|
|
40
|
+
The question: is rendering strategy a page concern or a server concern?
|
|
41
|
+
|
|
42
|
+
**The case for "server concern"**: The server decides how to bundle and serve. A page author shouldn't need to think about pre-rendering vs. per-request rendering — that's infrastructure. Keeping `render_mode` off the page keeps the page purely descriptive.
|
|
43
|
+
|
|
44
|
+
**The case for "page concern"**: Some pages *inherently* need dynamic rendering — a user profile that changes per-request can't be pre-rendered. The page author knows this; the server doesn't. Without a `render_mode` hint, the server either guesses (fragile) or forces all pages through the same pipeline (limiting).
|
|
45
|
+
|
|
46
|
+
**The deciding argument**: Today, `is_dynamic` (Approach B below) infers rendering strategy from the content type — `typeof content === 'function'` means dynamic. But this conflates "content is a class constructor" with "content must be rendered per-request." A control constructor might produce static output that's identical for every visitor. An explicit `render_mode` separates the *how to serve* question from the *what is the content* question.
|
|
47
|
+
|
|
48
|
+
**Resolution**: `render_mode` belongs on the Webpage as an **optional, recommended** field. It defaults to `undefined` (server decides based on content type, matching current behavior). When explicitly set, it overrides the server's inference. This is additive — existing code that doesn't set `render_mode` works exactly as before.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Approach A: Enhanced Property Bag
|
|
53
|
+
|
|
54
|
+
The simplest evolution of the current skeleton. Explicitly declare known properties instead of blindly `Object.assign`.
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
const { Evented_Class } = require('jsgui3-html');
|
|
58
|
+
|
|
59
|
+
class Webpage extends Evented_Class {
|
|
60
|
+
constructor(spec = {}) {
|
|
61
|
+
super();
|
|
62
|
+
|
|
63
|
+
this.name = spec.name || undefined;
|
|
64
|
+
this.title = spec.title || undefined;
|
|
65
|
+
this.path = spec.path; // No default — explicit is safer
|
|
66
|
+
this.content = spec.content || undefined;
|
|
67
|
+
this.meta = spec.meta || {};
|
|
68
|
+
this.client_js = spec.client_js || undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = Webpage;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Discussion
|
|
76
|
+
|
|
77
|
+
**What's good about this:**
|
|
78
|
+
- Dead simple — 13 lines of actual code
|
|
79
|
+
- Every property is visible in the constructor — excellent for discoverability
|
|
80
|
+
- Extends `Evented_Class` so lifecycle events are available if needed
|
|
81
|
+
- No default `path` — in a multi-page website, defaulting to `'/'` could silently create collisions
|
|
82
|
+
|
|
83
|
+
**What's missing:**
|
|
84
|
+
- No validation — `content` could be a string, a number, anything
|
|
85
|
+
- No computed properties — can't ask "does this page have content?"
|
|
86
|
+
- No serialization — no `toJSON()` for admin/tooling introspection
|
|
87
|
+
- No way to tell if a spec property was explicitly set vs. just absent
|
|
88
|
+
|
|
89
|
+
**Best for:** Getting started quickly. You can always add methods later without breaking anything because the base class is right.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Approach B: With Computed Properties and Introspection
|
|
94
|
+
|
|
95
|
+
Adds helpers that make the Webpage useful to servers, admin UIs, and tooling.
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
const { Evented_Class } = require('jsgui3-html');
|
|
99
|
+
|
|
100
|
+
class Webpage extends Evented_Class {
|
|
101
|
+
constructor(spec = {}) {
|
|
102
|
+
super();
|
|
103
|
+
|
|
104
|
+
this.name = spec.name || undefined;
|
|
105
|
+
this.title = spec.title || undefined;
|
|
106
|
+
this.path = spec.path;
|
|
107
|
+
this.content = spec.content || undefined;
|
|
108
|
+
this.meta = spec.meta || {};
|
|
109
|
+
this.client_js = spec.client_js || undefined;
|
|
110
|
+
this.favicon = spec.favicon || undefined;
|
|
111
|
+
this.scripts = spec.scripts || [];
|
|
112
|
+
this.stylesheets = spec.stylesheets || [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Does this page have renderable content? */
|
|
116
|
+
get has_content() {
|
|
117
|
+
return this.content !== undefined && this.content !== null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Is the content a constructor/function (needs instantiation)
|
|
122
|
+
* vs. an existing instance?
|
|
123
|
+
*/
|
|
124
|
+
get is_dynamic() {
|
|
125
|
+
return typeof this.content === 'function';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Stable, deterministic serialization for admin/tooling */
|
|
129
|
+
toJSON() {
|
|
130
|
+
return {
|
|
131
|
+
name: this.name,
|
|
132
|
+
title: this.title,
|
|
133
|
+
path: this.path,
|
|
134
|
+
has_content: this.has_content,
|
|
135
|
+
is_dynamic: this.is_dynamic,
|
|
136
|
+
meta: this.meta
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = Webpage;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Discussion
|
|
145
|
+
|
|
146
|
+
**What's good about this:**
|
|
147
|
+
|
|
148
|
+
- `has_content` and `is_dynamic` answer questions that publishers currently figure out themselves — moves that logic to the source of truth
|
|
149
|
+
- `toJSON()` gives admin UIs and diagnostic tools a stable contract to rely on — they don't need to understand the class internals
|
|
150
|
+
- `scripts` and `stylesheets` arrays let pages declare additional assets beyond what the bundler produces — useful for third-party libraries, analytics scripts, etc.
|
|
151
|
+
- Still very readable — the computed properties are clearly getters, not hidden magic
|
|
152
|
+
|
|
153
|
+
**What's debatable:**
|
|
154
|
+
|
|
155
|
+
- Should `toJSON()` include `content`? The content is usually a class constructor, which doesn't serialize. Including it could cause confusion. Excluding it means the JSON is a summary, not a full representation.
|
|
156
|
+
- Are `scripts` and `stylesheets` page concerns or server concerns? Today the server/publisher decides what goes into the HTML `<head>`. Moving that to the page definition shifts responsibility.
|
|
157
|
+
- `is_dynamic` currently means "content is a function" — but the term "dynamic" could also mean "rendered per-request." The naming might be confusing when `render_mode` enters the picture.
|
|
158
|
+
|
|
159
|
+
**Best for:** A practical middle ground that's immediately useful without over-engineering.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Approach C: With Validation and Lifecycle
|
|
164
|
+
|
|
165
|
+
Adds input validation and lifecycle semantics.
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
const { Evented_Class, tof } = require('jsgui3-html');
|
|
169
|
+
|
|
170
|
+
class Webpage extends Evented_Class {
|
|
171
|
+
constructor(spec = {}) {
|
|
172
|
+
super();
|
|
173
|
+
|
|
174
|
+
// Validate path format
|
|
175
|
+
if (spec.path !== undefined) {
|
|
176
|
+
if (typeof spec.path !== 'string') {
|
|
177
|
+
throw new TypeError(`Webpage path must be a string, got ${tof(spec.path)}`);
|
|
178
|
+
}
|
|
179
|
+
// Normalize: ensure leading slash
|
|
180
|
+
this.path = spec.path.startsWith('/') ? spec.path : '/' + spec.path;
|
|
181
|
+
} else {
|
|
182
|
+
this.path = undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Validate content type
|
|
186
|
+
if (spec.content !== undefined) {
|
|
187
|
+
const ct = tof(spec.content);
|
|
188
|
+
if (ct !== 'function' && ct !== 'object') {
|
|
189
|
+
throw new TypeError(
|
|
190
|
+
`Webpage content must be a Control constructor or instance, got ${ct}`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.name = spec.name || undefined;
|
|
196
|
+
this.title = spec.title || undefined;
|
|
197
|
+
this.content = spec.content || undefined;
|
|
198
|
+
this.meta = spec.meta || {};
|
|
199
|
+
this.client_js = spec.client_js || undefined;
|
|
200
|
+
this.favicon = spec.favicon || undefined;
|
|
201
|
+
this.scripts = spec.scripts || [];
|
|
202
|
+
this.stylesheets = spec.stylesheets || [];
|
|
203
|
+
|
|
204
|
+
this._finalized = false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
get has_content() {
|
|
208
|
+
return this.content !== undefined && this.content !== null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
get is_dynamic() {
|
|
212
|
+
return typeof this.content === 'function';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Mark this webpage as finalized — no further mutations expected.
|
|
217
|
+
* Called by the publisher before bundling. Optional but useful
|
|
218
|
+
* for catching accidental late modifications.
|
|
219
|
+
*/
|
|
220
|
+
finalize() {
|
|
221
|
+
if (this._finalized) return this;
|
|
222
|
+
this._finalized = true;
|
|
223
|
+
this.raise('finalized');
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
get finalized() {
|
|
228
|
+
return this._finalized;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
toJSON() {
|
|
232
|
+
return {
|
|
233
|
+
name: this.name,
|
|
234
|
+
title: this.title,
|
|
235
|
+
path: this.path,
|
|
236
|
+
has_content: this.has_content,
|
|
237
|
+
is_dynamic: this.is_dynamic,
|
|
238
|
+
finalized: this._finalized,
|
|
239
|
+
meta: this.meta
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = Webpage;
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Discussion
|
|
248
|
+
|
|
249
|
+
**What's good about this:**
|
|
250
|
+
|
|
251
|
+
- **Path normalization** — ensures leading slash, catches non-string paths early. Today a missing `'/'` prefix would silently create a broken route.
|
|
252
|
+
- **Content validation** — catches `content: 42` or `content: 'whoops'` at construction time instead of deep in the publisher pipeline where the error is confusing.
|
|
253
|
+
- **`finalize()` lifecycle** — this was suggested by the OpenAI reviewer as an alternative to full reactivity. The idea: a Webpage is mutable during composition and read-mostly after `finalize()` is called. This gives publishers confidence that the page won't change under them without requiring a full event system for every property.
|
|
254
|
+
- Uses `tof()` from jsgui3-html for type checking — ecosystem-consistent.
|
|
255
|
+
|
|
256
|
+
**What's debatable:**
|
|
257
|
+
|
|
258
|
+
- Is validation in the constructor too strict? If someone passes `content: null` temporarily and sets it later, the validation could get in the way. Counter-argument: if content is set later, `null` would pass validation (only non-null non-function/object types are rejected).
|
|
259
|
+
- `finalize()` is a new concept not used elsewhere in jsgui3. It adds cognitive load. Counter-argument: it's simple enough to ignore — just don't call it.
|
|
260
|
+
- Path normalization makes an opinionated choice. What about root-relative vs. absolute paths? What about paths with query strings or fragments?
|
|
261
|
+
|
|
262
|
+
**Best for:** When you want construction-time safety and a clear lifecycle boundary.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Comparison
|
|
267
|
+
|
|
268
|
+
| Criterion | A (Property Bag) | B (Computed + toJSON) | C (Validation + Lifecycle) |
|
|
269
|
+
|---|:---:|:---:|:---:|
|
|
270
|
+
| Lines of code | ~13 | ~40 | ~65 |
|
|
271
|
+
| Input validation | ☆ | ☆ | ★★★ |
|
|
272
|
+
| Introspection | ☆ | ★★★ | ★★★ |
|
|
273
|
+
| Lifecycle support | ☆ | ☆ | ★★ |
|
|
274
|
+
| Ease of understanding | ★★★ | ★★★ | ★★☆ |
|
|
275
|
+
| Catches bugs early | ☆ | ★ | ★★★ |
|
|
276
|
+
| Can evolve into C | ★★★ | ★★★ | — |
|
|
277
|
+
|
|
278
|
+
Note that all three extend `Evented_Class`, so they all have event capability. The difference is in how much the class *does* with that capability.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Should `Object.assign(this, spec)` stay?
|
|
283
|
+
|
|
284
|
+
The current code does `Object.assign(this, spec)`, which copies ALL spec properties onto the instance — including unknown/unexpected ones. This is a double-edged sword:
|
|
285
|
+
|
|
286
|
+
**For**: Maximum flexibility. Users can put anything in spec and it "just works." Good for rapid prototyping and forward compatibility (adding new fields doesn't require class changes).
|
|
287
|
+
|
|
288
|
+
**Against**: No discoverability. Typos in property names silently create wrong properties. No way to enumerate "known" vs. "extra" properties. Makes the class contract unclear.
|
|
289
|
+
|
|
290
|
+
**A middle ground**: Explicitly assign known properties, then optionally store extras:
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
// Known properties
|
|
294
|
+
this.name = spec.name;
|
|
295
|
+
this.title = spec.title;
|
|
296
|
+
// ...
|
|
297
|
+
|
|
298
|
+
// Store any additional spec properties for extensibility
|
|
299
|
+
this.extra = {};
|
|
300
|
+
for (const key of Object.keys(spec)) {
|
|
301
|
+
if (!['name', 'title', 'path', 'content', 'meta', 'client_js'].includes(key)) {
|
|
302
|
+
this.extra[key] = spec[key];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
This preserves flexibility while making the primary contract explicit. Whether this is worth the complexity depends on how important forward compatibility is.
|