@uniweb/core 0.7.32 → 0.7.34
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 +4 -3
- package/src/entity-store.js +27 -86
- package/src/fetch-config.js +153 -0
- package/src/theme.js +41 -52
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.34",
|
|
4
4
|
"description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.js",
|
|
8
|
+
"./fetch-config": "./src/fetch-config.js",
|
|
8
9
|
"./locale-config": "./src/locale-config.js",
|
|
9
10
|
"./section-id": "./src/section-id.js"
|
|
10
11
|
},
|
|
@@ -32,8 +33,8 @@
|
|
|
32
33
|
"vitest": "^4.1.7"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
|
-
"@uniweb/theming": "0.1.
|
|
36
|
-
"@uniweb/semantic-parser": "1.1.
|
|
36
|
+
"@uniweb/theming": "0.1.15",
|
|
37
|
+
"@uniweb/semantic-parser": "1.1.21"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
40
|
"test": "vitest run"
|
package/src/entity-store.js
CHANGED
|
@@ -15,16 +15,18 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { substitutePlaceholders } from './substitute-placeholders.js'
|
|
18
|
+
import { isFetchRefinement, resolveFetchConfigs } from './fetch-config.js'
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Is `block.fetch` a per-instance refinement of the ancestor's fetch config
|
|
21
22
|
* rather than a new source? The canonical spelling is `refine: true`; the
|
|
22
23
|
* legacy spelling `inherit: true` is still honored for one release with a
|
|
23
24
|
* dev-mode warning.
|
|
25
|
+
*
|
|
26
|
+
* The predicate itself lives in `./fetch-config.js` with the rest of the
|
|
27
|
+
* cascade rule; this alias keeps the local call sites reading as they did.
|
|
24
28
|
*/
|
|
25
|
-
|
|
26
|
-
return bf?.refine === true || bf?.inherit === true
|
|
27
|
-
}
|
|
29
|
+
const isRefinement = isFetchRefinement
|
|
28
30
|
|
|
29
31
|
let inheritDeprecationWarned = false
|
|
30
32
|
function warnInheritDeprecation(block) {
|
|
@@ -95,54 +97,39 @@ export default class EntityStore {
|
|
|
95
97
|
return []
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
/**
|
|
99
|
-
* Return a localized copy of a fetch config for collection data.
|
|
100
|
-
* Non-default locales get /{locale} prefixed onto /data/ paths so the
|
|
101
|
-
* client fetches the translated JSON (/fr/data/articles.json).
|
|
102
|
-
*/
|
|
103
|
-
_localizeConfig(cfg, website) {
|
|
104
|
-
if (!cfg.path || !website) return cfg
|
|
105
|
-
const locale = website.getActiveLocale?.()
|
|
106
|
-
const defaultLocale = website.getDefaultLocale?.()
|
|
107
|
-
if (!locale || locale === defaultLocale) return cfg
|
|
108
|
-
if (!cfg.path.startsWith('/data/')) return cfg
|
|
109
|
-
return { ...cfg, path: `/${locale}${cfg.path}` }
|
|
110
|
-
}
|
|
111
|
-
|
|
112
100
|
/**
|
|
113
101
|
* Walk the four-level hierarchy and collect fetch configs for the
|
|
114
102
|
* requested schemas. First match per schema wins.
|
|
103
|
+
*
|
|
104
|
+
* This method's job is to read the four source slots off the object graph;
|
|
105
|
+
* the rule applied to them (precedence, first-match-per-schema, locale
|
|
106
|
+
* normalization, deferred-detail injection) lives in `./fetch-config.js`,
|
|
107
|
+
* shared with every other host that has to answer the same question. Do not
|
|
108
|
+
* re-inline it here — divergence between copies is what the extraction
|
|
109
|
+
* exists to prevent.
|
|
115
110
|
*/
|
|
116
111
|
_findFetchConfigs(block, requested) {
|
|
117
|
-
const configs = new Map()
|
|
118
|
-
const collectAll = requested.length === 0
|
|
119
|
-
const sources = []
|
|
120
|
-
|
|
121
112
|
if (block.fetch?.inherit === true && block.fetch?.refine !== true) {
|
|
122
113
|
warnInheritDeprecation(block)
|
|
123
114
|
}
|
|
124
|
-
if (block.fetch && !isRefinement(block.fetch)) sources.push(block.fetch)
|
|
125
|
-
const page = block.page
|
|
126
|
-
if (page?.fetch) sources.push(page.fetch)
|
|
127
|
-
if (page?.parent?.fetch) sources.push(page.parent.fetch)
|
|
128
|
-
const siteFetch = block.website?.config?.fetch
|
|
129
|
-
if (siteFetch) sources.push(siteFetch)
|
|
130
115
|
|
|
116
|
+
const page = block.page
|
|
131
117
|
const website = block.website
|
|
132
118
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
119
|
+
return resolveFetchConfigs(
|
|
120
|
+
[
|
|
121
|
+
block.fetch && !isRefinement(block.fetch) ? block.fetch : null,
|
|
122
|
+
page?.fetch,
|
|
123
|
+
page?.parent?.fetch,
|
|
124
|
+
website?.config?.fetch,
|
|
125
|
+
],
|
|
126
|
+
{
|
|
127
|
+
schemas: requested,
|
|
128
|
+
locale: website?.getActiveLocale?.() ?? null,
|
|
129
|
+
defaultLocale: website?.getDefaultLocale?.() ?? null,
|
|
130
|
+
collections: website?.config?.collections ?? null,
|
|
131
|
+
},
|
|
132
|
+
)
|
|
146
133
|
}
|
|
147
134
|
|
|
148
135
|
/**
|
|
@@ -167,52 +154,6 @@ export default class EntityStore {
|
|
|
167
154
|
}
|
|
168
155
|
}
|
|
169
156
|
|
|
170
|
-
/**
|
|
171
|
-
* Auto-inject `detail:` on collection refs whose collection has
|
|
172
|
-
* `deferred:` declared. The detail pattern points at the per-record
|
|
173
|
-
* source so the existing dynamic-route singular flow fetches a record
|
|
174
|
-
* with all fields (including the deferred ones) instead of the
|
|
175
|
-
* matched-item-from-the-cascade-collection (without).
|
|
176
|
-
*
|
|
177
|
-
* Two patterns:
|
|
178
|
-
*
|
|
179
|
-
* - Markdown-backed collections (the build emits per-record files
|
|
180
|
-
* at `/data/<name>/<slug>.json`): the auto-injected pattern is
|
|
181
|
-
* that path. `isLocalPath` resolution downstream gives the
|
|
182
|
-
* fetch a `path:` shape.
|
|
183
|
-
*
|
|
184
|
-
* - API-backed collections (the source is a remote URL; the build
|
|
185
|
-
* emits no per-record files): the author declares a `detailUrl:`
|
|
186
|
-
* on the collection — e.g., `/api/articles/{slug}` — and the
|
|
187
|
-
* auto-injected pattern uses it. `isLocalPath` resolution
|
|
188
|
-
* downstream gives the fetch a `url:` shape (because the
|
|
189
|
-
* collection itself has `url:`, not `path:`).
|
|
190
|
-
*
|
|
191
|
-
* Conventions:
|
|
192
|
-
* - Per-record sources are keyed by `item.slug`. The injected
|
|
193
|
-
* pattern uses the `{slug}` placeholder; substitution works when
|
|
194
|
-
* the dynamic route's paramName is 'slug' (the documented
|
|
195
|
-
* convention). Routes with other param names need an explicit
|
|
196
|
-
* author-written `detail:` value.
|
|
197
|
-
* - Author-supplied `cfg.detail` always wins. This helper only fills
|
|
198
|
-
* in the default for collections that have declared deferred fields.
|
|
199
|
-
* - Per-record files are not currently localized; sites needing
|
|
200
|
-
* localized deferred collections write their own `detail:` URL.
|
|
201
|
-
*/
|
|
202
|
-
_applyDeferredDetail(cfg, website) {
|
|
203
|
-
if (cfg.detail !== undefined) return cfg
|
|
204
|
-
const schema = cfg.schema
|
|
205
|
-
if (!schema) return cfg
|
|
206
|
-
const collConfig = website?.config?.collections?.[schema]
|
|
207
|
-
if (!collConfig || typeof collConfig !== 'object') return cfg
|
|
208
|
-
const deferred = Array.isArray(collConfig.deferred) ? collConfig.deferred : null
|
|
209
|
-
if (!deferred || deferred.length === 0) return cfg
|
|
210
|
-
const pattern = typeof collConfig.detailUrl === 'string'
|
|
211
|
-
? collConfig.detailUrl
|
|
212
|
-
: `/data/${schema}/{slug}.json`
|
|
213
|
-
return { ...cfg, detail: pattern }
|
|
214
|
-
}
|
|
215
|
-
|
|
216
157
|
/**
|
|
217
158
|
* Build a detail-URL fetch config from a collection config + dynamic context.
|
|
218
159
|
*
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch-config resolution — the shared rule, in one place.
|
|
3
|
+
*
|
|
4
|
+
* "Which fetch configs apply here?" is a framework concept. Authors declare
|
|
5
|
+
* `fetch:` at the section, page, folder and site levels; the framework decides
|
|
6
|
+
* which declaration wins per schema, how a local data path is localized, and
|
|
7
|
+
* when a collection with deferred fields gets a detail pattern injected.
|
|
8
|
+
*
|
|
9
|
+
* Every host that renders a page needs that answer — the browser runtime, the
|
|
10
|
+
* build-time prerenderer, and any server-side renderer. The rule had grown
|
|
11
|
+
* more than one implementation, and they had diverged in both directions (each
|
|
12
|
+
* carrying a level or a semantic the other lacked). This module is the single
|
|
13
|
+
* definition they call.
|
|
14
|
+
*
|
|
15
|
+
* INTENTIONALLY A LEAF: no imports, so it is safe to load anywhere — including
|
|
16
|
+
* environments with no DOM, no filesystem, and a hard bundle-size ceiling.
|
|
17
|
+
* Importing anything here (or from the package root) would defeat that. Keep
|
|
18
|
+
* it dependency-free.
|
|
19
|
+
*
|
|
20
|
+
* WHAT THIS DOES NOT OWN: where the sources come from. A caller holding a live
|
|
21
|
+
* object graph reads them off the graph; a caller holding a content document
|
|
22
|
+
* reads them off the JSON. Both hand the same ordered array to
|
|
23
|
+
* `resolveFetchConfigs`. That difference is real and stays with the caller.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Is this fetch declaration a per-instance *refinement* of an ancestor's
|
|
28
|
+
* config rather than a new source of its own?
|
|
29
|
+
*
|
|
30
|
+
* The canonical spelling is `refine: true`. The legacy spelling `inherit: true`
|
|
31
|
+
* is still honored; callers that want to warn about it should test for the key
|
|
32
|
+
* themselves — this predicate stays silent so it is safe in any environment.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} cfg - a fetch declaration
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
export function isFetchRefinement(cfg) {
|
|
38
|
+
return cfg?.refine === true || cfg?.inherit === true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Localize a fetch config that reads a local data path.
|
|
43
|
+
*
|
|
44
|
+
* Non-default locales get `/{locale}` prefixed onto `/data/` paths so the
|
|
45
|
+
* caller reads the translated JSON (`/fr/data/articles.json`). Configs with no
|
|
46
|
+
* `path` (remote `url:` sources), or paths outside `/data/`, pass through
|
|
47
|
+
* untouched — a remote endpoint's localization is the author's business.
|
|
48
|
+
*
|
|
49
|
+
* @param {Object} cfg
|
|
50
|
+
* @param {string|null} locale - the locale being rendered
|
|
51
|
+
* @param {string|null} defaultLocale - the site's default locale
|
|
52
|
+
* @returns {Object} the original config, or a localized copy
|
|
53
|
+
*/
|
|
54
|
+
function localizeConfig(cfg, locale, defaultLocale) {
|
|
55
|
+
if (!cfg.path) return cfg
|
|
56
|
+
if (!locale || locale === defaultLocale) return cfg
|
|
57
|
+
if (!cfg.path.startsWith('/data/')) return cfg
|
|
58
|
+
return { ...cfg, path: `/${locale}${cfg.path}` }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Auto-inject `detail:` on a collection ref whose collection declares
|
|
63
|
+
* `deferred:` fields.
|
|
64
|
+
*
|
|
65
|
+
* A deferred collection ships a lean list payload, so the full record has to
|
|
66
|
+
* come from somewhere else. Two patterns, picked by what the collection
|
|
67
|
+
* declares:
|
|
68
|
+
*
|
|
69
|
+
* - the collection has `detailUrl:` → use it verbatim (a remote source);
|
|
70
|
+
* - otherwise → `/data/<schema>/{slug}.json`, the per-record file emitted
|
|
71
|
+
* alongside the lean list.
|
|
72
|
+
*
|
|
73
|
+
* Conventions carried from the original implementation:
|
|
74
|
+
* - Per-record sources are keyed by `item.slug`, and the injected pattern
|
|
75
|
+
* uses the `{slug}` placeholder. Substitution works when the dynamic
|
|
76
|
+
* route's paramName is `slug` (the documented convention); a route using
|
|
77
|
+
* another param name needs an explicit author-written `detail:`.
|
|
78
|
+
* - Per-record files are not currently localized. A site needing localized
|
|
79
|
+
* deferred collections writes its own `detail:` URL.
|
|
80
|
+
*
|
|
81
|
+
* An author-supplied `cfg.detail` always wins; this only fills the default.
|
|
82
|
+
* With no `collections` map available the config passes through untouched —
|
|
83
|
+
* deferred-detail injection is an enhancement, never a correctness
|
|
84
|
+
* requirement, so a caller that does not have collection metadata still gets
|
|
85
|
+
* a usable config. That matters for hosts whose content projection may not
|
|
86
|
+
* carry collection metadata at all.
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} cfg
|
|
89
|
+
* @param {Object|null} collections - the site's `config.collections` map
|
|
90
|
+
* @returns {Object} the original config, or a copy carrying `detail`
|
|
91
|
+
*/
|
|
92
|
+
function applyDeferredDetail(cfg, collections) {
|
|
93
|
+
if (cfg.detail !== undefined) return cfg
|
|
94
|
+
const schema = cfg.schema
|
|
95
|
+
if (!schema || !collections) return cfg
|
|
96
|
+
const collConfig = collections[schema]
|
|
97
|
+
if (!collConfig || typeof collConfig !== 'object') return cfg
|
|
98
|
+
const deferred = Array.isArray(collConfig.deferred) ? collConfig.deferred : null
|
|
99
|
+
if (!deferred || deferred.length === 0) return cfg
|
|
100
|
+
const pattern = typeof collConfig.detailUrl === 'string'
|
|
101
|
+
? collConfig.detailUrl
|
|
102
|
+
: `/data/${schema}/{slug}.json`
|
|
103
|
+
return { ...cfg, detail: pattern }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolve the applicable fetch configs from an ordered list of sources.
|
|
108
|
+
*
|
|
109
|
+
* The rule: walk the sources in precedence order and take the FIRST match per
|
|
110
|
+
* schema. Sources are the framework's cascade, most specific first — typically
|
|
111
|
+
* section → page → parent page → site. A source may be a single config or an
|
|
112
|
+
* array of them; arrays are walked in order.
|
|
113
|
+
*
|
|
114
|
+
* First-match-per-schema (rather than first-match-wins-outright) is what lets
|
|
115
|
+
* a page needing two schemas inherit one from the site and declare the other
|
|
116
|
+
* itself. Collapsing that to a single winner is a real behavior change, not a
|
|
117
|
+
* simplification.
|
|
118
|
+
*
|
|
119
|
+
* @param {Array<Object|Array<Object>>} sources - ordered, most specific first.
|
|
120
|
+
* Falsy entries are skipped, so callers can pass optional levels directly.
|
|
121
|
+
* @param {Object} [options]
|
|
122
|
+
* @param {string[]} [options.schemas] - restrict to these schema names.
|
|
123
|
+
* Empty (the default) collects every schema found.
|
|
124
|
+
* @param {string|null} [options.locale] - the locale being rendered
|
|
125
|
+
* @param {string|null} [options.defaultLocale] - the site's default locale
|
|
126
|
+
* @param {Object|null} [options.collections] - the site's `config.collections`
|
|
127
|
+
* @returns {Map<string, Object>} schema name → resolved config
|
|
128
|
+
*/
|
|
129
|
+
export function resolveFetchConfigs(sources, options = {}) {
|
|
130
|
+
const {
|
|
131
|
+
schemas = [],
|
|
132
|
+
locale = null,
|
|
133
|
+
defaultLocale = null,
|
|
134
|
+
collections = null,
|
|
135
|
+
} = options
|
|
136
|
+
|
|
137
|
+
const configs = new Map()
|
|
138
|
+
const collectAll = schemas.length === 0
|
|
139
|
+
|
|
140
|
+
for (const source of sources) {
|
|
141
|
+
if (!source) continue
|
|
142
|
+
const configList = Array.isArray(source) ? source : [source]
|
|
143
|
+
for (const cfg of configList) {
|
|
144
|
+
if (!cfg?.schema) continue
|
|
145
|
+
if (configs.has(cfg.schema)) continue
|
|
146
|
+
if (!collectAll && !schemas.includes(cfg.schema)) continue
|
|
147
|
+
const localized = localizeConfig(cfg, locale, defaultLocale)
|
|
148
|
+
configs.set(cfg.schema, applyDeferredDetail(localized, collections))
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return configs
|
|
153
|
+
}
|
package/src/theme.js
CHANGED
|
@@ -13,28 +13,6 @@ const SHADE_LEVELS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
|
|
|
13
13
|
// Valid color contexts
|
|
14
14
|
const VALID_CONTEXTS = ['light', 'medium', 'dark']
|
|
15
15
|
|
|
16
|
-
// Default semantic tokens by context
|
|
17
|
-
const DEFAULT_CONTEXT_TOKENS = {
|
|
18
|
-
light: {
|
|
19
|
-
bg: 'var(--neutral-50)',
|
|
20
|
-
text: 'var(--neutral-950)',
|
|
21
|
-
heading: 'var(--neutral-900)',
|
|
22
|
-
link: 'var(--primary-600)',
|
|
23
|
-
},
|
|
24
|
-
medium: {
|
|
25
|
-
bg: 'var(--neutral-100)',
|
|
26
|
-
text: 'var(--neutral-950)',
|
|
27
|
-
heading: 'var(--neutral-900)',
|
|
28
|
-
link: 'var(--primary-600)',
|
|
29
|
-
},
|
|
30
|
-
dark: {
|
|
31
|
-
bg: 'var(--neutral-900)',
|
|
32
|
-
text: 'var(--neutral-50)',
|
|
33
|
-
heading: 'white',
|
|
34
|
-
link: 'var(--primary-400)',
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
|
|
38
16
|
/**
|
|
39
17
|
* Theme class for runtime theme access
|
|
40
18
|
*/
|
|
@@ -154,40 +132,42 @@ export default class Theme {
|
|
|
154
132
|
// Context Access
|
|
155
133
|
// ============================================================
|
|
156
134
|
|
|
157
|
-
|
|
158
|
-
*
|
|
135
|
+
/*
|
|
136
|
+
* REMOVED 2026-07-28: getContextToken(context, token) and
|
|
137
|
+
* getContextTokens(context), plus the DEFAULT_CONTEXT_TOKENS table backing
|
|
138
|
+
* them. Read this before reintroducing either.
|
|
159
139
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
140
|
+
* They were unreachable-by-design rather than merely stale. A lookup keyed on
|
|
141
|
+
* (context, token) answers "what does the .context-<name> class set by
|
|
142
|
+
* default?", but the question callers ask is "what colour is --heading HERE",
|
|
143
|
+
* and those diverge for two reasons no such lookup can see:
|
|
163
144
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*/
|
|
168
|
-
getContextToken(context, token) {
|
|
169
|
-
// Check custom context tokens first
|
|
170
|
-
const customContext = this._contexts[context]
|
|
171
|
-
if (customContext && customContext[token]) {
|
|
172
|
-
return customContext[token]
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Fall back to defaults
|
|
176
|
-
const defaults = DEFAULT_CONTEXT_TOKENS[context]
|
|
177
|
-
return defaults?.[token] || null
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Get all tokens for a context
|
|
145
|
+
* - a section's own `theme:` overrides, emitted as `#section-{id} { … }` by
|
|
146
|
+
* @uniweb/theming's buildSectionOverrides;
|
|
147
|
+
* - the active site scheme, where `.scheme-dark` redefines the root tokens.
|
|
182
148
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
149
|
+
* So a correct-looking answer would still be wrong whenever a section
|
|
150
|
+
* overrides tokens or the visitor is in dark mode — the failure being a
|
|
151
|
+
* confident wrong value, not an error. (The table had also drifted: it held
|
|
152
|
+
* `bg`/`text`, retired in favour of `section`/`body`, and four tokens where
|
|
153
|
+
* the live set is ~25. Repopulating it would have made a misleading API look
|
|
154
|
+
* trustworthy, which is worse than leaving it visibly incomplete.)
|
|
155
|
+
*
|
|
156
|
+
* What to use instead:
|
|
157
|
+
* - an actually-resolved value (canvas, SVG, a chart matching the theme):
|
|
158
|
+
* getComputedStyle(el).getPropertyValue('--heading') — this accounts for
|
|
159
|
+
* both section overrides and the active scheme, which no static table can;
|
|
160
|
+
* - the defaults table itself (tooling, a theme editor):
|
|
161
|
+
* getDefaultContextTokens() from @uniweb/theming, which owns it;
|
|
162
|
+
* - ordinary component styling: the CSS variables directly. Reading tokens
|
|
163
|
+
* into JS to branch on them is the `isDark ? … : …` pattern semantic
|
|
164
|
+
* tokens exist to remove.
|
|
165
|
+
*
|
|
166
|
+
* SHADE_LEVELS below is duplicated from @uniweb/theming too, and was left
|
|
167
|
+
* deliberately: both copies are identical and the 11-step scale is fixed by
|
|
168
|
+
* the design, so there is no drift to prevent — noted so the next reader
|
|
169
|
+
* knows it was considered rather than missed.
|
|
185
170
|
*/
|
|
186
|
-
getContextTokens(context) {
|
|
187
|
-
const defaults = DEFAULT_CONTEXT_TOKENS[context] || {}
|
|
188
|
-
const custom = this._contexts[context] || {}
|
|
189
|
-
return { ...defaults, ...custom }
|
|
190
|
-
}
|
|
191
171
|
|
|
192
172
|
/**
|
|
193
173
|
* Get the CSS class name for a context
|
|
@@ -423,6 +403,15 @@ export default class Theme {
|
|
|
423
403
|
* point here; everything that consumes the model (runtime boot, kit's
|
|
424
404
|
* useAppearance) imports THIS one.
|
|
425
405
|
*
|
|
406
|
+
* A warning is not a mechanism, and this one has been tested: while it stood,
|
|
407
|
+
* a SECOND shared table in this same file — DEFAULT_CONTEXT_TOKENS, duplicating
|
|
408
|
+
* @uniweb/theming's — drifted to retired token names and lost twenty entries
|
|
409
|
+
* without anyone noticing, because nothing called the two methods that read it.
|
|
410
|
+
* It was removed 2026-07-28 (see the note where it sat). Treat the lockstep
|
|
411
|
+
* above as a live obligation with a track record, not a formality: if you find
|
|
412
|
+
* yourself copying a value out of @uniweb/theming into this file, that is the
|
|
413
|
+
* moment this comment is for.
|
|
414
|
+
*
|
|
426
415
|
* @param {Object} appearance - the resolved theme.yml `appearance:` block
|
|
427
416
|
* @returns {boolean}
|
|
428
417
|
*/
|