@homepages/template-kit 0.0.0 → 0.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/CHANGELOG.md +182 -0
- package/README.md +71 -14
- package/dist/base.css +85 -0
- package/dist/browser.d.ts +26 -0
- package/dist/browser.js +55 -0
- package/dist/cli.js +1 -1
- package/dist/contracts/image-descriptor.d.ts +17 -0
- package/dist/contracts/markers.d.ts +20 -0
- package/dist/contracts/markers.js +21 -0
- package/dist/contracts/slot-catalog.d.ts +237 -0
- package/dist/contracts/slot-catalog.js +247 -0
- package/dist/contracts/transforms.d.ts +108 -0
- package/dist/contracts/transforms.js +117 -0
- package/dist/design-system/breakpoints.d.ts +5 -0
- package/dist/design-system/breakpoints.js +14 -0
- package/dist/design-system/theme.d.ts +307 -0
- package/dist/design-system/theme.js +95 -0
- package/dist/eslint/is-client-file.js +79 -0
- package/dist/eslint/rules/image-bare-needs-reason.js +38 -0
- package/dist/eslint/rules/no-client-directive-in-contract.js +26 -0
- package/dist/eslint/rules/no-client-runtime-in-server.js +99 -0
- package/dist/eslint/rules/no-css-import-from-render-path.js +23 -0
- package/dist/eslint/rules/no-hex.js +111 -0
- package/dist/eslint/rules/no-inline-style.js +23 -0
- package/dist/eslint/rules/no-nondeterminism.js +44 -0
- package/dist/eslint/rules/no-raw-element.js +32 -0
- package/dist/eslint/rules/props-from-schema.js +32 -0
- package/dist/eslint/rules/serializable-island-props.js +108 -0
- package/dist/eslint/rules/slot-marker-literal.js +33 -0
- package/dist/eslint.d.ts +4 -8
- package/dist/eslint.js +75 -11
- package/dist/fixtures/sample-property.d.ts +67 -0
- package/dist/fixtures/sample-property.js +707 -0
- package/dist/fixtures.d.ts +2 -0
- package/dist/fixtures.js +3 -0
- package/dist/index.d.ts +37 -13
- package/dist/index.js +34 -2
- package/dist/island-runtime.d.ts +30 -0
- package/dist/island-runtime.js +73 -0
- package/dist/islands/contract.d.ts +56 -0
- package/dist/islands/contract.js +71 -0
- package/dist/islands/marker.d.ts +16 -0
- package/dist/islands/marker.js +18 -0
- package/dist/package.js +5 -0
- package/dist/primitives/Image.d.ts +49 -0
- package/dist/primitives/Image.js +95 -0
- package/dist/primitives/Section.d.ts +22 -0
- package/dist/primitives/Section.js +26 -0
- package/dist/primitives/Slot.d.ts +50 -0
- package/dist/primitives/Slot.js +39 -0
- package/dist/primitives/SlotGroup.d.ts +38 -0
- package/dist/primitives/SlotGroup.js +24 -0
- package/dist/primitives/SlotItem.d.ts +43 -0
- package/dist/primitives/SlotItem.js +38 -0
- package/dist/primitives/index.d.ts +7 -0
- package/dist/primitives/picture-sources.d.ts +22 -0
- package/dist/primitives/picture-sources.js +52 -0
- package/dist/schema/fill-spec.d.ts +692 -0
- package/dist/schema/fill-spec.js +98 -0
- package/dist/schema/fixture-schema.d.ts +143 -0
- package/dist/schema/fixture-schema.js +113 -0
- package/dist/schema/index.d.ts +16 -0
- package/dist/schema/manifest.d.ts +527 -0
- package/dist/schema/manifest.js +73 -0
- package/dist/schema/property-facts.d.ts +44 -0
- package/dist/schema/resolve-variants.d.ts +11 -0
- package/dist/schema/resolve-variants.js +15 -0
- package/dist/schema/runtime-values.d.ts +45 -0
- package/dist/schema/section-nav.d.ts +7 -0
- package/dist/schema/section-props.d.ts +69 -0
- package/dist/schema/section-schema.d.ts +533 -0
- package/dist/schema/section-schema.js +72 -0
- package/dist/schema/slot-types.d.ts +117 -0
- package/dist/schema/slot-types.js +181 -0
- package/dist/schema/source.d.ts +13 -0
- package/dist/schema/source.js +12 -0
- package/dist/schema/synthesize-nullable.d.ts +18 -0
- package/dist/schema/synthesize-nullable.js +47 -0
- package/dist/styles.css +131 -9
- package/docs/INDEX.md +6 -4
- package/docs/eslint.md +90 -0
- package/docs/islands.md +150 -0
- package/docs/llms.txt +30 -3
- package/docs/primitives.md +214 -0
- package/docs/schema-system.md +240 -0
- package/docs/theme-and-css.md +179 -0
- package/package.json +30 -6
- package/tsconfig.base.json +16 -0
- package/tsconfig.json +2 -13
- package/dist/src-CrdHXijV.js +0 -23
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# @homepages/template-kit
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 46e13a0: `base.css` no longer applies `content-visibility: auto` to off-screen sections on published
|
|
8
|
+
pages, and no longer defines the `--tr-control-*` tokens.
|
|
9
|
+
|
|
10
|
+
`content-visibility` is now opt-in: it carries a `contain: paint` cost that clips in-section
|
|
11
|
+
overlays, and only a template author knows whether their page needs it. If your page is long
|
|
12
|
+
and image-heavy, see "Recipe: `content-visibility` on long, image-heavy pages" in
|
|
13
|
+
`docs/theme-and-css.md` for the rule to add to your own template stylesheet.
|
|
14
|
+
|
|
15
|
+
The `--tr-control-*` tokens were unreferenced and are removed.
|
|
16
|
+
|
|
17
|
+
- b08117b: `ImageValue` now models the shape a renderer actually receives, instead of a
|
|
18
|
+
CSS-framing model the runtime never implemented.
|
|
19
|
+
|
|
20
|
+
**Removed** `crop` (`{ zoom, ox, oy }`) and `focal`. Their doc comments claimed the
|
|
21
|
+
`<Image>` primitive applied them as a CSS transform / `object-position` — it never
|
|
22
|
+
did, and no renderer or primitive has ever read either field. Framing is baked into a
|
|
23
|
+
**pre-cropped derivative asset**: when an image has been cropped, the platform serves
|
|
24
|
+
that derivative as the slot's `url`, and `<Image>` is a centered `object-cover` over
|
|
25
|
+
whatever `url` points at. A renderer that read `crop` or `focal` was reading fields
|
|
26
|
+
that were always absent from the resolved value.
|
|
27
|
+
|
|
28
|
+
**Added** `crop_key` — the content-addressed key of the baked crop derivative. It was
|
|
29
|
+
already on the wire but undeclared here; it is the _provenance_ of `url`, not a second
|
|
30
|
+
thing to render.
|
|
31
|
+
|
|
32
|
+
`rect` and `ar` remain, now labelled **carried, not rendered**: they are the editor's
|
|
33
|
+
re-edit state (so the cropper can reopen at the last crop), not framing inputs.
|
|
34
|
+
|
|
35
|
+
Renderers read only `url`, `alt`, `mobile`, and `responsive`. This matches
|
|
36
|
+
`ResolvedImage`, the shape the platform emits.
|
|
37
|
+
|
|
38
|
+
- c003008: Added the **schema system** — the section authoring contract, and the first real
|
|
39
|
+
surface of this package.
|
|
40
|
+
|
|
41
|
+
A section is now fully declarable against the kit: `schema.ts` (slots + options,
|
|
42
|
+
`as const satisfies SectionSchemaShape`), `fill-spec.ts` (`FillSpecShape`),
|
|
43
|
+
`fixtures.ts` (`FixtureModule`), and a `Renderer.tsx` whose props are **derived**
|
|
44
|
+
from the schema via `SectionProps<typeof schema>` rather than hand-written — a
|
|
45
|
+
required text slot resolves to `string`, an optional one to `string | null`, an
|
|
46
|
+
enum option to the literal union of its declared values.
|
|
47
|
+
|
|
48
|
+
Slots bind to property facts through a **closed vocabulary**: `direct()` accepts
|
|
49
|
+
only a known fact name and `derived()` only a known transform, so a misspelling is
|
|
50
|
+
a compile error at the call site instead of a runtime surprise. The vocabulary
|
|
51
|
+
tables (`SOURCE_FIELDS`, `TRANSFORMS`, `SLOT_TYPE_DESCRIPTORS`) are exported, and
|
|
52
|
+
changing them is a kit release.
|
|
53
|
+
|
|
54
|
+
Also exported: fact-driven variants (`makeResolveVariants`, `TransformInput`), the
|
|
55
|
+
template composition manifest (`TemplateManifest`), the fixture builders
|
|
56
|
+
(`buildFixtureSet`), and the image/breakpoint value shapes a renderer receives.
|
|
57
|
+
|
|
58
|
+
Everything lands on the package root — `import { … } from "@homepages/template-kit"`.
|
|
59
|
+
There are no subpath imports for the authoring API.
|
|
60
|
+
|
|
61
|
+
Zod is now a runtime dependency (`^3.23.8`).
|
|
62
|
+
|
|
63
|
+
Docs: `docs/schema-system.md`.
|
|
64
|
+
|
|
65
|
+
- 07d5dc8: Added the **contract primitives, theme tokens, and the golden property fixtures** —
|
|
66
|
+
with the schema system, a section is now authorable end to end against the kit.
|
|
67
|
+
|
|
68
|
+
**Primitives.** `Section` (the full-bleed section root), `Slot` and `SlotGroup` (the
|
|
69
|
+
editor's slot markers), `Image` (responsive `<picture>`/srcset from the platform's
|
|
70
|
+
image descriptor, with a deterministic empty-state box), and a new `SlotItem`.
|
|
71
|
+
|
|
72
|
+
`SlotItem` is the per-item handle inside a collection slot (`object_list`,
|
|
73
|
+
`image_collection`). It emits `data-slot-item`, which is what makes per-item
|
|
74
|
+
selection, per-item image cropping, and per-item live surfaces work in the editor —
|
|
75
|
+
a collection without it is editable only as a whole. The attribute is now declared
|
|
76
|
+
in the marker contract (`ATTR_SLOT_ITEM`) and emitted by a primitive rather than
|
|
77
|
+
hand-written as a magic string.
|
|
78
|
+
|
|
79
|
+
The primitives are the _only_ components the kit ships, and that is deliberate:
|
|
80
|
+
each one emits something the platform breaks without. Presentation — headings,
|
|
81
|
+
links, layout wells, icons, lists — is your template's own JSX.
|
|
82
|
+
|
|
83
|
+
**Theme tokens.** `@homepages/template-kit/styles.css` carries the Tailwind v4
|
|
84
|
+
`@theme` alias layer and the breakpoint ladder; `@homepages/template-kit/base.css`
|
|
85
|
+
carries the cascade-layer order, the reset, and `.tr-section`. A template supplies
|
|
86
|
+
the values behind the aliases as a `TokenTheme` (`theme.ts`), compiled by
|
|
87
|
+
`compileThemeToCss` into the `:root { --tr-*: … }` block its bundle ships.
|
|
88
|
+
|
|
89
|
+
Import the token entry **after** Tailwind, and import Tailwind exactly once — a
|
|
90
|
+
second `@import "tailwindcss"` anywhere in the graph double-emits the preflight
|
|
91
|
+
reset:
|
|
92
|
+
|
|
93
|
+
```css
|
|
94
|
+
@import "tailwindcss";
|
|
95
|
+
@import "@homepages/template-kit/styles.css";
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Golden fixtures.** `sampleProperty` and friends (`sampleContacts`, `samplePois`,
|
|
99
|
+
`sampleAmenities`, `allPhotos`, `mkResponsive`) — a realistic 12-unit building with
|
|
100
|
+
long unit labels, a reserved unit and a missing price. Preview is only as honest as
|
|
101
|
+
the data behind it; build your `fixtures.ts` on these rather than on lorem ipsum.
|
|
102
|
+
|
|
103
|
+
**Breaking within 0.x:** the type formerly exported as `Slot` (the slot-definition
|
|
104
|
+
union) is now `SlotDefinition`. `Slot` is the primitive component.
|
|
105
|
+
|
|
106
|
+
- 35b0939: Added `@homepages/template-kit/eslint` — the flat-config ESLint preset for section
|
|
107
|
+
authoring. Eleven rules, all syntactic (no type-aware linting, so "zero extra config"
|
|
108
|
+
holds), catch the mistakes specific to writing a HomePages section template: reading
|
|
109
|
+
the clock or randomness (`no-nondeterminism`), effects/state/handlers/browser globals
|
|
110
|
+
outside a `"use client"` island (`no-client-runtime-in-server`), a prop crossing to an
|
|
111
|
+
island that can't survive JSON serialization (`serializable-island-props`), a hard-coded
|
|
112
|
+
colour where a theme token belongs (`no-hex`), and seven more covering the contract
|
|
113
|
+
files, slot markers, and raw-CSS boundaries a section must respect.
|
|
114
|
+
|
|
115
|
+
Consume it as:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
import kit from "@homepages/template-kit/eslint";
|
|
119
|
+
export default kit;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`@typescript-eslint/parser` is now a real `dependency` (not only a devDependency) —
|
|
123
|
+
the preset wires it directly so a consumer's `eslint.config.mjs` needs no parser
|
|
124
|
+
setup of its own.
|
|
125
|
+
|
|
126
|
+
- 2a32639: Islands: interactivity is now an ordinary React component marked "use client". Adds the
|
|
127
|
+
island contract and marker to the main entry, a new `./island-runtime` loader that
|
|
128
|
+
hydrates each island as its own React root, and a new `./browser` subpath with the
|
|
129
|
+
site-config and signed-ingest helpers for published pages.
|
|
130
|
+
- 96356b5: The published barrel now tree-shakes, and the golden fixtures move to their own subpath.
|
|
131
|
+
|
|
132
|
+
The build previously collapsed the primitives, the schema system, zod, the theme compiler
|
|
133
|
+
and the sample-property fixtures into a single chunk that `dist/index.js` only re-exported.
|
|
134
|
+
Because a consumer's bundler can only tree-shake at module granularity, importing one
|
|
135
|
+
primitive pulled in all of it: `import { Section }` cost **84,755 B** and inlined zod plus
|
|
136
|
+
the golden fixture dataset — agent phone numbers, listing prices, lot sizes — into every
|
|
137
|
+
section's published renderer.
|
|
138
|
+
|
|
139
|
+
The build is now unbundled (one output module per source module), so the import graph is
|
|
140
|
+
honest. `import { Section }` is **202 B**; `import { makeResolveVariants }` is **179 B**.
|
|
141
|
+
Neither carries zod or fixture data.
|
|
142
|
+
|
|
143
|
+
**Breaking:** `sampleProperty`, `sampleContacts`, `samplePois`, `sampleAmenities`,
|
|
144
|
+
`allPhotos`, `mkResponsive`, `sampleHeroSlots` and the `PhotoFixture` / `PoiFixture` /
|
|
145
|
+
`ContactFixture` / `AmenityItem` types are no longer exported from the root barrel. Import
|
|
146
|
+
them from `@homepages/template-kit/fixtures`:
|
|
147
|
+
|
|
148
|
+
```diff
|
|
149
|
+
-import { sampleProperty } from "@homepages/template-kit";
|
|
150
|
+
+import { sampleProperty } from "@homepages/template-kit/fixtures";
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Dev-only sample data must not be reachable from a renderer's import graph at all: a
|
|
154
|
+
section's published bundle is an immutable, publicly readable artifact. Import fixtures
|
|
155
|
+
from `fixtures.ts`, never from `Renderer.tsx`. Everything else on the root barrel — the
|
|
156
|
+
primitives, the schema types, `makeResolveVariants`, `compileThemeToCss`, `KIT_VERSION`,
|
|
157
|
+
the marker constants, the islands contract — is unchanged.
|
|
158
|
+
|
|
159
|
+
The kit's `styles.css` also narrows its Tailwind `@source` scan to the shipped
|
|
160
|
+
`primitives/` modules, which are the only ones that emit markup. This drops 8 dead
|
|
161
|
+
utilities and 19 stray `@property` registrations that were being compiled out of the CLI's
|
|
162
|
+
and the ESLint preset's doc comments into consumers' stylesheets.
|
|
163
|
+
|
|
164
|
+
### Patch Changes
|
|
165
|
+
|
|
166
|
+
- bb15161: Hardened the release pipeline that produces this package, with no change to the
|
|
167
|
+
authoring API. Packing or publishing now always rebuilds first, so the tarball
|
|
168
|
+
can never ship a stale `dist/`. The tsconfig preset shipped at
|
|
169
|
+
`@homepages/template-kit/tsconfig` no longer leaks this repo's own `exclude`
|
|
170
|
+
list or forces `noEmit` onto consumers who extend it. `CHANGELOG.md` is now
|
|
171
|
+
included in the published tarball, so you can read what changed in a given
|
|
172
|
+
version without leaving `node_modules`. The docs shipped alongside the package
|
|
173
|
+
no longer reference this project's private internal repo layout.
|
|
174
|
+
|
|
175
|
+
## 0.0.1
|
|
176
|
+
|
|
177
|
+
### Patch Changes
|
|
178
|
+
|
|
179
|
+
- c1d3127: Package skeleton: the published boundary and its hygiene gates. Ships the exports
|
|
180
|
+
map (root entry, ESLint flat-config preset, tsconfig preset, Tailwind v4 tokens
|
|
181
|
+
CSS), the `template-kit` CLI with its commands stubbed, and the changesets → npm
|
|
182
|
+
trusted-publishing release pipeline. No authoring surface yet.
|
package/README.md
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
The authoring surface for HomePages marketing-section templates: schema system,
|
|
4
4
|
contract primitives, theme tokens, and the `template-kit` CLI.
|
|
5
5
|
|
|
6
|
-
**Status: 0.0.x
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
**Status: 0.0.x.** The **schema system** (the section authoring contract and the closed
|
|
7
|
+
vocabulary it validates against), the **contract primitives**, and the **theme tokens**
|
|
8
|
+
are complete — a section is authorable end to end against the kit. The `template-kit`
|
|
9
|
+
CLI's commands are still being extracted.
|
|
9
10
|
|
|
10
11
|
## Install
|
|
11
12
|
|
|
@@ -15,35 +16,90 @@ npm install @homepages/template-kit react react-dom
|
|
|
15
16
|
|
|
16
17
|
React 19 is a **peer dependency** — the kit never bundles its own copy.
|
|
17
18
|
|
|
19
|
+
## Authoring a section
|
|
20
|
+
|
|
21
|
+
A section is four files: `schema.ts` (slots + options), `fill-spec.ts` (how AI
|
|
22
|
+
fills them), `fixtures.ts` (preview content), and `Renderer.tsx` (markup, with
|
|
23
|
+
props **derived** from the schema).
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { direct, type SectionProps, type SectionSchemaShape } from "@homepages/template-kit";
|
|
27
|
+
|
|
28
|
+
export const schema = {
|
|
29
|
+
meta: { displayName: "Hero" },
|
|
30
|
+
slots: {
|
|
31
|
+
headline: { type: "text", required: true, produced_by: "headline_text" },
|
|
32
|
+
listing_status: { type: "select", required: false, source: direct("listing_intent") },
|
|
33
|
+
},
|
|
34
|
+
} as const satisfies SectionSchemaShape;
|
|
35
|
+
|
|
36
|
+
export type Props = SectionProps<typeof schema>; // slots.headline is `string`
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Property facts come from a closed vocabulary, so `direct("listing_intentt")` is a
|
|
40
|
+
compile error, not a runtime surprise. Full contract:
|
|
41
|
+
**[docs/schema-system.md](docs/schema-system.md)**.
|
|
42
|
+
|
|
43
|
+
Your fixtures build on the kit's golden property dataset (`sampleProperty`, `allPhotos`,
|
|
44
|
+
`samplePois`, …) — a realistic 12-unit building, long labels and missing values included.
|
|
45
|
+
|
|
46
|
+
## The primitives
|
|
47
|
+
|
|
48
|
+
The kit ships **five components and nothing else**: `Section` (the full-bleed section
|
|
49
|
+
root), `Slot` and `SlotItem` and `SlotGroup` (the editor's marker attributes), and
|
|
50
|
+
`Image` (responsive `<picture>` + a deterministic empty-state box). Each earns its place
|
|
51
|
+
by emitting something the platform breaks without. Presentation — headings, links,
|
|
52
|
+
layout, icons — is your template's own JSX.
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { Image, Section, Slot, SlotItem } from "@homepages/template-kit";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Full contract: **[docs/primitives.md](docs/primitives.md)**.
|
|
59
|
+
|
|
18
60
|
## Exports
|
|
19
61
|
|
|
20
62
|
| Entry | Use |
|
|
21
63
|
|---|---|
|
|
22
|
-
| `@homepages/template-kit` | Authoring API
|
|
23
|
-
| `@homepages/template-kit/
|
|
64
|
+
| `@homepages/template-kit` | Authoring API — schema system, primitives, marker contract, theme types, and the golden fixtures |
|
|
65
|
+
| `@homepages/template-kit/island-runtime` | Browser loader that hydrates a page's islands |
|
|
66
|
+
| `@homepages/template-kit/browser` | Browser helpers for published pages (site config, signed ingest) |
|
|
67
|
+
| `@homepages/template-kit/styles.css` | Tailwind v4 theme tokens, the breakpoint ladder, kit source registration |
|
|
68
|
+
| `@homepages/template-kit/base.css` | The page's base stylesheet: layer order, reset, `.tr-section`, universal tokens |
|
|
24
69
|
| `@homepages/template-kit/eslint` | ESLint flat-config preset |
|
|
25
70
|
| `@homepages/template-kit/tsconfig` | tsconfig preset (`"extends"`) |
|
|
26
71
|
|
|
27
|
-
The
|
|
72
|
+
The authoring API has **no subpath imports** — one specifier for everything you
|
|
73
|
+
write a section against. The two CSS entries are the deliberate exception.
|
|
74
|
+
|
|
75
|
+
`styles.css` does **not** import Tailwind. Import it *after* Tailwind in your entry:
|
|
28
76
|
|
|
29
77
|
```css
|
|
30
78
|
@import "tailwindcss";
|
|
31
79
|
@import "@homepages/template-kit/styles.css";
|
|
32
80
|
```
|
|
33
81
|
|
|
34
|
-
Two `@import "tailwindcss"` in one graph double-emits preflight.
|
|
82
|
+
Two `@import "tailwindcss"` in one graph double-emits preflight. `base.css` is not part
|
|
83
|
+
of that entry — it loads as its own stylesheet on the page. Full contract:
|
|
84
|
+
**[docs/theme-and-css.md](docs/theme-and-css.md)**.
|
|
35
85
|
|
|
36
86
|
## Development
|
|
37
87
|
|
|
38
88
|
```bash
|
|
39
89
|
npm install
|
|
40
|
-
npm run check # typecheck + lint +
|
|
90
|
+
npm run check # typecheck + lint + build + island-size gate + test + publint + attw + consumer gate
|
|
41
91
|
```
|
|
42
92
|
|
|
43
93
|
`npm run verify:consumer` is the boundary gate: it packs the kit, installs the
|
|
44
|
-
tarball into a scratch project, and asserts that types resolve
|
|
45
|
-
|
|
46
|
-
|
|
94
|
+
tarball into a scratch project, and asserts that types resolve; that a real section
|
|
95
|
+
triple typechecks against the installed package, with a misspelled property fact or
|
|
96
|
+
slot key still failing to compile; that Tailwind's cross-package `@source` scan
|
|
97
|
+
reaches the shipped `dist`; and that only one React ends up in the tree. Run it
|
|
98
|
+
before every release.
|
|
99
|
+
|
|
100
|
+
The island loader (`@homepages/template-kit/island-runtime`) has its own gate,
|
|
101
|
+
`npm run size:islands`: it must stay under 3072 B gzip and never import
|
|
102
|
+
`react`/`react-dom` statically, so a page with no islands loads no React.
|
|
47
103
|
|
|
48
104
|
## Releasing
|
|
49
105
|
|
|
@@ -71,6 +127,7 @@ optional capability is a **minor**.
|
|
|
71
127
|
|
|
72
128
|
### Bumping the version by hand
|
|
73
129
|
|
|
74
|
-
Don't
|
|
75
|
-
|
|
76
|
-
|
|
130
|
+
Don't — changesets owns `package.json`'s version. There is nothing else to keep
|
|
131
|
+
in step with it: `KIT_VERSION` is read from `package.json` and inlined at build
|
|
132
|
+
time, and the test suite asserts `template-kit --version` against `package.json`
|
|
133
|
+
to prove the inlining happened.
|
package/dist/base.css
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
@layer theme, base, components, sections, template, utilities;
|
|
2
|
+
|
|
3
|
+
/* @homepages/template-kit — the base stylesheet every template's page loads.
|
|
4
|
+
*
|
|
5
|
+
* Exported as `@homepages/template-kit/base.css`. It is NOT part of the Tailwind
|
|
6
|
+
* entry (that is styles.css); it ships as its own stylesheet on the page.
|
|
7
|
+
*
|
|
8
|
+
* Contains:
|
|
9
|
+
* - The cascade-layer order declaration above — the one place it is stated
|
|
10
|
+
* - The @layer base reset
|
|
11
|
+
* - Document defaults (html/body)
|
|
12
|
+
* - The section box model (.tr-section, emitted by the <Section> primitive)
|
|
13
|
+
* - Universal tokens: the spacing scale and motion
|
|
14
|
+
*
|
|
15
|
+
* NOT here: anything a template owns. Colors, fonts, type, radii, shadows and
|
|
16
|
+
* layout all come from your theme.ts, compiled by compileThemeToCss into a
|
|
17
|
+
* `:root { --tr-*: … }` block. Component styling is Tailwind utilities written
|
|
18
|
+
* on the elements themselves.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/* ----- Reset (lightweight; assumes modern browsers) --------------------- *
|
|
22
|
+
* Wrapped in `@layer base` so Tailwind's utilities (which compile into
|
|
23
|
+
* `@layer utilities`) win over these element defaults. Unlayered, a rule like
|
|
24
|
+
* `a { color: inherit }` would beat the layered `.text-ink` utility outright —
|
|
25
|
+
* unlayered always wins over layered in the cascade.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
@layer base {
|
|
29
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
30
|
+
html, body { margin: 0; padding: 0; }
|
|
31
|
+
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
|
32
|
+
img, svg, video { display: block; max-width: 100%; }
|
|
33
|
+
button { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; color: inherit; }
|
|
34
|
+
a { color: inherit; text-decoration: none; }
|
|
35
|
+
ul, ol { margin: 0; padding: 0; list-style: none; }
|
|
36
|
+
h1, h2, h3, h4, h5, h6, p { margin: 0; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ----- Document defaults ------------------------------------------------ */
|
|
40
|
+
|
|
41
|
+
html { scroll-behavior: smooth; scroll-padding-top: var(--tr-header-height); }
|
|
42
|
+
body {
|
|
43
|
+
font-family: var(--tr-font-sans);
|
|
44
|
+
color: var(--tr-color-ink);
|
|
45
|
+
background: var(--tr-color-background);
|
|
46
|
+
font-size: var(--tr-text-base);
|
|
47
|
+
line-height: 1.5;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ----- Primitives ------------------------------------------------------- */
|
|
51
|
+
|
|
52
|
+
/* The full-bleed section root, emitted by the <Section> primitive. Always 100%
|
|
53
|
+
* wide with zero padding/margin, so adjacent sections butt edge-to-edge and a page
|
|
54
|
+
* composes as a vertical stack with no framework-injected whitespace. A section
|
|
55
|
+
* brings its own background and internal padding. */
|
|
56
|
+
.tr-section {
|
|
57
|
+
display: block;
|
|
58
|
+
width: 100%;
|
|
59
|
+
padding: 0;
|
|
60
|
+
margin: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ----- Universal tokens ------------------------------------------------- */
|
|
64
|
+
|
|
65
|
+
:root {
|
|
66
|
+
/* Spacing scale */
|
|
67
|
+
--tr-space-1: 0.25rem;
|
|
68
|
+
--tr-space-2: 0.5rem;
|
|
69
|
+
--tr-space-3: 0.75rem;
|
|
70
|
+
--tr-space-4: 1rem;
|
|
71
|
+
--tr-space-5: 1.25rem;
|
|
72
|
+
--tr-space-6: 1.5rem;
|
|
73
|
+
--tr-space-8: 2rem;
|
|
74
|
+
--tr-space-10: 2.5rem;
|
|
75
|
+
--tr-space-12: 3rem;
|
|
76
|
+
--tr-space-16: 4rem;
|
|
77
|
+
--tr-space-20: 5rem;
|
|
78
|
+
--tr-space-24: 6rem;
|
|
79
|
+
--tr-space-32: 8rem;
|
|
80
|
+
|
|
81
|
+
/* Motion */
|
|
82
|
+
--tr-duration-fast: 150ms;
|
|
83
|
+
--tr-duration-base: 250ms;
|
|
84
|
+
--tr-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
85
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/browser/site-config.d.ts
|
|
2
|
+
interface SiteConfig {
|
|
3
|
+
siteId: string;
|
|
4
|
+
siteSecret: string;
|
|
5
|
+
ingestBase: string;
|
|
6
|
+
}
|
|
7
|
+
/** Read the per-site config a published page embeds as `#tr-site-config`. Null when absent or incomplete. */
|
|
8
|
+
declare function readSiteConfig(): SiteConfig | null;
|
|
9
|
+
/**
|
|
10
|
+
* Decode hex to bytes. Always ArrayBuffer-backed (never SharedArrayBuffer), so the
|
|
11
|
+
* result satisfies the BufferSource parameter of crypto.subtle.importKey — but that
|
|
12
|
+
* generic stays out of this public signature: on TypeScript < 5.7, `Uint8Array` isn't
|
|
13
|
+
* generic at all, and writing `Uint8Array<ArrayBuffer>` here would fail every
|
|
14
|
+
* consumer's build below that floor (TS2315) for a detail only this module's own
|
|
15
|
+
* `crypto.subtle.importKey` call needs.
|
|
16
|
+
*/
|
|
17
|
+
declare function hexToBytes(hex: string): Uint8Array;
|
|
18
|
+
declare function bytesToHex(buf: ArrayBuffer): string;
|
|
19
|
+
declare function hmacHex(secretHex: string, message: string): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* The signed header set every ingest request carries:
|
|
22
|
+
* `X-Signature` = hex(hmac_sha256(siteSecret, `${unixSeconds}.${body}`)).
|
|
23
|
+
*/
|
|
24
|
+
declare function signedIngestHeaders(config: SiteConfig, body: string): Promise<Record<string, string>>;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { SiteConfig, bytesToHex, hexToBytes, hmacHex, readSiteConfig, signedIngestHeaders };
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region src/browser/site-config.ts
|
|
2
|
+
/** Read the per-site config a published page embeds as `#tr-site-config`. Null when absent or incomplete. */
|
|
3
|
+
function readSiteConfig() {
|
|
4
|
+
const el = document.getElementById("tr-site-config");
|
|
5
|
+
if (!el?.textContent) return null;
|
|
6
|
+
try {
|
|
7
|
+
const config = JSON.parse(el.textContent);
|
|
8
|
+
if (!config.siteId || !config.siteSecret || !config.ingestBase) return null;
|
|
9
|
+
return config;
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Decode hex to bytes. Always ArrayBuffer-backed (never SharedArrayBuffer), so the
|
|
16
|
+
* result satisfies the BufferSource parameter of crypto.subtle.importKey — but that
|
|
17
|
+
* generic stays out of this public signature: on TypeScript < 5.7, `Uint8Array` isn't
|
|
18
|
+
* generic at all, and writing `Uint8Array<ArrayBuffer>` here would fail every
|
|
19
|
+
* consumer's build below that floor (TS2315) for a detail only this module's own
|
|
20
|
+
* `crypto.subtle.importKey` call needs.
|
|
21
|
+
*/
|
|
22
|
+
function hexToBytes(hex) {
|
|
23
|
+
if (hex.length % 2 !== 0) throw new Error("hexToBytes: odd-length hex string");
|
|
24
|
+
if (!/^[0-9a-fA-F]*$/.test(hex)) throw new Error("hexToBytes: invalid hex string");
|
|
25
|
+
const out = new Uint8Array(/* @__PURE__ */ new ArrayBuffer(hex.length / 2));
|
|
26
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
function bytesToHex(buf) {
|
|
30
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
31
|
+
}
|
|
32
|
+
async function hmacHex(secretHex, message) {
|
|
33
|
+
const key = await crypto.subtle.importKey("raw", hexToBytes(secretHex), {
|
|
34
|
+
name: "HMAC",
|
|
35
|
+
hash: "SHA-256"
|
|
36
|
+
}, false, ["sign"]);
|
|
37
|
+
return bytesToHex(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message)));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The signed header set every ingest request carries:
|
|
41
|
+
* `X-Signature` = hex(hmac_sha256(siteSecret, `${unixSeconds}.${body}`)).
|
|
42
|
+
*/
|
|
43
|
+
async function signedIngestHeaders(config, body) {
|
|
44
|
+
const ts = Math.floor(Date.now() / 1e3).toString();
|
|
45
|
+
const signature = await hmacHex(config.siteSecret, `${ts}.${body}`);
|
|
46
|
+
return {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
"X-Site-Id": config.siteId,
|
|
49
|
+
"X-Timestamp": ts,
|
|
50
|
+
"X-Signature": signature
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { bytesToHex, hexToBytes, hmacHex, readSiteConfig, signedIngestHeaders };
|
package/dist/cli.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/contracts/image-descriptor.d.ts
|
|
2
|
+
interface ResponsiveSource {
|
|
3
|
+
/** Image MIME type: 'image/avif' | 'image/webp' | 'image/jpeg' | 'image/png'. */
|
|
4
|
+
type: string;
|
|
5
|
+
/** "<presigned-url> 320w, … 2048w" — rungs ascending by ACTUAL width. */
|
|
6
|
+
srcset: string;
|
|
7
|
+
}
|
|
8
|
+
interface ResponsiveImage {
|
|
9
|
+
/** Per-format candidate sets, ordered preferred→fallback: avif, webp, jpeg, png. */
|
|
10
|
+
sources: ResponsiveSource[];
|
|
11
|
+
/** Ceiling-rung (MAX edge) actual width — drives <img> width/aspect/sizes. */
|
|
12
|
+
width: number;
|
|
13
|
+
/** Ceiling-rung actual height. */
|
|
14
|
+
height: number;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { ResponsiveImage, ResponsiveSource };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/contracts/markers.d.ts
|
|
2
|
+
/** Wrapper marker the editor resolves a slot from (`Slot`, primitive `slotId`). */
|
|
3
|
+
declare const ATTR_SLOT_ID = "data-slot-id";
|
|
4
|
+
/** Marks the immediate text-leaf child of a `textLeaf` slot. */
|
|
5
|
+
declare const ATTR_SLOT_TEXT_LEAF = "data-slot-text-leaf";
|
|
6
|
+
/** Wraps a set of slots edited/regenerated as one unit (`SlotGroup`). */
|
|
7
|
+
declare const ATTR_SLOT_GROUP = "data-slot-group";
|
|
8
|
+
/**
|
|
9
|
+
* Zero-based index of one item inside a collection slot (`SlotItem`) — the
|
|
10
|
+
* per-item handle for `object_list` / `image_collection` slots. The editor
|
|
11
|
+
* resolves per-item selection, live enhancer surfaces, and per-item image
|
|
12
|
+
* crops against it, scoped to the enclosing `ATTR_SLOT_ID` element.
|
|
13
|
+
*/
|
|
14
|
+
declare const ATTR_SLOT_ITEM = "data-slot-item";
|
|
15
|
+
/** Per-section-instance identity on the section root. */
|
|
16
|
+
declare const ATTR_SECTION_INSTANCE_ID = "data-section-instance-id";
|
|
17
|
+
/** Opt-in hook telling an enhancer to attach to this element. */
|
|
18
|
+
declare const ATTR_TR_ENHANCE = "data-tr-enhance";
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ATTR_SECTION_INSTANCE_ID, ATTR_SLOT_GROUP, ATTR_SLOT_ID, ATTR_SLOT_ITEM, ATTR_SLOT_TEXT_LEAF, ATTR_TR_ENHANCE };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/contracts/markers.ts
|
|
2
|
+
/** Wrapper marker the editor resolves a slot from (`Slot`, primitive `slotId`). */
|
|
3
|
+
const ATTR_SLOT_ID = "data-slot-id";
|
|
4
|
+
/** Marks the immediate text-leaf child of a `textLeaf` slot. */
|
|
5
|
+
const ATTR_SLOT_TEXT_LEAF = "data-slot-text-leaf";
|
|
6
|
+
/** Wraps a set of slots edited/regenerated as one unit (`SlotGroup`). */
|
|
7
|
+
const ATTR_SLOT_GROUP = "data-slot-group";
|
|
8
|
+
/**
|
|
9
|
+
* Zero-based index of one item inside a collection slot (`SlotItem`) — the
|
|
10
|
+
* per-item handle for `object_list` / `image_collection` slots. The editor
|
|
11
|
+
* resolves per-item selection, live enhancer surfaces, and per-item image
|
|
12
|
+
* crops against it, scoped to the enclosing `ATTR_SLOT_ID` element.
|
|
13
|
+
*/
|
|
14
|
+
const ATTR_SLOT_ITEM = "data-slot-item";
|
|
15
|
+
/** Per-section-instance identity on the section root. */
|
|
16
|
+
const ATTR_SECTION_INSTANCE_ID = "data-section-instance-id";
|
|
17
|
+
/** Opt-in hook telling an enhancer to attach to this element. */
|
|
18
|
+
const ATTR_TR_ENHANCE = "data-tr-enhance";
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { ATTR_SECTION_INSTANCE_ID, ATTR_SLOT_GROUP, ATTR_SLOT_ID, ATTR_SLOT_ITEM, ATTR_SLOT_TEXT_LEAF, ATTR_TR_ENHANCE };
|