@rathnasgala2/template 2.0.0 → 2.1.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/README.md +204 -137
- package/contracts/render-policy.jcs +1 -1
- package/contracts/theme-styling-contract.jcs +1 -1
- package/package.json +4 -5
- package/src/core/errors.js +13 -13
- package/src/core/index.js +102 -66
- package/src/core/internal/appearance/base-layer.js +180 -0
- package/src/core/internal/appearance/bootstrap-script.js +11 -11
- package/src/core/internal/appearance/contract.js +59 -44
- package/src/core/internal/appearance/controller-markup.js +7 -8
- package/src/core/internal/appearance/styling-contract.js +132 -29
- package/src/core/internal/canonical-jcs.js +0 -0
- package/src/core/internal/content-security.js +0 -0
- package/src/core/internal/eleventy-render.js +52 -35
- package/src/core/internal/feeds.js +10 -13
- package/src/core/internal/fs-walk.js +2 -2
- package/src/core/internal/media/avif-probe.js +4 -4
- package/src/core/internal/media/font-policy.js +2 -2
- package/src/core/internal/media/jpeg-codec.js +0 -0
- package/src/core/internal/media/limits.js +32 -34
- package/src/core/internal/media/pipeline.js +4 -4
- package/src/core/internal/media/png-codec.js +7 -7
- package/src/core/internal/media/resize.js +2 -2
- package/src/core/internal/media/sniff.js +4 -4
- package/src/core/internal/media/theme-svg-sanitizer.js +278 -0
- package/src/core/internal/media/webp-probe.js +3 -3
- package/src/core/internal/messages.js +8 -8
- package/src/core/internal/page-kinds.js +46 -48
- package/src/core/internal/render-policy-content.js +57 -21
- package/src/core/internal/route-labels.js +5 -6
- package/src/core/internal/route.js +16 -20
- package/src/core/internal/search-index.js +12 -14
- package/src/core/internal/semver-range.js +84 -0
- package/src/core/internal/seo.js +21 -23
- package/src/core/internal/sitemap.js +6 -8
- package/src/core/internal/skeleton.js +42 -39
- package/src/core/internal/source-inventory.js +4 -5
- package/src/core/internal/text-direction.js +1 -2
- package/src/core/internal/theme-assets.js +294 -67
- package/src/core/internal/uuid.js +4 -5
- package/src/core/manifest.js +0 -0
- package/types/index.d.ts +22 -8
package/README.md
CHANGED
|
@@ -5,14 +5,14 @@ portable author repository format. It is one of the three author-facing S2
|
|
|
5
5
|
artifacts, alongside one `@rathnasgala2/theme-*` package and
|
|
6
6
|
`@rathnasgala2/publish-action`.
|
|
7
7
|
|
|
8
|
-
This package is JavaScript ESM on Node 24, authored without TypeScript sources
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
This package is JavaScript ESM on Node 24, authored without TypeScript sources:
|
|
9
|
+
every export carries JSDoc types, checked by `tsc --checkJs --noEmit` against a
|
|
10
|
+
hand-maintained `.d.ts` surface.
|
|
11
11
|
|
|
12
12
|
## Status
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
absence gate, quality-gate tooling) plus
|
|
14
|
+
This repository's scaffold (exact pins, `src/core/`-only layout, module-tree
|
|
15
|
+
absence gate, quality-gate tooling) plus its renderer adapter:
|
|
16
16
|
`renderPublication(buildInput, options)`, exported from `src/core/index.js`, is
|
|
17
17
|
the package's one documented entry point. It:
|
|
18
18
|
|
|
@@ -29,7 +29,7 @@ the package's one documented entry point. It:
|
|
|
29
29
|
`urn:gala:schema:artifact-manifest:2.0.0` instance describing the candidate
|
|
30
30
|
output directory.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
The media pipeline lives under `src/core/internal/media/`, run from
|
|
33
33
|
`renderPublication` after the Eleventy route listing so its output is never
|
|
34
34
|
mistaken for an HTML route:
|
|
35
35
|
|
|
@@ -40,8 +40,8 @@ mistaken for an HTML route:
|
|
|
40
40
|
entropy decode (a documented scope decision — see
|
|
41
41
|
`webp-probe.js`/`avif-probe.js`). Every format shares one set of resource
|
|
42
42
|
ceilings in `limits.js` (raster dimension, pixel count, decoded-buffer and
|
|
43
|
-
source-byte caps reused from
|
|
44
|
-
plus this renderer's own documented per-publication ceilings).
|
|
43
|
+
source-byte caps reused from the theme package's own closed binary-asset
|
|
44
|
+
decoder bounds, plus this renderer's own documented per-publication ceilings).
|
|
45
45
|
- **Metadata strip, orientation.** PNG/JPEG derivatives are always re-encoded
|
|
46
46
|
from decoded pixels, never byte-copied, so no ancillary/EXIF metadata survives
|
|
47
47
|
into a derivative; JPEG's EXIF `Orientation` tag is read and applied
|
|
@@ -55,7 +55,7 @@ mistaken for an HTML route:
|
|
|
55
55
|
unconditionally as an image reference (`MEDIA_SVG_REJECTED`) — SVG is never
|
|
56
56
|
processed as author media.
|
|
57
57
|
- **Font policy.** `appearance.fontAssets` entries must be bounded WOFF2 (exact
|
|
58
|
-
signature,
|
|
58
|
+
signature, a fixed 4,194,304-byte font cap); a remote font reference cannot
|
|
59
59
|
enter `build-input` in the first place (`resolvedFile` is always a
|
|
60
60
|
repository-relative path), so "no remote font is ever retrieved" holds
|
|
61
61
|
structurally.
|
|
@@ -63,17 +63,16 @@ mistaken for an HTML route:
|
|
|
63
63
|
mismatch, SVG, unrecognized format, invalid font — is a `MediaPipelineError`
|
|
64
64
|
with a stable `reasonCode`, thrown before the manifest is assembled.
|
|
65
65
|
|
|
66
|
-
`options.sourceDirectory`
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
`
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
`const: "text/html"`): a `renderableBody.body` inside a validated
|
|
66
|
+
`options.sourceDirectory` is the caller-mounted, read-only repository source
|
|
67
|
+
tree `build-input`'s `resolvedFile` references point into; every referenced
|
|
68
|
+
file's bytes are read from there and verified against `build-input`'s declared
|
|
69
|
+
`sourceDigest` before being decoded.
|
|
70
|
+
|
|
71
|
+
The complete output-security pipeline (`src/core/internal/content-security.js`)
|
|
72
|
+
sits on top of that adapter. **Division of labour** (normalization replaces an
|
|
73
|
+
authored body path with a `renderableBody` carrying "the deterministic policy
|
|
74
|
+
output/digest"; `build-input.schema.json` fixes `renderableBody.bodyMediaType`
|
|
75
|
+
as `const: "text/html"`): a `renderableBody.body` inside a validated
|
|
77
76
|
`build-input:2.0.0` instance is **already** render-policy-conformant HTML,
|
|
78
77
|
produced once upstream of this renderer. This package therefore exposes two
|
|
79
78
|
distinct public entry points:
|
|
@@ -115,23 +114,37 @@ highlighter grammar/token-class catalog) both surfaces above read from,
|
|
|
115
114
|
generated deterministically by `scripts/generate-contracts.mjs` from
|
|
116
115
|
`src/core/internal/render-policy-content.js` — the single source of truth, so
|
|
117
116
|
the published contract and the enforced policy cannot drift apart. Every
|
|
118
|
-
rendered page also carries the
|
|
117
|
+
rendered page also carries the meta-safe CSP baseline as a
|
|
119
118
|
`<meta http-equiv="Content-Security-Policy">` tag (byte-identical on every
|
|
120
119
|
route, since S2 materializes no module package/configuration/output/runtime).
|
|
121
120
|
|
|
121
|
+
**TPL-H4 fix — meta vs. header-only directives.** The CSP specification
|
|
122
|
+
explicitly ignores `frame-ancestors` (and `report-uri`/`sandbox`, neither used
|
|
123
|
+
here) when a policy is delivered via `<meta http-equiv>`; the `<meta>` tag above
|
|
124
|
+
therefore never includes `frame-ancestors`, and
|
|
125
|
+
`contentSecurityPolicyHeaderOnlyDirectives()` (`internal/content-security.js`)
|
|
126
|
+
returns the one directive that is not: `frame-ancestors 'none'`. Clickjacking
|
|
127
|
+
protection for a published artifact is a **hosting-layer requirement**: the
|
|
128
|
+
adapter/deployment serving this artifact must add
|
|
129
|
+
`Content-Security-Policy: frame-ancestors 'none'` (or an equivalent
|
|
130
|
+
`X-Frame-Options` header) as a real HTTP response header. This cannot yet be
|
|
131
|
+
carried in `artifact-manifest:2.0.0`'s own `declarativeHeaders` field, which
|
|
132
|
+
`@rathnasgala2/schemas` currently fixes to `const: []`; surfacing it there is a
|
|
133
|
+
schema-repo change, out of this repository's scope.
|
|
134
|
+
|
|
122
135
|
`img[src]` is restricted to repository-relative paths only in this pipeline;
|
|
123
|
-
full manifest-coverage verification of media references is the
|
|
124
|
-
|
|
136
|
+
full manifest-coverage verification of media references is the media pipeline's
|
|
137
|
+
job (immediately above). A handful of manifest fields (`builder`,
|
|
125
138
|
`sourceIdentity.repository`, `workflowIdentity`, `buildToolVersions`,
|
|
126
139
|
`excludedInputs`) describe facts `build-input` does not itself carry;
|
|
127
140
|
`renderPublication` accepts them as an explicit `options.provenance` bundle
|
|
128
141
|
rather than fabricating them — see `src/core/manifest.js`'s module
|
|
129
|
-
documentation. `contracts/theme-styling-contract.jcs` is
|
|
130
|
-
`templateStylingContract` catalog (see the
|
|
142
|
+
documentation. `contracts/theme-styling-contract.jcs` is the real, closed
|
|
143
|
+
`templateStylingContract` catalog (see the theme styling contract section
|
|
144
|
+
below).
|
|
131
145
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
through:
|
|
146
|
+
The core semantic skeleton, page kinds and navigation renderer sit on top of the
|
|
147
|
+
output-security pipeline every generated page still passes through:
|
|
135
148
|
|
|
136
149
|
- **`src/core/internal/skeleton.js`** renders the one landmark contract every
|
|
137
150
|
page kind shares, through its one `renderPageBody` composition: a skip link
|
|
@@ -144,13 +157,13 @@ through:
|
|
|
144
157
|
template attribution line). Core owns nine versioned semantic slots
|
|
145
158
|
(`header-actions`, `article-preamble`, `article-end`, `footer-profile`,
|
|
146
159
|
`footer-auxiliary`, `account-intent`, `conversation`, `newsletter`,
|
|
147
|
-
`edition-selector`) plus the collapsed `article-footer-ad` slot
|
|
148
|
-
|
|
149
|
-
|
|
160
|
+
`edition-selector`) plus the collapsed `article-footer-ad` slot: every slot is
|
|
161
|
+
one `data-gala-slot="<name>"` attribute on an otherwise empty, non-landmark
|
|
162
|
+
`<div>`, and the collapsed slot additionally carries `hidden`. This renderer
|
|
150
163
|
has no module system, so every slot except `footer-profile` (which always
|
|
151
|
-
wraps the publication's own footer card) and `header-actions` (
|
|
152
|
-
|
|
153
|
-
|
|
164
|
+
wraps the publication's own footer card) and `header-actions` (the appearance
|
|
165
|
+
control, core content rather than a module) renders empty. This module also
|
|
166
|
+
renders breadcrumbs and deterministic pagination controls
|
|
154
167
|
(`rel="prev"`/`rel="next"`, an `aria-current="page"` status).
|
|
155
168
|
- **`src/core/internal/page-kinds.js`** builds every generated page kind from a
|
|
156
169
|
validated `build-input:2.0.0`: `profile` (the optional publication profile),
|
|
@@ -162,10 +175,10 @@ through:
|
|
|
162
175
|
page per distinct authored value, plus a `/tags`/`/series` root page linking
|
|
163
176
|
to each), and `archive` (one listing page per UTC calendar year, plus a
|
|
164
177
|
`/archive` root page). The `error` page kind (`renderErrorPageBody`) is
|
|
165
|
-
exported as a pure function for
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
178
|
+
exported as a pure function for the `404.html` caller (see below), not wired
|
|
179
|
+
into any generated route here. Listing page size (10), sort tie-break, and the
|
|
180
|
+
tag/series/archive root pages are documented scoped decisions this renderer's
|
|
181
|
+
own governing brief does not fix verbatim.
|
|
169
182
|
- **`src/core/internal/messages.js`** is the message catalog every user-visible
|
|
170
183
|
chrome string above is looked up from (never an inline literal), keyed by
|
|
171
184
|
BCP-47 tag with a fixed `en` fallback — the only catalog S2 authors;
|
|
@@ -186,19 +199,19 @@ every page (a scoped decision: navigation/footer text does not flip language
|
|
|
186
199
|
page to page); each page's own `<html lang>`/`dir` still reflects that
|
|
187
200
|
individual page's own content language.
|
|
188
201
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
The Light/Dark/System appearance controller (`src/core/internal/appearance/`) is
|
|
203
|
+
the only browser bootstrap this renderer emits ("Module absence and CSP
|
|
204
|
+
equality"):
|
|
192
205
|
|
|
193
206
|
- **`contract.js`** is the one module every other appearance-related file
|
|
194
|
-
imports every name/value from: the two
|
|
207
|
+
imports every name/value from: the two fixed root attributes
|
|
195
208
|
(`data-gala-publication-root` presence, `data-gala-resolved-color-mode`
|
|
196
209
|
exact-value `"light"`/`"dark"`), the template-internal
|
|
197
210
|
`data-gala-color-mode-selection` bookkeeping attribute, the three canonical
|
|
198
211
|
mode values (`light`/`dark`/`system`), the versioned local storage key
|
|
199
212
|
(`gala:appearance:color-mode:v1`), the control's `id`, the bootstrap script's
|
|
200
213
|
fixed output path/media type and the `color-scheme` meta content — the
|
|
201
|
-
contract
|
|
214
|
+
contract the published theme styling catalog targets.
|
|
202
215
|
- **`controller-markup.js`** renders a native `<select>` (a brief-admitted
|
|
203
216
|
pattern; needs no `aria-pressed`/`aria-checked` bookkeeping of its own, unlike
|
|
204
217
|
a custom button-group or radio-group would) bound to a visible `<label>`, with
|
|
@@ -225,29 +238,47 @@ section 3, "Module absence and CSP equality"):
|
|
|
225
238
|
directory and enters it into the manifest as an ordinary `manifestAsset` row
|
|
226
239
|
(`{path, byteLength, sha256}` plus its fixed
|
|
227
240
|
`application/javascript; charset=utf-8` media type), after the Eleventy route
|
|
228
|
-
listing — exactly like
|
|
229
|
-
|
|
230
|
-
|
|
241
|
+
listing — exactly like a media derivative — so it is never mistaken for an
|
|
242
|
+
HTML route. `src/core/internal/eleventy-render.js`'s skeleton layout carries
|
|
243
|
+
the `data-gala-publication-root` attribute, the
|
|
231
244
|
`<meta name="color-scheme" content="light dark">` tag and the one
|
|
232
245
|
`<script src>` reference, all fixed literals from `contract.js`, so every
|
|
233
|
-
route's `<head>` is still byte-identical (
|
|
246
|
+
route's `<head>` is still byte-identical (this renderer materializes no module
|
|
234
247
|
package/configuration/output/runtime).
|
|
235
248
|
- With JavaScript disabled, the control still renders (a native `<select>`,
|
|
236
|
-
fully keyboard-operable and usable with no script at all)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
249
|
+
fully keyboard-operable and usable with no script at all). The resolved root
|
|
250
|
+
attribute is server-rendered as the fixed `light` default, so a no-JS reader,
|
|
251
|
+
an archival crawler, or a load where the bootstrap script fails still gets a
|
|
252
|
+
fully themed page instead of unstyled UA-default HTML; a scripted reader's
|
|
253
|
+
phase 1 bootstrap always overwrites this attribute with the reader's real
|
|
254
|
+
stored selection/system preference before first paint.
|
|
255
|
+
`<meta name="color-scheme">` also carries `prefers-color-scheme` through to
|
|
256
|
+
user-agent styling (form controls, scrollbars) independent of any script.
|
|
257
|
+
|
|
258
|
+
**Authoring a theme:** see [`docs/theme-authoring.md`](docs/theme-authoring.md)
|
|
259
|
+
for the full guide — how a theme is a delta over the template-owned `gala-base`
|
|
260
|
+
layer, the closed 35-token catalog, the closed five-member pseudo-class catalog,
|
|
261
|
+
passive-asset/SVG admission, the `theme.json` digest chain, and `templateRange`.
|
|
262
|
+
Run `node scripts/scaffold-theme.mjs <directory>` to write a minimal,
|
|
263
|
+
already-conformant theme package skeleton rather than hand-typing one.
|
|
264
|
+
|
|
265
|
+
The theme styling contract replaces `contracts/theme-styling-contract.jcs`'s
|
|
266
|
+
original scaffold placeholder with the real, closed `templateStylingContract`
|
|
267
|
+
catalog this renderer's own internal styling decision defines, and wires the
|
|
268
|
+
selected theme package's stylesheets into every generated page:
|
|
245
269
|
|
|
246
270
|
- **`src/core/internal/appearance/styling-contract.js`** is the one reviewed
|
|
247
271
|
source module `scripts/generate-contracts.mjs` emits the contract from: the
|
|
248
|
-
ordered
|
|
249
|
-
`gala-print`
|
|
250
|
-
|
|
272
|
+
ordered five-layer catalog (`gala-base`/`gala-tokens`/`gala-components`/
|
|
273
|
+
`gala-utilities`/`gala-print` — contract 2.1.0: `gala-base` is the template's
|
|
274
|
+
own layer, carrying the reset/type-scale/focus-ring defaults documented below
|
|
275
|
+
`internal/appearance/base-layer.js`, always ordered first so a theme's own
|
|
276
|
+
layers can override it), the publication-root/ resolved-palette selectors, the
|
|
277
|
+
closed type/class/id/attribute leaf catalogs, a closed five-member
|
|
278
|
+
pseudo-class catalog (`hover`, `focus-visible`, `active`, `visited`,
|
|
279
|
+
`disabled` — contract 2.1.0; see that module's own documentation for why each
|
|
280
|
+
is admitted and why the token catalog's `color-focus`/`color-link-visited` had
|
|
281
|
+
no reachable application before it), and exactly 64 `publicThemeSlotHooks` —
|
|
251
282
|
one per catalog leaf, so every leaf a theme could validly select is a named,
|
|
252
283
|
documented hook and no catalog member is orphaned. Every leaf is drawn from
|
|
253
284
|
what this renderer actually renders: 28 type-selector hooks (every
|
|
@@ -255,16 +286,16 @@ generated page:
|
|
|
255
286
|
`internal/page-kinds.js` and the markdown-it CommonMark pipeline can produce),
|
|
256
287
|
15 class-selector hooks (the base Prism `.token` class plus one
|
|
257
288
|
`.language-<grammar>` hook per admitted highlight grammar — fine-grained
|
|
258
|
-
per-token-kind classes are a documented
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
289
|
+
per-token-kind classes are a documented scope exclusion), 2 id-selector hooks
|
|
290
|
+
(`#main-content`, the appearance `<select>`'s fixed id) and 19 attribute-value
|
|
291
|
+
hooks (one per `data-gala-slot` value, one per the `data-gala-page-kind`
|
|
292
|
+
value). `catalogDigest` is
|
|
262
293
|
`SHA256(UTF8("GALA-TEMPLATE-STYLING-CONTRACT-V2\0") || JCS(...))`, matching
|
|
263
|
-
|
|
264
|
-
`templateStylingContract` profile uses
|
|
265
|
-
the package's public API, so this
|
|
266
|
-
domain-separated formula rather than importing
|
|
267
|
-
`assertTemplateStylingContractShape` is this repository's own structural
|
|
294
|
+
the same domain-separated digest construction and the digest profile
|
|
295
|
+
`@rathnasgala2/schemas`' own internal `templateStylingContract` profile uses
|
|
296
|
+
(that function is not exported through the package's public API, so this
|
|
297
|
+
module reimplements the same domain-separated formula rather than importing
|
|
298
|
+
it). `assertTemplateStylingContractShape` is this repository's own structural
|
|
268
299
|
self-validator (root/palette attribute rows, fixed layer/selector/composition
|
|
269
300
|
constants, hook count/uniqueness bound, `catalogDigest` recomputation); no
|
|
270
301
|
`urn:gala:schema:template-styling-contract` schema ID is registered in
|
|
@@ -286,50 +317,67 @@ generated page:
|
|
|
286
317
|
`@rathnasgala2/schemas`' own exported `validateGalaDocument`.
|
|
287
318
|
- **`src/core/internal/theme-assets.js`** is the optional theme-asset
|
|
288
319
|
integration layer: given `options.themeDirectory` (an extracted theme package
|
|
289
|
-
directory, shaped exactly like the
|
|
290
|
-
`tokens.css`, `components.css`, an optional `utilities.css`,
|
|
291
|
-
`package.json`, `LICENSE`, `README.md`, `assets/*`), it copies
|
|
292
|
-
stylesheets and any declared non-CSS passive assets into
|
|
293
|
-
enters each as an ordinary `{path, byteLength, sha256}`
|
|
294
|
-
and renders the ordered `<link>` markup every generated
|
|
295
|
-
inserts (`print.css` alone carries `media="print"`). It
|
|
296
|
-
own declared `stylesheets` list against the two admitted
|
|
297
|
-
without `utilities.css`) and that `cssLayers` byte-equals the
|
|
298
|
-
layer projection, but does **not** re-implement
|
|
299
|
-
Module admission grammar — that is the shared `theme-release.yml`
|
|
300
|
-
runner's job
|
|
320
|
+
directory, shaped exactly like the theme package's own file set —
|
|
321
|
+
`theme.json`, `tokens.css`, `components.css`, an optional `utilities.css`,
|
|
322
|
+
`print.css`, `package.json`, `LICENSE`, `README.md`, `assets/*`), it copies
|
|
323
|
+
the declared stylesheets and any declared non-CSS passive assets into
|
|
324
|
+
`assets/theme/`, enters each as an ordinary `{path, byteLength, sha256}`
|
|
325
|
+
`manifestAsset` row, and renders the ordered `<link>` markup every generated
|
|
326
|
+
page's `<head>` inserts (`print.css` alone carries `media="print"`). It
|
|
327
|
+
validates the theme's own declared `stylesheets` list against the two admitted
|
|
328
|
+
shapes (with or without `utilities.css`) and that `cssLayers` byte-equals the
|
|
329
|
+
corresponding layer projection, but does **not** re-implement the full CSS
|
|
330
|
+
Syntax Module admission grammar — that is the shared `theme-release.yml`
|
|
331
|
+
conformance runner's job, run once per theme release, never per publication
|
|
301
332
|
build. `renderPublication` falls back to the pre-existing fixed
|
|
302
333
|
`assets/theme/print.css` link when no `options.themeDirectory` is supplied
|
|
303
|
-
(every caller
|
|
304
|
-
exercise
|
|
334
|
+
(every caller that predates theme integration, and this repository's own tests
|
|
335
|
+
that do not exercise it). Minimal in-repo fixture theme packages for both
|
|
305
336
|
stylesheet-list shapes live under `test/fixtures/theme-fixture-full/` and
|
|
306
337
|
`test/fixtures/theme-fixture-minimal/` — deliberately not real, conformant
|
|
307
|
-
theme CSS (authoring one is
|
|
308
|
-
copy/link/manifest mechanism.
|
|
338
|
+
theme CSS (authoring one is a theme package's own task); they exist only to
|
|
339
|
+
exercise the copy/link/manifest mechanism.
|
|
340
|
+
|
|
341
|
+
Hardening at consume time (a theme package is lower-trust supply-chain input
|
|
342
|
+
than the repository owner's own authored content, so it is held to at least the
|
|
343
|
+
same bar): `theme.json` is schema-validated against
|
|
344
|
+
`urn:gala:schema:theme-contract:2.0.0` before anything else, and its
|
|
345
|
+
`contractVersion`/`stylingContractDigest`/ `templateRange` are checked against
|
|
346
|
+
this renderer's own published styling contract and version
|
|
347
|
+
(`internal/semver-range.js`: a deliberately closed exact-version/caret-range
|
|
348
|
+
subset). Every declared file's `byteLength`/ `sha256` is verified against the
|
|
349
|
+
bytes actually read, and `theme.json.budgets` is enforced. Every passive asset's
|
|
350
|
+
bytes are sniffed (never trusted from the declared `mediaType`) against a closed
|
|
351
|
+
allowlist: raster formats are admitted directly, and SVG is admitted only after
|
|
352
|
+
`internal/media/theme-svg-sanitizer.js`'s closed grammar rejects any
|
|
353
|
+
`<script>`/event-handler/external-reference construct outright. **Iconography is
|
|
354
|
+
in scope and expected** (TPL-H6 decision): this sanitizer is what makes a
|
|
355
|
+
theme-declared SVG icon mark safe to publish.
|
|
356
|
+
|
|
309
357
|
- **basePath fix (LOCAL-7 follow-up; independent-review finding B2).** Before
|
|
310
|
-
|
|
311
|
-
`og:image`/`twitter:image` URL were both root-absolute,
|
|
312
|
-
publication's own `basePath` — a non-root `basePath`
|
|
313
|
-
them. `internal/appearance/contract.js`'s new
|
|
358
|
+
theme integration existed, the appearance bootstrap script's `<script src>`
|
|
359
|
+
and the social-image `og:image`/`twitter:image` URL were both root-absolute,
|
|
360
|
+
unprefixed by the publication's own `basePath` — a non-root `basePath`
|
|
361
|
+
publication would 404 them. `internal/appearance/contract.js`'s new
|
|
314
362
|
`appearanceBootstrapScriptHref(basePath)` and `internal/seo.js`'s
|
|
315
363
|
`resolveSocialImageUrl` (now taking `basePath`) both join through
|
|
316
364
|
`internal/route.js`'s shared `joinBasePathAndRoute`/`projectFixedAssetPath`,
|
|
317
365
|
exactly like the pre-existing print-stylesheet convention (now folded into
|
|
318
366
|
`internal/theme-assets.js`) already did for _hrefs_. Independent review
|
|
319
367
|
(finding B2) found that this was only half the fix: the _physical files_ this
|
|
320
|
-
renderer writes for every asset class — theme CSS/passive assets, every
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
368
|
+
renderer writes for every asset class — theme CSS/passive assets, every media
|
|
369
|
+
derivative, and the appearance bootstrap script — were still written, and
|
|
370
|
+
entered into the manifest, at their bare **unprefixed** paths. Since the
|
|
371
|
+
local-directory adapter serves a `manifestAsset.path` verbatim, a non-root
|
|
372
|
+
`basePath` publication's `<link>`/`<script src>`/`og:image` href would point
|
|
373
|
+
at `<basePath>/assets/...` while the actual file sat at the unprefixed
|
|
374
|
+
`assets/...` — a 404 in practice, not just a theoretical href mismatch.
|
|
375
|
+
`src/core/index.js` now joins `basePath` into the physical destination path
|
|
376
|
+
and the `manifestAsset.path` for media derivatives (`joinedMediaAssets`,
|
|
377
|
+
computed with the same `projectFixedAssetPath` every other fixed-name
|
|
378
|
+
generated asset already uses) and the appearance bootstrap script
|
|
379
|
+
(`joinedAppearanceScriptPath`); `internal/theme-assets.js` computes its joined
|
|
380
|
+
output path once per file and derives both the `<link href>` and the
|
|
333
381
|
file/manifest path from that exact same value, so they cannot drift apart.
|
|
334
382
|
`test/theme-styling-contract.test.js` carries a dedicated non-root,
|
|
335
383
|
multi-segment `basePath` (`/blog/2024`) fixture test asserting every one of
|
|
@@ -356,14 +404,15 @@ generated page:
|
|
|
356
404
|
|
|
357
405
|
### Media pipeline scope decisions (documented, not silently assumed)
|
|
358
406
|
|
|
359
|
-
Neither
|
|
360
|
-
formats/quality, or a per-publication
|
|
361
|
-
media (
|
|
362
|
-
`gala-theme-binary-assets-v2` _theme passive-asset_
|
|
363
|
-
therefore documents its own choices in
|
|
364
|
-
rather than inventing undocumented magic
|
|
407
|
+
Neither this renderer's own governing brief nor its internal styling decision
|
|
408
|
+
fixes exact derivative widths, output formats/quality, or a per-publication
|
|
409
|
+
media byte/count ceiling for _author_ media (those numeric bounds are stated
|
|
410
|
+
only for the closed `gala-theme-binary-assets-v2` _theme passive-asset_
|
|
411
|
+
profile). This renderer therefore documents its own choices in
|
|
412
|
+
`src/core/internal/media/limits.js` rather than inventing undocumented magic
|
|
413
|
+
numbers:
|
|
365
414
|
|
|
366
|
-
- decode/resource ceilings reuse
|
|
415
|
+
- decode/resource ceilings reuse the theme binary-asset profile's numbers
|
|
367
416
|
(dimension, pixel-count, decoded-buffer, source-byte caps), since the
|
|
368
417
|
decode-bomb risk they defend against is identical regardless of asset class;
|
|
369
418
|
- responsive derivative widths (`320/640/960/1280/1920`), JPEG derivative
|
|
@@ -375,8 +424,8 @@ rather than inventing undocumented magic numbers:
|
|
|
375
424
|
proportion to this task, and is flagged as a follow-up rather than attempted
|
|
376
425
|
partially.
|
|
377
426
|
|
|
378
|
-
|
|
379
|
-
on top of
|
|
427
|
+
Every remaining "Required generated output" from this renderer's own governing
|
|
428
|
+
brief sits on top of the page kinds above:
|
|
380
429
|
|
|
381
430
|
- **Feeds** (`src/core/internal/feeds.js`): deterministic Atom 1.0
|
|
382
431
|
(`/feed/atom.xml`) and RSS 2.0 (`/feed/rss.xml`) documents over the exact
|
|
@@ -384,7 +433,8 @@ on top of S2-T06's page kinds:
|
|
|
384
433
|
`internal/page-kinds.js`'s `index` page kind paginates (the shared
|
|
385
434
|
`selectPublishedArticles` export), every item URL absolute against
|
|
386
435
|
`buildInput.baseUrl`, bounded to the most recent 50 entries (documented scoped
|
|
387
|
-
decision — neither the brief nor
|
|
436
|
+
decision — neither the brief nor this renderer's own styling decision fixes a
|
|
437
|
+
feed item count).
|
|
388
438
|
- **Sitemap** (`src/core/internal/sitemap.js`): `/sitemap.xml` over every page
|
|
389
439
|
`internal/page-kinds.js` generated that this renderer did not itself mark
|
|
390
440
|
`noindex` (unlisted content), deterministically sorted by each entry's own
|
|
@@ -394,9 +444,10 @@ on top of S2-T06's page kinds:
|
|
|
394
444
|
`/search-index.json`, a deterministic JSON document indexing every published
|
|
395
445
|
`article`/`page` record's title, description, a bounded plain-text excerpt,
|
|
396
446
|
tags and timestamps. Its shape is a documented scoped decision (neither the
|
|
397
|
-
brief nor
|
|
398
|
-
— the Light/Dark/System appearance controller
|
|
399
|
-
browser bootstrap this renderer ever emits
|
|
447
|
+
brief nor this renderer's own styling decision fixes one). **No runtime search
|
|
448
|
+
JavaScript accompanies it** — the Light/Dark/System appearance controller
|
|
449
|
+
remains the only browser bootstrap this renderer ever emits (acceptance test
|
|
450
|
+
C).
|
|
400
451
|
- **SEO/Open-Graph/Twitter/localization metadata**: every generated page's
|
|
401
452
|
`<head>` now carries a viewport meta, an optional description, an optional
|
|
402
453
|
`robots` directive (`noindex, follow` for `status: "unlisted"` content and for
|
|
@@ -406,16 +457,15 @@ on top of S2-T06's page kinds:
|
|
|
406
457
|
`<link>`s. **hreflang** is a documented scope decision, not a silent omission:
|
|
407
458
|
`build-input:2.0.0` has no translation/locale-variant linkage between distinct
|
|
408
459
|
`content[]` records (see `internal/seo.js`'s module documentation), so no
|
|
409
|
-
`<link rel="alternate" hreflang="...">` is ever emitted
|
|
410
|
-
- **`404.html`**: the
|
|
411
|
-
actually wired into `renderPublication` at
|
|
460
|
+
`<link rel="alternate" hreflang="...">` is ever emitted by this renderer.
|
|
461
|
+
- **`404.html`**: the `error` page kind's pure `renderErrorPageBody`, now
|
|
462
|
+
actually wired into `renderPublication` at the fixed `errorDocumentKey`
|
|
412
463
|
(`internal/route.js#errorDocumentPath`) — `404.html` at the root, or
|
|
413
464
|
`<basePath>/404.html` otherwise.
|
|
414
|
-
- **Redirect documents**: unchanged from
|
|
465
|
+
- **Redirect documents**: unchanged from the renderer adapter
|
|
415
466
|
(`internal/route.js#renderRedirectDocument`) — every authored redirect was
|
|
416
467
|
already an ordinary generated artifact with manifest status fixed at `200`;
|
|
417
|
-
this
|
|
418
|
-
it.
|
|
468
|
+
this is documenting that fact here, not re-implementing it.
|
|
419
469
|
- **Print CSS hookup** (`src/core/internal/print-stylesheet.js`): every page's
|
|
420
470
|
`<head>` carries a `<link rel="stylesheet" media="print">` at the documented
|
|
421
471
|
conventional path `<basePath>/assets/theme/print.css`. A selected theme's own
|
|
@@ -430,11 +480,16 @@ by the route's own fixed output path.
|
|
|
430
480
|
|
|
431
481
|
## Prerequisites
|
|
432
482
|
|
|
433
|
-
- Node.js 24.
|
|
434
|
-
|
|
483
|
+
- Node.js >= 24.0.0 (this repository's own development/CI toolchain is Node.js
|
|
484
|
+
24.18.0)
|
|
485
|
+
- npm >= 11.0.0 (this repository's own development/CI toolchain is npm 11.16.0)
|
|
435
486
|
|
|
436
|
-
|
|
437
|
-
|
|
487
|
+
`package.json`'s published `engines` field declares the supported _range_
|
|
488
|
+
(TPL-H5 fix: an exact pin published in the tarball rejects every consumer not on
|
|
489
|
+
that precise patch version, including under a consumer's own `engine-strict`
|
|
490
|
+
setting). This repository's own exact toolchain is pinned separately in
|
|
491
|
+
`devEngines` (evaluated only for this project root, never for a consumer) and in
|
|
492
|
+
`.nvmrc`/`.node-version`.
|
|
438
493
|
|
|
439
494
|
## Commands
|
|
440
495
|
|
|
@@ -443,11 +498,15 @@ npm install
|
|
|
443
498
|
npm run verify
|
|
444
499
|
```
|
|
445
500
|
|
|
446
|
-
`npm run verify`
|
|
501
|
+
`npm run verify` (this description is kept in sync with `package.json`'s own
|
|
502
|
+
`verify` script string by `test/readme-verify-description.test.js`, not
|
|
503
|
+
hand-maintained) runs, in order: Prettier format check, ESLint,
|
|
447
504
|
`tsc --checkJs --noEmit`, the dependency-cruiser
|
|
448
505
|
architecture/module-tree-absence gate, `jscpd` duplication scan, the
|
|
449
|
-
`contracts/*.jcs` canonical-form check,
|
|
450
|
-
check,
|
|
506
|
+
`contracts/*.jcs` canonical-form check, the no-local-schema-pin check,
|
|
507
|
+
`node --test`, the license inventory check, the SBOM currency check
|
|
508
|
+
(`sbom:check` compares `sbom.cdx.json` against the current lockfile — it is not
|
|
509
|
+
a presence check), the workflow-pin check, and `npm audit --audit-level=high`.
|
|
451
510
|
|
|
452
511
|
Individual gates:
|
|
453
512
|
|
|
@@ -458,21 +517,25 @@ npm run lint # ESLint flat config
|
|
|
458
517
|
npm run typecheck # tsc --checkJs --noEmit against types/index.d.ts
|
|
459
518
|
npm run architecture # dependency-cruiser: no src/modules/, no circular deps
|
|
460
519
|
npm run duplication # jscpd, 3% / 50 tokens
|
|
461
|
-
npm run contracts:generate # (re)write contracts/*.jcs
|
|
520
|
+
npm run contracts:generate # (re)write contracts/*.jcs from their reviewed source modules
|
|
462
521
|
npm run contracts:check # fail if committed contracts drifted
|
|
522
|
+
npm run schema-pin:check # fail on a file: @rathnasgala2/schemas specifier
|
|
463
523
|
npm test # node --test
|
|
464
524
|
npm run licenses:generate # (re)write THIRD_PARTY_LICENSES.json
|
|
465
525
|
npm run licenses:check # fail if it drifted from package-lock.json
|
|
466
526
|
npm run sbom:generate # cyclonedx-npm -> sbom.cdx.json
|
|
467
|
-
npm run sbom:check # fail if sbom.cdx.json is
|
|
527
|
+
npm run sbom:check # fail if sbom.cdx.json is stale against package-lock.json
|
|
528
|
+
npm run workflows:check # fail if a GitHub Actions ref is not SHA-pinned
|
|
529
|
+
npm run workflows:drift # fail if workflow files diverge from their own baseline
|
|
530
|
+
npm run audit # npm audit --audit-level=high
|
|
468
531
|
```
|
|
469
532
|
|
|
470
533
|
## Package layout
|
|
471
534
|
|
|
472
535
|
```text
|
|
473
536
|
src/core/ # the only source root
|
|
474
|
-
contracts/render-policy.jcs # normalized-body policy identity (
|
|
475
|
-
contracts/theme-styling-contract.jcs # published styling catalog (
|
|
537
|
+
contracts/render-policy.jcs # normalized-body policy identity (real content)
|
|
538
|
+
contracts/theme-styling-contract.jcs # published styling catalog (real and closed)
|
|
476
539
|
```
|
|
477
540
|
|
|
478
541
|
No `src/modules/` tree, module import edge, registration stub, module
|
|
@@ -481,7 +544,7 @@ configuration, module output or module runtime is ever admitted beside
|
|
|
481
544
|
`only-src-core-is-a-source-root` rule both enforce this; `interactions`,
|
|
482
545
|
`whitelabel`, `newsletter` and `prism` have no module directory, package,
|
|
483
546
|
configuration schema, stub, output, runtime code, network target or
|
|
484
|
-
compatibility promise in this MVP
|
|
547
|
+
compatibility promise in this MVP.
|
|
485
548
|
|
|
486
549
|
## Consuming `@rathnasgala2/schemas`
|
|
487
550
|
|
|
@@ -508,8 +571,12 @@ and `artifact-manifest` are byte-identical from 2.6.1 to 2.8.0.
|
|
|
508
571
|
|
|
509
572
|
## Governing documents
|
|
510
573
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
-
|
|
574
|
+
The internal specifications this package is built against live outside this
|
|
575
|
+
repository and have no public URL, so they are named here as plain text rather
|
|
576
|
+
than as dead links:
|
|
577
|
+
|
|
578
|
+
- Slice brief S2: author-owned publication, section 3 (`@rathnasgala2/template`)
|
|
579
|
+
(internal)
|
|
580
|
+
- The internal decision record establishing JavaScript ESM for public packages
|
|
581
|
+
(internal)
|
|
582
|
+
- WORKSPACE.md, sections 4-8 and 13 (internal)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"contentSecurityPolicy":"default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; form-action 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'none'; media-src 'self'; manifest-src 'self'; worker-src 'none'","highlighter":{"grammars":["bash","css","diff","java","javascript","json","jsx","markdown","markup","python","sql","tsx","typescript","yaml"],"package":"@11ty/eleventy-plugin-syntaxhighlight","tokenClasses":["atrule","attr-equals","attr-name","attr-value","bold","boolean","builtin","cdata","char","class-name","comment","constant","content","coord","deleted","deleted-sign","doctype","entity","for-or-select","function","function-variable","important","inserted","inserted-sign","interpolation","interpolation-punctuation","italic","key","keyword","namespace","null","number","operator","prefix","prolog","property","punctuation","regex","rule","selector","shebang","string","symbol","tag","template-punctuation","template-string","title","token","url","variable"],"version":"5.0.2"},"markdownIt":{"options":{"html":false,"linkify":false,"typographer":false},"package":"markdown-it","version":"14.3.1"},"name":"gala-render-policy","sanitizer":{"allowedAttributes":{"a":["href","rel"],"code":["class"],"h1":["id"],"h2":["id"],"h3":["id"],"h4":["id"],"h5":["id"],"h6":["id"],"img":["src","alt"],"ol":["start"],"pre":["class"],"span":["class"]},"allowedTags":["p","br","hr","h1","h2","h3","h4","h5","h6","ul","ol","li","blockquote","pre","code","strong","em","b","i","u","s","del","ins","sub","sup","mark","small","wbr","a","img","table","thead","tbody","tr","th","td","dl","dt","dd","figure","figcaption","span"],"allowedUrlSchemes":["https","http","mailto"],"package":"sanitize-html","version":"2.17.7"},"version":"2.0.0"}
|
|
1
|
+
{"contentSecurityPolicy":"default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; form-action 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'none'; media-src 'self'; manifest-src 'self'; worker-src 'none'","contentSecurityPolicyHeaderOnlyDirectives":["frame-ancestors 'none'"],"contentSecurityPolicyMeta":"default-src 'none'; base-uri 'none'; object-src 'none'; form-action 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'none'; media-src 'self'; manifest-src 'self'; worker-src 'none'","highlighter":{"grammars":["bash","css","diff","java","javascript","json","jsx","markdown","markup","python","sql","tsx","typescript","yaml"],"package":"@11ty/eleventy-plugin-syntaxhighlight","tokenClasses":["atrule","attr-equals","attr-name","attr-value","bold","boolean","builtin","cdata","char","class-name","comment","constant","content","coord","deleted","deleted-sign","doctype","entity","for-or-select","function","function-variable","important","inserted","inserted-sign","interpolation","interpolation-punctuation","italic","key","keyword","namespace","null","number","operator","prefix","prolog","property","punctuation","regex","rule","selector","shebang","string","symbol","tag","template-punctuation","template-string","title","token","url","variable"],"version":"5.0.2"},"markdownIt":{"options":{"html":false,"linkify":false,"typographer":false},"package":"markdown-it","version":"14.3.1"},"name":"gala-render-policy","sanitizer":{"allowedAttributes":{"a":["href","rel"],"code":["class"],"h1":["id"],"h2":["id"],"h3":["id"],"h4":["id"],"h5":["id"],"h6":["id"],"img":["src","alt"],"ol":["start"],"pre":["class"],"span":["class"]},"allowedTags":["p","br","hr","h1","h2","h3","h4","h5","h6","ul","ol","li","blockquote","pre","code","strong","em","b","i","u","s","del","ins","sub","sup","mark","small","wbr","a","img","table","thead","tbody","tr","th","td","dl","dt","dd","figure","figcaption","span"],"allowedUrlSchemes":["https","http","mailto"],"package":"sanitize-html","version":"2.17.7"},"version":"2.0.0"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"attributes":[{"match":"exact-value","name":"data-gala-page-kind","role":"semantic","values":["archive","article","author","error","index","page","profile","series","tag"]},{"match":"exact-value","name":"data-gala-resolved-color-mode","role":"state","values":["dark","light"]},{"match":"exact-value","name":"data-gala-slot","role":"semantic","values":["account-intent","article-end","article-footer-ad","article-preamble","conversation","edition-selector","footer-auxiliary","footer-profile","header-actions","newsletter"]},{"match":"presence","name":"data-gala-publication-root","role":"root","values":[]}],"catalogDigest":"sha256:
|
|
1
|
+
{"attributes":[{"match":"exact-value","name":"data-gala-page-kind","role":"semantic","values":["archive","article","author","error","index","page","profile","series","tag"]},{"match":"exact-value","name":"data-gala-resolved-color-mode","role":"state","values":["dark","light"]},{"match":"exact-value","name":"data-gala-slot","role":"semantic","values":["account-intent","article-end","article-footer-ad","article-preamble","conversation","edition-selector","footer-auxiliary","footer-profile","header-actions","newsletter"]},{"match":"presence","name":"data-gala-publication-root","role":"root","values":[]}],"catalogDigest":"sha256:4aca7e9794bddfb2c3afd22f4efd078e7dcf69dde9d62ce52d28cec19aef8b91","classSelectors":["language-bash","language-css","language-diff","language-java","language-javascript","language-json","language-jsx","language-markdown","language-markup","language-python","language-sql","language-tsx","language-typescript","language-yaml","token"],"combinators":[" "," > "," + "," ~ "],"composition":{"compoundOrder":["type","id","class","attribute","pseudo-class","pseudo-element"],"functionalPseudoKeywordArguments":["even","odd"],"functionalSelectorArguments":"compound-only","maximumFunctionalDepth":1,"nthExpressionProfile":"gala-positive-an-plus-b-v2","rootScope":"first-compound-required"},"contractVersion":"2.1.0","functionalPseudos":["is","where","not","nth-child","nth-last-child"],"idSelectors":["gala-appearance-color-mode","main-content"],"orderedLayers":["gala-base","gala-tokens","gala-components","gala-utilities","gala-print"],"profile":"gala-template-styling-contract-v2","pseudoClasses":["active","disabled","focus-visible","hover","visited"],"pseudoElements":["after","before","marker","selection"],"publicThemeSlotHooks":[{"hookId":"code-block","selectorAtom":"pre"},{"hookId":"code-inline","selectorAtom":"code"},{"hookId":"code-language-bash","selectorAtom":".language-bash"},{"hookId":"code-language-css","selectorAtom":".language-css"},{"hookId":"code-language-diff","selectorAtom":".language-diff"},{"hookId":"code-language-java","selectorAtom":".language-java"},{"hookId":"code-language-javascript","selectorAtom":".language-javascript"},{"hookId":"code-language-json","selectorAtom":".language-json"},{"hookId":"code-language-jsx","selectorAtom":".language-jsx"},{"hookId":"code-language-markdown","selectorAtom":".language-markdown"},{"hookId":"code-language-markup","selectorAtom":".language-markup"},{"hookId":"code-language-python","selectorAtom":".language-python"},{"hookId":"code-language-sql","selectorAtom":".language-sql"},{"hookId":"code-language-tsx","selectorAtom":".language-tsx"},{"hookId":"code-language-typescript","selectorAtom":".language-typescript"},{"hookId":"code-language-yaml","selectorAtom":".language-yaml"},{"hookId":"code-token","selectorAtom":".token"},{"hookId":"content-article","selectorAtom":"article"},{"hookId":"control-appearance-select","selectorAtom":"#gala-appearance-color-mode"},{"hookId":"control-label","selectorAtom":"label"},{"hookId":"control-option","selectorAtom":"option"},{"hookId":"control-select","selectorAtom":"select"},{"hookId":"heading-1","selectorAtom":"h1"},{"hookId":"heading-2","selectorAtom":"h2"},{"hookId":"heading-3","selectorAtom":"h3"},{"hookId":"heading-4","selectorAtom":"h4"},{"hookId":"heading-5","selectorAtom":"h5"},{"hookId":"heading-6","selectorAtom":"h6"},{"hookId":"landmark-footer","selectorAtom":"footer"},{"hookId":"landmark-header","selectorAtom":"header"},{"hookId":"landmark-main","selectorAtom":"main"},{"hookId":"landmark-main-content","selectorAtom":"#main-content"},{"hookId":"landmark-navigation","selectorAtom":"nav"},{"hookId":"media-image","selectorAtom":"img"},{"hookId":"page-archive","selectorAtom":"[data-gala-page-kind=\"archive\"]"},{"hookId":"page-article","selectorAtom":"[data-gala-page-kind=\"article\"]"},{"hookId":"page-author","selectorAtom":"[data-gala-page-kind=\"author\"]"},{"hookId":"page-error","selectorAtom":"[data-gala-page-kind=\"error\"]"},{"hookId":"page-index","selectorAtom":"[data-gala-page-kind=\"index\"]"},{"hookId":"page-page","selectorAtom":"[data-gala-page-kind=\"page\"]"},{"hookId":"page-profile","selectorAtom":"[data-gala-page-kind=\"profile\"]"},{"hookId":"page-series","selectorAtom":"[data-gala-page-kind=\"series\"]"},{"hookId":"page-tag","selectorAtom":"[data-gala-page-kind=\"tag\"]"},{"hookId":"prose-blockquote","selectorAtom":"blockquote"},{"hookId":"prose-divider","selectorAtom":"hr"},{"hookId":"prose-emphasis","selectorAtom":"em"},{"hookId":"prose-link","selectorAtom":"a"},{"hookId":"prose-list-item","selectorAtom":"li"},{"hookId":"prose-list-ordered","selectorAtom":"ol"},{"hookId":"prose-list-unordered","selectorAtom":"ul"},{"hookId":"prose-paragraph","selectorAtom":"p"},{"hookId":"prose-span","selectorAtom":"span"},{"hookId":"prose-strong","selectorAtom":"strong"},{"hookId":"prose-time","selectorAtom":"time"},{"hookId":"slot-account-intent","selectorAtom":"[data-gala-slot=\"account-intent\"]"},{"hookId":"slot-article-end","selectorAtom":"[data-gala-slot=\"article-end\"]"},{"hookId":"slot-article-footer-ad","selectorAtom":"[data-gala-slot=\"article-footer-ad\"]"},{"hookId":"slot-article-preamble","selectorAtom":"[data-gala-slot=\"article-preamble\"]"},{"hookId":"slot-conversation","selectorAtom":"[data-gala-slot=\"conversation\"]"},{"hookId":"slot-edition-selector","selectorAtom":"[data-gala-slot=\"edition-selector\"]"},{"hookId":"slot-footer-auxiliary","selectorAtom":"[data-gala-slot=\"footer-auxiliary\"]"},{"hookId":"slot-footer-profile","selectorAtom":"[data-gala-slot=\"footer-profile\"]"},{"hookId":"slot-header-actions","selectorAtom":"[data-gala-slot=\"header-actions\"]"},{"hookId":"slot-newsletter","selectorAtom":"[data-gala-slot=\"newsletter\"]"}],"publicationRootSelector":"[data-gala-publication-root]","resolvedPaletteSelectors":{"dark":"[data-gala-publication-root][data-gala-resolved-color-mode=\"dark\"]","light":"[data-gala-publication-root][data-gala-resolved-color-mode=\"light\"]"},"stylesheetLayers":{"components":"gala-components","print":"gala-print","tokens":"gala-tokens","utilities":"gala-utilities"},"templatePackage":"@rathnasgala2/template","templateVersion":"2.1.0","typeSelectors":["a","article","blockquote","code","em","footer","h1","h2","h3","h4","h5","h6","header","hr","img","label","li","main","nav","ol","option","p","pre","select","span","strong","time","ul"]}
|