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,395 @@
|
|
|
1
|
+
# Chapter 12: The Content Model
|
|
2
|
+
|
|
3
|
+
The previous chapters treated `content` as a reference to a Control constructor — a UI component that renders the page. This chapter expands the model: a **Webpage holds actual content**, not just a pointer to a renderer.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 12.1 Why Content Belongs on the Page
|
|
8
|
+
|
|
9
|
+
A Webpage that only holds routing metadata (`path`, `title`) and a Control reference leaves a fundamental question unanswered: **where does the text live?**
|
|
10
|
+
|
|
11
|
+
In the current jsgui3 ecosystem, page text is typically hardcoded inside the Control's `compose()` method:
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
class AboutPage extends Control {
|
|
15
|
+
compose() {
|
|
16
|
+
const h1 = new Control({ context: this.context, tagName: 'h1' });
|
|
17
|
+
h1.text = 'About Our Company'; // ← text is embedded in the UI
|
|
18
|
+
this.add(h1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This works for single-language sites with stable copy, but it breaks down when:
|
|
24
|
+
|
|
25
|
+
- The same page needs to render in multiple languages
|
|
26
|
+
- Content is authored by non-developers (CMS, markdown files, API)
|
|
27
|
+
- The same Control layout needs different text for different sites
|
|
28
|
+
- A/B testing requires swapping copy without changing the component
|
|
29
|
+
|
|
30
|
+
**The fix**: the Webpage holds the content, and the Control receives it at render time.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 12.2 Content as Named Strings
|
|
35
|
+
|
|
36
|
+
The simplest content model is a flat map of named strings:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
const page = new Webpage({
|
|
40
|
+
path: '/about',
|
|
41
|
+
title: 'About Us',
|
|
42
|
+
ctrl: AboutCtrl,
|
|
43
|
+
strings: {
|
|
44
|
+
heading: 'About Our Company',
|
|
45
|
+
body: 'We build tools for developers...',
|
|
46
|
+
cta: 'Get Started'
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Why named strings?
|
|
52
|
+
|
|
53
|
+
| Property | Benefit |
|
|
54
|
+
|----------|---------|
|
|
55
|
+
| Explicit keys | Controls reference content by name, not position |
|
|
56
|
+
| Serializable | `JSON.stringify(page.strings)` just works |
|
|
57
|
+
| Inspectable | Admin UIs can list what content a page expects |
|
|
58
|
+
| Diffable | Content changes show up as key-level diffs |
|
|
59
|
+
| Translatable | Each key maps independently to translations |
|
|
60
|
+
|
|
61
|
+
### What counts as a "string"?
|
|
62
|
+
|
|
63
|
+
Content strings may contain:
|
|
64
|
+
|
|
65
|
+
- **Plain text**: `'About Our Company'`
|
|
66
|
+
- **Markdown**: `'# Welcome\n\nWe build **tools** for developers.'`
|
|
67
|
+
- **HTML fragments**: `'<em>Welcome</em> to our site'`
|
|
68
|
+
|
|
69
|
+
The Webpage does not interpret the content — it stores it. The Control decides how to render it (text node, innerHTML, markdown parser). This separation means the content model is format-agnostic.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 12.3 Structured Content
|
|
74
|
+
|
|
75
|
+
Named strings are flat. Some pages need structured content — a blog post has a title, author, date, body, and tags. A product page has a name, price, features list, and images.
|
|
76
|
+
|
|
77
|
+
Two approaches:
|
|
78
|
+
|
|
79
|
+
### Approach A: Nested strings map
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
strings: {
|
|
83
|
+
'hero.heading': 'Build Faster',
|
|
84
|
+
'hero.subheading': 'Tools for modern developers',
|
|
85
|
+
'features.0.title': 'Speed',
|
|
86
|
+
'features.0.body': 'Compile in seconds',
|
|
87
|
+
'features.1.title': 'Safety',
|
|
88
|
+
'features.1.body': 'Type-checked by default'
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Flat but uses dot-notation keys. Easy to serialize and translate, but awkward to iterate over feature lists.
|
|
93
|
+
|
|
94
|
+
### Approach B: Content object
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
content: {
|
|
98
|
+
hero: {
|
|
99
|
+
heading: 'Build Faster',
|
|
100
|
+
subheading: 'Tools for modern developers'
|
|
101
|
+
},
|
|
102
|
+
features: [
|
|
103
|
+
{ title: 'Speed', body: 'Compile in seconds' },
|
|
104
|
+
{ title: 'Safety', body: 'Type-checked by default' }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Richer structure, maps naturally to component trees. But translation tools need to walk nested objects.
|
|
110
|
+
|
|
111
|
+
### Recommendation
|
|
112
|
+
|
|
113
|
+
Support **both**. The Webpage stores a `content` object that can be arbitrarily nested. A convenience `get_string(dotted.path)` method provides flat access when needed:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
page.content = { hero: { heading: 'Build Faster' } };
|
|
117
|
+
page.get_string('hero.heading'); // → 'Build Faster'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This gives structured data for complex pages while keeping simple pages simple.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 12.4 Internationalization (i18n)
|
|
125
|
+
|
|
126
|
+
### The core model
|
|
127
|
+
|
|
128
|
+
Each content value can have translations keyed by locale:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
const page = new Webpage({
|
|
132
|
+
path: '/about',
|
|
133
|
+
title: { en: 'About Us', fr: 'À propos', de: 'Über uns' },
|
|
134
|
+
ctrl: AboutCtrl,
|
|
135
|
+
content: {
|
|
136
|
+
heading: { en: 'About Our Company', fr: 'À propos de notre entreprise' },
|
|
137
|
+
body: { en: 'We build tools...', fr: 'Nous construisons des outils...' }
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Locale detection
|
|
143
|
+
|
|
144
|
+
How does the Webpage know which locale to use? It doesn't — that's a server/runtime concern. The Webpage stores all translations; the caller picks one:
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
// At render time, the server resolves the locale
|
|
148
|
+
const locale = req.acceptsLanguages(['en', 'fr']) || 'en';
|
|
149
|
+
const heading = page.get_string('heading', locale);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Fallback chains
|
|
153
|
+
|
|
154
|
+
Locales fall back through a chain:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
'en-GB' → 'en' → default (first available)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The method `get_string(key, locale)` implements this:
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
get_string(key, locale) {
|
|
164
|
+
const value = this._resolve_dotted(this.content, key);
|
|
165
|
+
if (value == null) return undefined;
|
|
166
|
+
|
|
167
|
+
// Plain string (no translations)
|
|
168
|
+
if (typeof value === 'string') return value;
|
|
169
|
+
|
|
170
|
+
// Translated object
|
|
171
|
+
if (typeof value === 'object') {
|
|
172
|
+
// Try exact match
|
|
173
|
+
if (value[locale]) return value[locale];
|
|
174
|
+
|
|
175
|
+
// Try language-only (en-GB → en)
|
|
176
|
+
const lang = locale && locale.split('-')[0];
|
|
177
|
+
if (lang && value[lang]) return value[lang];
|
|
178
|
+
|
|
179
|
+
// Fallback to first available
|
|
180
|
+
const keys = Object.keys(value);
|
|
181
|
+
return keys.length > 0 ? value[keys[0]] : undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return String(value);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Mixed content (some translated, some not)
|
|
189
|
+
|
|
190
|
+
A page can mix translated and untranslated strings:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
content: {
|
|
194
|
+
heading: { en: 'About', fr: 'À propos' }, // translated
|
|
195
|
+
copyright: '© 2026 JSGUI' // not translated
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`get_string('copyright', 'fr')` returns `'© 2026 JSGUI'` — if the value is a plain string, locale is ignored.
|
|
200
|
+
|
|
201
|
+
### Available locales
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
get locales() {
|
|
205
|
+
// Walk content tree, collect all locale keys from translated values
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This lets admin UIs show which languages a page supports and identify missing translations.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 12.5 Content vs. Ctrl — Separation of Concerns
|
|
214
|
+
|
|
215
|
+
The Webpage now has two distinct properties:
|
|
216
|
+
|
|
217
|
+
| Property | Type | Role |
|
|
218
|
+
|----------|------|------|
|
|
219
|
+
| `ctrl` | Function (constructor) | **How** the page looks (layout, styling, interaction) |
|
|
220
|
+
| `content` | Object | **What** the page says (text, data, translations) |
|
|
221
|
+
|
|
222
|
+
The Control receives content at render time:
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
// Server-side rendering
|
|
226
|
+
const instance = new page.ctrl({
|
|
227
|
+
context,
|
|
228
|
+
content: page.resolve_content(locale)
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The Control accesses strings through whatever mechanism it prefers:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
class AboutCtrl extends Control {
|
|
236
|
+
compose() {
|
|
237
|
+
const spec = this.spec || {};
|
|
238
|
+
const content = spec.content || {};
|
|
239
|
+
|
|
240
|
+
const h1 = new Control({ context: this.context, tagName: 'h1' });
|
|
241
|
+
h1.text = content.heading || 'About';
|
|
242
|
+
this.add(h1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### What `resolve_content(locale)` returns
|
|
248
|
+
|
|
249
|
+
A flat or nested object with all strings resolved to the requested locale:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
page.resolve_content('fr');
|
|
253
|
+
// → { heading: 'À propos de notre entreprise', body: 'Nous construisons...' }
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
This means the Control never sees locale keys — it gets a plain content object. The translation layer is transparent.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 12.6 Title as Content
|
|
261
|
+
|
|
262
|
+
`title` is a special string — it appears in the `<title>` tag, in navigation, in admin UIs. Under the content model, it should support translation:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
const page = new Webpage({
|
|
266
|
+
path: '/about',
|
|
267
|
+
title: { en: 'About Us', fr: 'À propos' }
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// get_title(locale) resolves like any other string
|
|
271
|
+
page.get_title('en'); // → 'About Us'
|
|
272
|
+
page.get_title('fr'); // → 'À propos'
|
|
273
|
+
page.get_title(); // → first available
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
For backward compatibility, `title` can also be a plain string:
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
const page = new Webpage({ path: '/', title: 'Home' });
|
|
280
|
+
page.get_title('fr'); // → 'Home' (no translation, returns as-is)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 12.7 Content Sources
|
|
286
|
+
|
|
287
|
+
Where does content come from? The Webpage doesn't care — it stores whatever it's given. But common patterns include:
|
|
288
|
+
|
|
289
|
+
### Inline (hardcoded)
|
|
290
|
+
|
|
291
|
+
```js
|
|
292
|
+
new Webpage({
|
|
293
|
+
path: '/',
|
|
294
|
+
content: { heading: 'Welcome' }
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### From JSON files
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
const content = require('./content/about.json');
|
|
302
|
+
new Webpage({ path: '/about', content });
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### From a CMS or API (at build time)
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
const content = await cms.getPage('about');
|
|
309
|
+
new Webpage({ path: '/about', content });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### From a CMS or API (at serve time)
|
|
313
|
+
|
|
314
|
+
The server can populate content lazily:
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
const page = new Webpage({ path: '/about', ctrl: AboutCtrl });
|
|
318
|
+
// Content loaded on demand per request
|
|
319
|
+
server.on('before-render', async (page, req) => {
|
|
320
|
+
page.content = await cms.getPage('about', req.locale);
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The Webpage supports all of these because it just holds data. The loading strategy is the application's concern.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## 12.8 Content and Validation
|
|
329
|
+
|
|
330
|
+
How does two-stage validation (Chapter 11) apply to content?
|
|
331
|
+
|
|
332
|
+
### Construction time
|
|
333
|
+
|
|
334
|
+
- `content` can be `undefined` (set later)
|
|
335
|
+
- If present, must be an object (not a string, number, or array)
|
|
336
|
+
- No requirement for specific keys
|
|
337
|
+
|
|
338
|
+
### Finalize time
|
|
339
|
+
|
|
340
|
+
- Content is **not required** — a page can be purely dynamic (Control renders everything itself)
|
|
341
|
+
- If content includes translation objects, warn about inconsistent locale coverage (some keys have `fr`, others don't)
|
|
342
|
+
- Title follows the same rules: can be string or translation object
|
|
343
|
+
|
|
344
|
+
This means content is always optional. Some pages are data-driven (need content), others are interactive (Control-only). Both are valid.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## 12.9 Serialization
|
|
349
|
+
|
|
350
|
+
`toJSON()` includes content for admin introspection:
|
|
351
|
+
|
|
352
|
+
```js
|
|
353
|
+
page.toJSON();
|
|
354
|
+
// {
|
|
355
|
+
// path: '/about',
|
|
356
|
+
// title: { en: 'About Us', fr: 'À propos' },
|
|
357
|
+
// has_content: true,
|
|
358
|
+
// content_keys: ['heading', 'body', 'cta'],
|
|
359
|
+
// locales: ['en', 'fr'],
|
|
360
|
+
// is_dynamic: true,
|
|
361
|
+
// ...
|
|
362
|
+
// }
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Note: `toJSON()` includes `content_keys` (what strings exist) and `locales` (what languages), but **not the content values themselves**. Full content is available via `page.content`. This keeps admin summaries lightweight while preserving access to the full data when needed.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## 12.10 Design Decisions Summary
|
|
370
|
+
|
|
371
|
+
| Decision | Choice | Rationale |
|
|
372
|
+
|----------|--------|-----------|
|
|
373
|
+
| Content storage | Arbitrarily nested object | Supports both flat strings and structured data |
|
|
374
|
+
| Translation format | Values are either strings or `{locale: string}` objects | Minimal overhead, no wrapper classes |
|
|
375
|
+
| Locale resolution | Fallback chain with `get_string(key, locale)` | Predictable, simple to implement |
|
|
376
|
+
| Content required? | No — optional at both stages | Some pages are purely interactive |
|
|
377
|
+
| Ctrl receives content how? | Via `spec.content` in constructor | Standard jsgui3 pattern, no new mechanism |
|
|
378
|
+
| Title | Supports same translation format | Consistent with content model |
|
|
379
|
+
| Format interpretation | None — Webpage stores, Control interprets | Format-agnostic, no coupling to markdown/HTML |
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 12.11 Open Questions for Review
|
|
384
|
+
|
|
385
|
+
These are intentionally left open for other agents to comment on:
|
|
386
|
+
|
|
387
|
+
1. **Should content support non-string values?** (numbers, booleans, dates) — or should everything be stringified?
|
|
388
|
+
2. **Should there be a content schema?** — a way to declare what keys a page expects, so missing translations or typos can be caught
|
|
389
|
+
3. **Should `get_string` throw or return undefined for missing keys?** — silent fallback vs. fail-fast
|
|
390
|
+
4. **Should the Webpage support content inheritance?** — a base page that provides defaults, overridden by child pages
|
|
391
|
+
5. **How should images and media be handled?** — as content values (URLs) or as a separate `assets` property?
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
*Next: [Chapter 13](13-webpage-module-spec.md) defines the complete `jsgui3-webpage` module specification.*
|