@uniweb/core 0.7.33 → 0.8.0
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/data-paths.js +121 -0
- package/src/fetch-config.js +12 -6
- package/src/index.js +27 -4
- package/src/schemas.js +73 -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.
|
|
3
|
+
"version": "0.8.0",
|
|
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
|
+
"./data-paths": "./src/data-paths.js",
|
|
8
9
|
"./fetch-config": "./src/fetch-config.js",
|
|
9
10
|
"./locale-config": "./src/locale-config.js",
|
|
10
11
|
"./section-id": "./src/section-id.js"
|
|
@@ -33,8 +34,8 @@
|
|
|
33
34
|
"vitest": "^4.1.7"
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"@uniweb/
|
|
37
|
-
"@uniweb/
|
|
37
|
+
"@uniweb/semantic-parser": "1.2.0",
|
|
38
|
+
"@uniweb/theming": "0.1.15"
|
|
38
39
|
},
|
|
39
40
|
"scripts": {
|
|
40
41
|
"test": "vitest run"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiled-collection paths — the ONE home for the URL and directory
|
|
3
|
+
* convention that the build emits and every fetcher requests.
|
|
4
|
+
*
|
|
5
|
+
* Why this module exists. The path `/data/<name>.json` was a bare string
|
|
6
|
+
* literal in six places across three packages: the build wrote it
|
|
7
|
+
* (`collection-processor.js`), the build resolved `collection:` to it
|
|
8
|
+
* (`data-fetcher.js`), core injected the per-record default
|
|
9
|
+
* (`fetch-config.js applyDeferredDetail`), core gated locale-prefixing on it
|
|
10
|
+
* (`fetch-config.js localizeConfig`), the dev server matched it with a regex
|
|
11
|
+
* (`build/src/site/plugin.js`), and kit's `useEntityDetail` requested it.
|
|
12
|
+
*
|
|
13
|
+
* They drifted. `useEntityDetail` was edited to `/_data/` on its own and
|
|
14
|
+
* nothing else followed, so a public documented hook requested a URL that
|
|
15
|
+
* nothing anywhere emitted or served — broken on every lane, silently,
|
|
16
|
+
* because it has no call site in this workspace to fail. Emit and request
|
|
17
|
+
* had passing tests the whole time; each pinned its own literal.
|
|
18
|
+
*
|
|
19
|
+
* So the invariant is structural, not documented: producers and consumers
|
|
20
|
+
* read the same constant, and a test asserts they agree. Changing the
|
|
21
|
+
* convention is then one edit here rather than a six-site sweep with a
|
|
22
|
+
* silent-failure trap in it (see `localizeConfig` — a missed site there
|
|
23
|
+
* degrades to default-locale content with a 200, not a 404).
|
|
24
|
+
*
|
|
25
|
+
* WHY THIS PATH IS NOT `_data`. The site's other reserved paths are
|
|
26
|
+
* underscore-prefixed — `_search`, `_pages/`, `_importmap/` — and the
|
|
27
|
+
* inconsistency invites a rename. It has been proposed and declined. Those
|
|
28
|
+
* are machinery: an endpoint and bundler artifacts, which no visitor should
|
|
29
|
+
* land on and which an underscore correctly marks as internal. Compiled
|
|
30
|
+
* collection JSON is the opposite — it is the site's own content, the same
|
|
31
|
+
* records an agent that found the site through `llms.txt` may reasonably
|
|
32
|
+
* fetch directly. `/data/articles.json` is a legitimate public address, and
|
|
33
|
+
* `data` is a legitimate page route; neither collides with the other, since
|
|
34
|
+
* pages emit `.html` and collections emit `.json`. Prefixing it would say
|
|
35
|
+
* "internal" about something that is not.
|
|
36
|
+
*
|
|
37
|
+
* Zero-dependency leaf, like `./locale-config.js`, so a consumer that must
|
|
38
|
+
* not pull core's graph (semantic-parser, theming) can import the subpath
|
|
39
|
+
* `@uniweb/core/data-paths` directly.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The directory segment, for filesystem joins and regex construction.
|
|
44
|
+
* The build writes `<site>/public/<DATA_DIR>/` and copies it to
|
|
45
|
+
* `<dist>/<DATA_DIR>/`.
|
|
46
|
+
*/
|
|
47
|
+
export const DATA_DIR = 'data'
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The URL prefix every compiled-collection request carries. Note the
|
|
51
|
+
* trailing slash: `isDataUrl` is a prefix test, and without it `/database`
|
|
52
|
+
* would match.
|
|
53
|
+
*/
|
|
54
|
+
export const DATA_URL_PREFIX = `/${DATA_DIR}/`
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* URL of a collection's cascade payload — the whole collection, with
|
|
58
|
+
* `deferred:` fields stripped when the collection declares them.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} name - The collection name.
|
|
61
|
+
* @returns {string} e.g. `/data/articles.json`
|
|
62
|
+
*/
|
|
63
|
+
export function collectionDataUrl(name) {
|
|
64
|
+
return `${DATA_URL_PREFIX}${name}.json`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* URL of one record's full payload — every field, including deferred ones.
|
|
69
|
+
* Emitted per item only when the collection declares `deferred:`.
|
|
70
|
+
*
|
|
71
|
+
* Takes either a concrete slug (kit's `useEntityDetail`, which holds a
|
|
72
|
+
* record) or the literal placeholder `{slug}` (core's `applyDeferredDetail`,
|
|
73
|
+
* which builds a pattern that `substitutePlaceholders` resolves later
|
|
74
|
+
* against the dynamic-route param). Both are plain interpolation; this
|
|
75
|
+
* function does not encode, matching the behavior of the call sites it
|
|
76
|
+
* replaced.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} collection - The collection name.
|
|
79
|
+
* @param {string} slug - A record slug, or a `{param}` placeholder.
|
|
80
|
+
* @returns {string} e.g. `/data/articles/design-tips.json`
|
|
81
|
+
*/
|
|
82
|
+
export function recordDataUrl(collection, slug) {
|
|
83
|
+
return `${DATA_URL_PREFIX}${collection}/${slug}.json`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The inverse of `collectionDataUrl` — recover a collection name from a fetch
|
|
88
|
+
* path so a caller can look it up among the declared collections.
|
|
89
|
+
*
|
|
90
|
+
* Best-effort by design, and the caller decides what a miss means: a path
|
|
91
|
+
* outside the compiled-collection tree is returned with only its `.json`
|
|
92
|
+
* suffix removed, which simply will not match any declared collection and
|
|
93
|
+
* lets the caller fall through to reading the file. Nested names round-trip
|
|
94
|
+
* (`/data/archive/2024/posts.json` → `archive/2024/posts`).
|
|
95
|
+
*
|
|
96
|
+
* Lives here because it is the *same* convention read backwards. Left at a
|
|
97
|
+
* call site it becomes a regex with the prefix baked in — which is exactly
|
|
98
|
+
* how `validate-data.js` came to hold a sixth copy of it.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} path - A fetch config's `path`.
|
|
101
|
+
* @returns {string} The derived collection name.
|
|
102
|
+
*/
|
|
103
|
+
export function collectionNameFromUrl(path) {
|
|
104
|
+
if (typeof path !== 'string') return ''
|
|
105
|
+
// DATA_DIR is a plain identifier segment, so it needs no regex escaping.
|
|
106
|
+
return path.replace(new RegExp(`^/?${DATA_DIR}/`), '').replace(/\.json$/i, '')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Whether a fetch config's `path` addresses compiled collection data.
|
|
111
|
+
*
|
|
112
|
+
* Used to scope behavior that only makes sense for build-emitted files —
|
|
113
|
+
* locale prefixing in particular, which must not touch a remote `url:`
|
|
114
|
+
* source or an author-declared `detailUrl:`.
|
|
115
|
+
*
|
|
116
|
+
* @param {*} path - A fetch config's `path` field.
|
|
117
|
+
* @returns {boolean}
|
|
118
|
+
*/
|
|
119
|
+
export function isDataUrl(path) {
|
|
120
|
+
return typeof path === 'string' && path.startsWith(DATA_URL_PREFIX)
|
|
121
|
+
}
|
package/src/fetch-config.js
CHANGED
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
* carrying a level or a semantic the other lacked). This module is the single
|
|
13
13
|
* definition they call.
|
|
14
14
|
*
|
|
15
|
-
* INTENTIONALLY A LEAF:
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* INTENTIONALLY A LEAF: safe to load anywhere — including environments with no
|
|
16
|
+
* DOM, no filesystem, and a hard bundle-size ceiling. The rule that protects
|
|
17
|
+
* that is **no transitive graph**: import nothing from the package root (which
|
|
18
|
+
* pulls semantic-parser and theming) and nothing that itself imports. A
|
|
19
|
+
* zero-dependency sibling leaf is admissible and `./data-paths.js` is the only
|
|
20
|
+
* one taken — the path convention it holds has to be identical here and in the
|
|
21
|
+
* build that emits the files, and a second copy of that string is precisely
|
|
22
|
+
* the drift this module exists to prevent.
|
|
19
23
|
*
|
|
20
24
|
* WHAT THIS DOES NOT OWN: where the sources come from. A caller holding a live
|
|
21
25
|
* object graph reads them off the graph; a caller holding a content document
|
|
@@ -23,6 +27,8 @@
|
|
|
23
27
|
* `resolveFetchConfigs`. That difference is real and stays with the caller.
|
|
24
28
|
*/
|
|
25
29
|
|
|
30
|
+
import { isDataUrl, recordDataUrl } from './data-paths.js'
|
|
31
|
+
|
|
26
32
|
/**
|
|
27
33
|
* Is this fetch declaration a per-instance *refinement* of an ancestor's
|
|
28
34
|
* config rather than a new source of its own?
|
|
@@ -54,7 +60,7 @@ export function isFetchRefinement(cfg) {
|
|
|
54
60
|
function localizeConfig(cfg, locale, defaultLocale) {
|
|
55
61
|
if (!cfg.path) return cfg
|
|
56
62
|
if (!locale || locale === defaultLocale) return cfg
|
|
57
|
-
if (!cfg.path
|
|
63
|
+
if (!isDataUrl(cfg.path)) return cfg
|
|
58
64
|
return { ...cfg, path: `/${locale}${cfg.path}` }
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -99,7 +105,7 @@ function applyDeferredDetail(cfg, collections) {
|
|
|
99
105
|
if (!deferred || deferred.length === 0) return cfg
|
|
100
106
|
const pattern = typeof collConfig.detailUrl === 'string'
|
|
101
107
|
? collConfig.detailUrl
|
|
102
|
-
:
|
|
108
|
+
: recordDataUrl(schema, '{slug}')
|
|
103
109
|
return { ...cfg, detail: pattern }
|
|
104
110
|
}
|
|
105
111
|
|
package/src/index.js
CHANGED
|
@@ -27,9 +27,20 @@ export {
|
|
|
27
27
|
resolvePublishableLocales,
|
|
28
28
|
validateLanguageConfig
|
|
29
29
|
} from './locale-config.js'
|
|
30
|
+
export {
|
|
31
|
+
DATA_DIR,
|
|
32
|
+
DATA_URL_PREFIX,
|
|
33
|
+
collectionDataUrl,
|
|
34
|
+
recordDataUrl,
|
|
35
|
+
collectionNameFromUrl,
|
|
36
|
+
isDataUrl
|
|
37
|
+
} from './data-paths.js'
|
|
30
38
|
export { evaluate as evaluateWhere, match as matchWhere } from './where.js'
|
|
31
|
-
export { isRichSchema } from './schemas.js'
|
|
32
|
-
export {
|
|
39
|
+
export { isRichSchema, normalizeSchema } from './schemas.js'
|
|
40
|
+
export {
|
|
41
|
+
resolveStyle as resolveRequestStyle,
|
|
42
|
+
listStyleNames as listRequestStyleNames
|
|
43
|
+
} from './request-styles/index.js'
|
|
33
44
|
|
|
34
45
|
/**
|
|
35
46
|
* The singleton Uniweb instance.
|
|
@@ -53,8 +64,20 @@ export function getUniweb() {
|
|
|
53
64
|
* this transport. Used only by the editor's preview iframe.
|
|
54
65
|
* @returns {Uniweb} The created instance (also assigned to globalThis.uniweb).
|
|
55
66
|
*/
|
|
56
|
-
export function createUniweb(
|
|
57
|
-
|
|
67
|
+
export function createUniweb(
|
|
68
|
+
content,
|
|
69
|
+
foundation = null,
|
|
70
|
+
extensions = [],
|
|
71
|
+
{ defaultFetcher = null, transport = null, dev = false } = {}
|
|
72
|
+
) {
|
|
73
|
+
const instance = new Uniweb({
|
|
74
|
+
content,
|
|
75
|
+
foundation,
|
|
76
|
+
extensions,
|
|
77
|
+
defaultFetcher,
|
|
78
|
+
transport,
|
|
79
|
+
dev
|
|
80
|
+
})
|
|
58
81
|
globalThis.uniweb = instance
|
|
59
82
|
return instance
|
|
60
83
|
}
|
package/src/schemas.js
CHANGED
|
@@ -35,3 +35,76 @@ export function isRichSchema(schema) {
|
|
|
35
35
|
if (schema.childSchema && typeof schema.childSchema === 'object') return true
|
|
36
36
|
return false
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Normalize any authored `data:` schema shape to the rich form the editor
|
|
41
|
+
* renders, or null when it is not a single form at all.
|
|
42
|
+
*
|
|
43
|
+
* WHY THIS EXISTS. `isRichSchema` answers "is this already the rich shape?",
|
|
44
|
+
* which is the right question for dispatch and the wrong one for "can this be
|
|
45
|
+
* edited". There are THREE authored shapes and it accepts exactly one:
|
|
46
|
+
*
|
|
47
|
+
* { fields: [ {id, …} ] } meta.js inline rich-form → true
|
|
48
|
+
* { fields: { name: spec } } a RESOLVED NAMED REF → FALSE
|
|
49
|
+
* { name: spec } meta.js inline field map → false
|
|
50
|
+
*
|
|
51
|
+
* The middle row is the important one and the reason this helper is in `core`
|
|
52
|
+
* rather than in the editor. A named ref (`'@/article'`, `'@std/person'`) is the
|
|
53
|
+
* FIRST authoring form the docs show, and `validateAndNormalizeSchema` in the
|
|
54
|
+
* build resolves it to `{ fields: <MAP> }` — a map, not an array. So filtering
|
|
55
|
+
* with `isRichSchema` discards not merely "simple" schemas but the primary
|
|
56
|
+
* documented one, and any consumer that wants to render it has to re-derive the
|
|
57
|
+
* conversion. Three consumers re-deriving it is exactly the divergence the
|
|
58
|
+
* shared predicate was introduced to prevent.
|
|
59
|
+
*
|
|
60
|
+
* A field map is an unordered `fields[]`, so the conversion is mechanical.
|
|
61
|
+
* Ordering comes from `Object.entries`, which is insertion order for string keys
|
|
62
|
+
* — i.e. the order the author wrote, which is the order a form should show.
|
|
63
|
+
*
|
|
64
|
+
* `sections` returns null on purpose: a sectioned data-schema describes a Model
|
|
65
|
+
* with several sections, which is not one form. Flattening it would invent a
|
|
66
|
+
* layout the author never expressed.
|
|
67
|
+
*
|
|
68
|
+
* @param {*} schema - any authored or resolved `data:` schema value
|
|
69
|
+
* @returns {{ fields: Array<object> } | null} the rich shape, or null
|
|
70
|
+
*/
|
|
71
|
+
export function normalizeSchema(schema) {
|
|
72
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema))
|
|
73
|
+
return null
|
|
74
|
+
|
|
75
|
+
// Already rich — hand back untouched. Composite/childSchema variants are rich
|
|
76
|
+
// by `isRichSchema`'s definition and are not ours to reshape.
|
|
77
|
+
if (Array.isArray(schema.fields)) return schema
|
|
78
|
+
if (schema.isComposite === true || schema.childSchema) return schema
|
|
79
|
+
|
|
80
|
+
// A sectioned Model is not a single form.
|
|
81
|
+
if (schema.sections !== undefined) return null
|
|
82
|
+
|
|
83
|
+
const mapToFields = (map) =>
|
|
84
|
+
Object.entries(map).map(([id, spec]) =>
|
|
85
|
+
typeof spec === 'string' ? { id, type: spec } : { id, ...spec }
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
// A resolved named ref: `fields` present, as a map.
|
|
89
|
+
if (schema.fields && typeof schema.fields === 'object') {
|
|
90
|
+
const { fields, ...rest } = schema
|
|
91
|
+
return { ...rest, fields: mapToFields(fields) }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// An inline field map: no `fields` key, so every value must be an OBJECT
|
|
95
|
+
// carrying `type`.
|
|
96
|
+
//
|
|
97
|
+
// The bare-type string shorthand (`{ cpu: 'string' }`) is deliberately NOT
|
|
98
|
+
// accepted here, even though schema FILES support it. Without a `fields` key
|
|
99
|
+
// there is nothing to distinguish it from ordinary data: `{ name: 'Acme' }` and
|
|
100
|
+
// `{ cpu: 'string' }` are the same shape, and an earlier cut of this function
|
|
101
|
+
// turned `{ name, description }` into a two-field form. Erring toward null
|
|
102
|
+
// costs an author the object spelling; erring the other way invents a form out
|
|
103
|
+
// of a config block.
|
|
104
|
+
const entries = Object.entries(schema)
|
|
105
|
+
if (!entries.length) return null
|
|
106
|
+
const isFieldSpec = ([, v]) =>
|
|
107
|
+
v && typeof v === 'object' && !Array.isArray(v) && v.type !== undefined
|
|
108
|
+
if (!entries.every(isFieldSpec)) return null
|
|
109
|
+
return { fields: mapToFields(schema) }
|
|
110
|
+
}
|
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
|
*/
|