@uniweb/core 0.29.0 → 0.29.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.29.0",
3
+ "version": "0.29.2",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
@@ -46,8 +46,8 @@
46
46
  "vitest": "^4.1.7"
47
47
  },
48
48
  "dependencies": {
49
- "@uniweb/theming": "^0.1.15",
50
- "@uniweb/semantic-parser": "^1.4.0"
49
+ "@uniweb/semantic-parser": "^1.4.0",
50
+ "@uniweb/theming": "^0.1.15"
51
51
  },
52
52
  "scripts": {
53
53
  "test": "vitest run"
package/src/block.js CHANGED
@@ -104,10 +104,16 @@ export default class Block {
104
104
 
105
105
  // Merge fetched data from prerender (if present)
106
106
  // Prerender stores fetched data in blockData.parsedContent.data
107
+ //
108
+ // ⭐ THE SECTION'S OWN DATA WINS, as it does when nothing was prerendered: a tagged
109
+ // data block fills its key first, and a fetch fills only what is left. ⛔ The
110
+ // prerendered answer was spread LAST until 2026-09-14, so a key held by both a
111
+ // tagged block and the section's own fetch showed the fetch's records on a
112
+ // prerendered page and the tagged block in the browser.
107
113
  if (blockData.parsedContent?.data) {
108
114
  this.parsedContent.data = {
109
- ...(this.parsedContent.data || {}),
110
115
  ...blockData.parsedContent.data,
116
+ ...(this.parsedContent.data || {}),
111
117
  }
112
118
  }
113
119
 
package/src/data-paths.js CHANGED
@@ -13,7 +13,8 @@
13
13
  * (`data-fetcher.js`), core injected the per-record default
14
14
  * (`fetch-config.js applyDeferredDetail`), core gated locale-prefixing on it
15
15
  * (`fetch-config.js localizeConfig`), the dev server matched it with a regex
16
- * (`build/src/site/plugin.js`), and kit's `useEntityDetail` requested it.
16
+ * (`build/src/site/plugin.js`), and kit's `useEntityDetail` — `useWholeRecord`
17
+ * since 2026-09-14 — requested it.
17
18
  *
18
19
  * They drifted. `useEntityDetail` was edited to `/_data/` on its own and
19
20
  * nothing else followed, so a public documented hook requested a URL that
@@ -73,12 +74,11 @@ export function queryDataUrl(name) {
73
74
  * URL of one record's full payload — every field, including deferred ones.
74
75
  * Emitted per record only when the query declares `deferred:`.
75
76
  *
76
- * Takes either a concrete slug (kit's `useEntityDetail`, which holds a
77
- * record) or the literal placeholder `{slug}` (core's `applyDeferredDetail`,
78
- * which builds a pattern that `substitutePlaceholders` resolves later
79
- * against the dynamic-route param). Both are plain interpolation; this
80
- * function does not encode, matching the behavior of the call sites it
81
- * replaced.
77
+ * Takes either a concrete slug — a caller holding the record — or the literal
78
+ * placeholder `{slug}` (core's `applyDeferredDetail`, which builds a pattern
79
+ * that `substitutePlaceholders` resolves later against the dynamic-route
80
+ * param). Both are plain interpolation; this function does not encode,
81
+ * matching the behavior of the call sites it replaced.
82
82
  *
83
83
  * @param {string} query - The query name.
84
84
  * @param {string} slug - A record slug, or a `{param}` placeholder.
package/src/detail-url.js CHANGED
@@ -45,7 +45,7 @@ function paramContext(paramName, paramValue, record) {
45
45
  // detail URL was `/data/articles/{slug}.json`, a guaranteed 404, on every
46
46
  // template page with `deferred:` fields (measured 2026-09-04). When the caller
47
47
  // holds the record — the entity store does (it matched it), and so does
48
- // `useEntityDetail` — its slug fills the name the FILE was written under. A
48
+ // kit's `useWholeRecord` — its slug fills the name the FILE was written under. A
49
49
  // caller with no record in hand leaves `{slug}` literal rather than guessing
50
50
  // the capture is one: a visibly unresolved address beats a plausible wrong one.
51
51
  const handle = recordHandle(record)
@@ -144,7 +144,7 @@ export default class EntityStore {
144
144
 
145
145
  /**
146
146
  * The ROUTE QUERY of the block's page — the query its URL names one record of —
147
- * worked out from the same sources `_findFetchConfigs` walks, at the page that
147
+ * worked out from the same sources `_levels` reads, at the page that
148
148
  * captured the URL's variable (`pageRouteQuery`, `./fetch-config.js`): its fetch,
149
149
  * its parent's, the site's, and, when none of those declares one, the key its
150
150
  * sections share. So what a section receives and what the URL narrows are read
@@ -195,8 +195,12 @@ function localizeConfig(cfg, locale, defaultLocale) {
195
195
  * by the URL's value (`buildDetailConfig`);
196
196
  * - an external query declaring `record:` → `true`: its own request;
197
197
  * - a query declaring `deferred:` → `/data/<query>/{slug}.json`, the per-record
198
- * file the build emits beside the lean list. Per-record files are keyed by the
199
- * record's slug, and are not localized.
198
+ * file the build emits beside the lean list, keyed by the record's slug. ⭐ On a
199
+ * locale other than the default it is `/<locale>/data/<query>/{slug}.json`, the
200
+ * way the list's own path is (`localizeConfig`): a localized build writes a
201
+ * translated copy of every per-record file beside the translated list. ⛔ Until
202
+ * 2026-09-14 the pattern was never localized, so a translated record page asked
203
+ * for the default locale's file and showed an untranslated body.
200
204
  *
201
205
  * ⛔ `detail:` is no longer AUTHORED (retired 2026-09-13 [Diego]; the build refuses
202
206
  * it): its `rest`, `query`, pattern and `{ body, envelope }` forms are an external
@@ -208,9 +212,11 @@ function localizeConfig(cfg, locale, defaultLocale) {
208
212
  *
209
213
  * @param {Object} cfg
210
214
  * @param {Object|null} queries - the site's `config.queries` map
215
+ * @param {string|null} [locale] - the page's locale
216
+ * @param {string|null} [defaultLocale] - the site's default locale
211
217
  * @returns {Object} the original config, or a copy carrying `detail`
212
218
  */
213
- function applyDeferredDetail(cfg, queries) {
219
+ function applyDeferredDetail(cfg, queries, locale = null, defaultLocale = null) {
214
220
  if (cfg.detail !== undefined) return cfg
215
221
 
216
222
  // The records service answers a RECORD by the query's set narrowed to it, so
@@ -239,7 +245,9 @@ function applyDeferredDetail(cfg, queries) {
239
245
  if (!collConfig || typeof collConfig !== 'object') return cfg
240
246
  const deferred = Array.isArray(collConfig.deferred) ? collConfig.deferred : null
241
247
  if (!deferred || deferred.length === 0) return cfg
242
- return { ...cfg, detail: recordDataUrl(queryName, '{slug}') }
248
+ const pattern = recordDataUrl(queryName, '{slug}')
249
+ const localized = locale && locale !== defaultLocale && isDataUrl(pattern) ? `/${locale}${pattern}` : pattern
250
+ return { ...cfg, detail: localized }
243
251
  }
244
252
 
245
253
  /**
@@ -549,7 +557,7 @@ export function resolveFetchConfigs(sources, options = {}) {
549
557
  const sourced = resolveQuerySource(cfg, services, { queries, locale, defaultLocale })
550
558
  const localized = localizeConfig(sourced, locale, defaultLocale)
551
559
  const bound = dropRootScope(bindRouteVariables(localized, variables))
552
- configs.set(key, stampDepthAndLocale(applyDeferredDetail(bound, queries), locale, defaultLocale))
560
+ configs.set(key, stampDepthAndLocale(applyDeferredDetail(bound, queries, locale, defaultLocale), locale, defaultLocale))
553
561
  }
554
562
  }
555
563
 
package/src/website.js CHANGED
@@ -908,7 +908,7 @@ export default class Website {
908
908
  *
909
909
  * This is what fills a delivered record's `$route` (`EntityStore`) when its fetch picks
910
910
  * no `detailPage`, and how a caller outside a parametric page learns which record field
911
- * the page's URL is built on: `kit`'s `useEntityDetail` asks it so a hover card and the
911
+ * the page's URL is built on: `kit`'s `useWholeRecord` asks it so a hover card and the
912
912
  * page it links to address the record by the SAME field.
913
913
  *
914
914
  * ⛔ Replaces `detailTemplateFor(name)` (2026-09-14), which matched a binding key OR a