@uniweb/core 0.29.2 → 0.29.4
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/package.json +1 -1
- package/src/index.js +1 -0
- package/src/route-match.js +30 -0
- package/src/services.js +20 -0
- package/src/website.js +6 -2
package/package.json
CHANGED
package/src/index.js
CHANGED
package/src/route-match.js
CHANGED
|
@@ -261,6 +261,36 @@ export function recordHandle(record) {
|
|
|
261
261
|
return record.slug
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* A record's DISPLAY TITLE — what the page about it, and a search result for it, are called.
|
|
266
|
+
*
|
|
267
|
+
* ⭐ ONE RULE, read by every producer of that title (ruled 2026-09-14 [Diego]): the
|
|
268
|
+
* parametric page (`Website._createDynamicPage`, so the SPA and a host's render), the
|
|
269
|
+
* static build's page expansion, and the record search index (`@uniweb/projections`).
|
|
270
|
+
*
|
|
271
|
+
* title → name → the record's handle (`recordHandle`: `$name`, else `slug`)
|
|
272
|
+
*
|
|
273
|
+
* ⛔ Until then a page read `title` alone while the search index read `title || name`,
|
|
274
|
+
* so a record with a `name` and no `title` — a person — titled its search result and
|
|
275
|
+
* left its page carrying the template's own title, which on the site where this was
|
|
276
|
+
* measured was the route token itself: `:slug` (2026-09-14).
|
|
277
|
+
*
|
|
278
|
+
* Only a non-empty string (or a finite number — `title: 2024` in YAML) names a record.
|
|
279
|
+
* A localized map or a rich document is structure this rule cannot render, and
|
|
280
|
+
* `String()` of one is `[object Object]`.
|
|
281
|
+
*
|
|
282
|
+
* @param {Object} record
|
|
283
|
+
* @returns {string|undefined}
|
|
284
|
+
*/
|
|
285
|
+
export function recordTitle(record) {
|
|
286
|
+
if (!record || typeof record !== 'object') return undefined
|
|
287
|
+
for (const value of [record.title, record.name, recordHandle(record)]) {
|
|
288
|
+
if (typeof value === 'string' && value.trim() !== '') return value
|
|
289
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value)
|
|
290
|
+
}
|
|
291
|
+
return undefined
|
|
292
|
+
}
|
|
293
|
+
|
|
264
294
|
/**
|
|
265
295
|
* The record field a route param is matched on — ONE MAP, read by the local match
|
|
266
296
|
* (`routeParamValue`, below) and by the records service's record question (`match`,
|
package/src/services.js
CHANGED
|
@@ -21,6 +21,18 @@ import { applyBasePath } from './base-path.js'
|
|
|
21
21
|
* does not provide, and on a static site, where no host speaks, the whole
|
|
22
22
|
* answer.
|
|
23
23
|
*
|
|
24
|
+
* ## ⛔ THE TWO TIERS ARE NOT TWO SOURCES OF ONE KIND OF THING
|
|
25
|
+
*
|
|
26
|
+
* ⭐ **A site's block is an EXPRESSION OF INTENT — a request for something. The
|
|
27
|
+
* host's is THE ANSWER.** That is the real reason step 1 wins below, and it is
|
|
28
|
+
* not a detail of precedence: an operator's declaration can be answered
|
|
29
|
+
* differently by the deployment that serves it, and is routinely settled later
|
|
30
|
+
* than the moment it was written.
|
|
31
|
+
*
|
|
32
|
+
* ⇒ **So never read a site's declaration as what the site HAS**, and never
|
|
33
|
+
* report a resolved address back as what the operator asked for. The two are
|
|
34
|
+
* different kinds of statement that happen to share a spelling.
|
|
35
|
+
*
|
|
24
36
|
* Absent from both means the site has no such service, and the caller acts on
|
|
25
37
|
* that rather than guessing an address. That is the same rule for every service,
|
|
26
38
|
* and it is why this module exists: it was previously implemented three times —
|
|
@@ -80,6 +92,14 @@ import { applyBasePath } from './base-path.js'
|
|
|
80
92
|
* and a framework that encodes which capabilities cost money ships the business
|
|
81
93
|
* model into open source.
|
|
82
94
|
*
|
|
95
|
+
* ⛔ **AND IT COVERS A FIELD'S JUSTIFICATION, NOT ONLY ITS NAME.** A field naming
|
|
96
|
+
* no tier still breaks this if the reason it exists is to help someone manage
|
|
97
|
+
* what a host charges for: the commercial surface has then been designed around,
|
|
98
|
+
* which is the same coupling wearing a neutral spelling. ⇒ **Ask what a field is
|
|
99
|
+
* FOR, not only what it says** — a neutral spelling with a commercial purpose
|
|
100
|
+
* passes every check that reads only the name. (One such field was proposed and
|
|
101
|
+
* rejected on this ground, 2026-09-15.)
|
|
102
|
+
*
|
|
83
103
|
* ⛔ **There is deliberately no explanatory string, and there was one — it was
|
|
84
104
|
* a mistake.** Until 2026-08-13 a declining host could supply a `reason` that
|
|
85
105
|
* this module relayed "to the UI verbatim", with an English default when nothing
|
package/src/website.js
CHANGED
|
@@ -11,7 +11,7 @@ import FetcherDispatcher from './fetcher-dispatcher.js'
|
|
|
11
11
|
import ObservableState from './observable-state.js'
|
|
12
12
|
import { normalizeSeo } from './seo.js'
|
|
13
13
|
import { resolveDefaultLocale, localeLabel } from './locale-config.js'
|
|
14
|
-
import { matchDynamicRoute, decodeRouteValue, matchesRouteParam, routeBinding, routeParamName, parentRouteOf } from './route-match.js'
|
|
14
|
+
import { matchDynamicRoute, decodeRouteValue, matchesRouteParam, recordTitle, routeBinding, routeParamName, parentRouteOf } from './route-match.js'
|
|
15
15
|
import { resolveFetchConfigs, pageRouteQuery, recordPages, routeSelection, siteReaches } from './fetch-config.js'
|
|
16
16
|
import { declaredKeys } from './data-keys.js'
|
|
17
17
|
import { buildDetailConfig } from './detail-url.js'
|
|
@@ -679,7 +679,11 @@ export default class Website {
|
|
|
679
679
|
}
|
|
680
680
|
|
|
681
681
|
if (currentItem) {
|
|
682
|
-
|
|
682
|
+
// ⭐ The record names the page by the one rule its search result is named by
|
|
683
|
+
// too (`recordTitle`: `title`, `name`, then its handle). ⛔ Until 2026-09-14 this
|
|
684
|
+
// read `title` alone, so a person record kept the template's title — `:slug`.
|
|
685
|
+
const title = recordTitle(currentItem)
|
|
686
|
+
if (title) pageData.title = title
|
|
683
687
|
if (currentItem.description || currentItem.excerpt) {
|
|
684
688
|
pageData.description = currentItem.description || currentItem.excerpt
|
|
685
689
|
}
|