@uniweb/core 0.37.0 → 0.37.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/core",
3
- "version": "0.37.0",
3
+ "version": "0.37.2",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
@@ -36,13 +36,16 @@
36
36
  *
37
37
  * ## ⛔ The HOST tier only — and the reason is mechanical, not a policy
38
38
  *
39
- * `resolveService` reads the site's authored tier first and the host's second.
40
- * This does not, because it cannot: its caller `resolveFetchConfigs` is a leaf
41
- * that runs in an SSR isolate over a plain content object with no `Website` to
42
- * hand. It reads `config.services` directly, so the authored tier is not
43
- * available here. ⇒ **A site does not declare its own record provider**, which
44
- * is also the standing ruling — a third-party source is a foundation transport,
45
- * not site-level config *(2026-09-04, the retired `fetcher:` vocabulary)*.
39
+ * `resolveService` reads two tiers: the host's offer, then the site's authored
40
+ * declaration for what the host does not offer. *(This said "the site's authored
41
+ * tier first" until 2026-10-06; the host's offer has come first since 2026-09-10.)*
42
+ * This reads the host's alone, because it cannot read the other: its caller
43
+ * `resolveFetchConfigs` is a leaf that runs in an SSR isolate over a plain
44
+ * content object with no `Website` to hand. It reads `config.services` directly,
45
+ * so the authored tier is not available here. ⇒ **A site does not declare its
46
+ * own record provider**, which is also the standing ruling — a third-party
47
+ * source is a foundation transport, not site-level config *(2026-09-04, the
48
+ * retired `fetcher:` vocabulary)*.
46
49
  *
47
50
  * ## ⛔ A pattern, not a base — and the reason is a deleted function
48
51
  *
@@ -297,6 +297,27 @@ export function recordTitle(record) {
297
297
  return undefined
298
298
  }
299
299
 
300
+ /**
301
+ * A record's SHARED IMAGE — what a link to the page about it shows (its `og:image`).
302
+ *
303
+ * ONE RULE, read where `recordTitle` is: the parametric page (`Website._createDynamicPage`)
304
+ * and the static build's page expansion. It is the record's `image` — the card's picture,
305
+ * which a page names itself from as it does from the card's title — and it takes the
306
+ * place of the page's own `seo.image`, which then serves a record that has none. A site's
307
+ * `seo.image` comes last (`Page.getHeadMeta`).
308
+ *
309
+ * Only a non-empty string is an image: a localized map or an object is structure this
310
+ * rule cannot write into a meta tag.
311
+ *
312
+ * @param {Object} record
313
+ * @returns {string|undefined}
314
+ */
315
+ export function recordImage(record) {
316
+ if (!record || typeof record !== 'object') return undefined
317
+ const image = record.image
318
+ return typeof image === 'string' && image.trim() !== '' ? image : undefined
319
+ }
320
+
300
321
  /**
301
322
  * The record field a route param is matched on — ONE MAP, read by the local match
302
323
  * (`routeParamValue`, below) and by the records service's record question (`match`,
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, recordTitle, routeBinding, routeParamName, parentRouteOf } from './route-match.js'
14
+ import { matchDynamicRoute, recordTitle, recordImage, routeBinding, routeParamName, parentRouteOf } from './route-match.js'
15
15
  import { resolveFetchConfigs, pageRouteQuery, recordPages } from './fetch-config.js'
16
16
  import { declaredKeys } from './data-keys.js'
17
17
  import { pageRecordConfig, planFor, runPlanSync } from './page-data.js'
@@ -636,6 +636,11 @@ export default class Website {
636
636
  if (currentItem.description || currentItem.excerpt) {
637
637
  pageData.description = currentItem.description || currentItem.excerpt
638
638
  }
639
+ // ⭐ And by its picture: a link to the page shows the record's image (`recordImage`).
640
+ // ⛔ Until 2026-10-05 it showed the page's or the site's `seo.image`, the same for
641
+ // every record.
642
+ const image = recordImage(currentItem)
643
+ if (image) pageData.seo = { ...(pageData.seo || {}), image }
639
644
  } else if (answered) {
640
645
  // The cache answered and the record is not there: the set is loaded and this ID is
641
646
  // not in it, or the record question answered `[]` — definitive not found.