@homepages/template-kit 0.4.0 → 0.5.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 +96 -0
- package/README.md +38 -27
- package/dist/base.css +6 -4
- package/dist/cli/dev/fill-state.js +5 -3
- package/dist/cli/link/overlay.js +1 -1
- package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +7 -7
- package/dist/contracts/slot-catalog.d.ts +2 -2
- package/dist/contracts/slot-catalog.js +3 -1
- package/dist/eslint/rules/no-raw-element.js +3 -2
- package/dist/fixtures/helpers.d.ts +23 -0
- package/dist/fixtures/helpers.js +50 -0
- package/dist/fixtures/sample-property.d.ts +7 -17
- package/dist/fixtures/sample-property.js +179 -57
- package/dist/fixtures/scenario.d.ts +20 -0
- package/dist/fixtures/scenarios/for-rent-condo.d.ts +5 -0
- package/dist/fixtures/scenarios/for-rent-condo.js +364 -0
- package/dist/fixtures/scenarios/index.d.ts +8 -0
- package/dist/fixtures/scenarios/index.js +13 -0
- package/dist/fixtures/scenarios/luxury-multi-unit.d.ts +5 -0
- package/dist/fixtures/scenarios/luxury-multi-unit.js +19 -0
- package/dist/fixtures/scenarios/sparse-single-family.d.ts +5 -0
- package/dist/fixtures/scenarios/sparse-single-family.js +233 -0
- package/dist/fixtures.d.ts +8 -2
- package/dist/fixtures.js +7 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/island-runtime.js +22 -7
- package/dist/islands/wrap.d.ts +1 -0
- package/dist/islands/wrap.js +1 -1
- package/dist/package.js +1 -1
- package/dist/primitives/Image.d.ts +1 -1
- package/dist/primitives/Video.d.ts +38 -0
- package/dist/primitives/Video.js +63 -0
- package/dist/primitives/index.d.ts +2 -1
- package/dist/rules/registry.js +3 -3
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/runtime-values.d.ts +20 -1
- package/dist/schema/section-props.d.ts +8 -1
- package/dist/schema/section-schema.d.ts +2 -2
- package/dist/schema/slot-types.d.ts +15 -1
- package/dist/schema/slot-types.js +34 -0
- package/dist/schema/synthesize-nullable.js +1 -0
- package/{docs → guide}/INDEX.md +7 -6
- package/{docs → guide}/check.md +2 -2
- package/{docs → guide}/islands.md +6 -1
- package/{docs → guide}/llms.txt +34 -14
- package/{docs → guide}/overview.md +4 -4
- package/{docs → guide}/pack.md +2 -2
- package/{docs → guide}/primitives.md +59 -11
- package/{docs → guide}/recipes/INDEX.md +1 -0
- package/{docs → guide}/recipes/collection-slot.md +10 -8
- package/guide/recipes/fixture-states.md +109 -0
- package/{docs → guide}/recipes/image-slot-crop.md +5 -3
- package/guide/recipes/pick-a-scenario.md +119 -0
- package/{docs → guide}/rules/INDEX.md +2 -2
- package/{docs → guide}/rules/fixtures-invalid.md +2 -2
- package/{docs → guide}/rules/no-raw-element.md +13 -8
- package/{docs → guide}/rules/render-invariant.md +4 -3
- package/{docs → guide}/schema-system.md +97 -16
- package/{docs → guide}/theme-and-css.md +3 -2
- package/guide/upgrading.md +61 -0
- package/{docs → guide}/vocabulary.md +3 -1
- package/package.json +9 -3
- package/dist/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +0 -77
- package/dist/node_modules/magic-string/dist/magic-string.es.js +0 -939
- package/docs/recipes/fixture-states.md +0 -68
- /package/{docs → guide}/assets.md +0 -0
- /package/{docs → guide}/dev.md +0 -0
- /package/{docs → guide}/eslint.md +0 -0
- /package/{docs → guide}/new.md +0 -0
- /package/{docs → guide}/quickstart.md +0 -0
- /package/{docs → guide}/recipes/bind-property-fact.md +0 -0
- /package/{docs → guide}/recipes/fill-spec-decision.md +0 -0
- /package/{docs → guide}/recipes/interactive-island.md +0 -0
- /package/{docs → guide}/recipes/organize-section-folder.md +0 -0
- /package/{docs → guide}/recipes/prepare-submission.md +0 -0
- /package/{docs → guide}/recipes/second-template.md +0 -0
- /package/{docs → guide}/recipes/select-slot.md +0 -0
- /package/{docs → guide}/recipes/static-asset.md +0 -0
- /package/{docs → guide}/recipes/third-party-package.md +0 -0
- /package/{docs → guide}/rules/audit-severity.md +0 -0
- /package/{docs → guide}/rules/bundle-binary-asset.md +0 -0
- /package/{docs → guide}/rules/bundle-incomplete.md +0 -0
- /package/{docs → guide}/rules/css-reason.md +0 -0
- /package/{docs → guide}/rules/determinism-drift.md +0 -0
- /package/{docs → guide}/rules/image-bare-needs-reason.md +0 -0
- /package/{docs → guide}/rules/license-denied.md +0 -0
- /package/{docs → guide}/rules/lockfile-missing.md +0 -0
- /package/{docs → guide}/rules/lockfile-stale.md +0 -0
- /package/{docs → guide}/rules/manifest-invalid.md +0 -0
- /package/{docs → guide}/rules/missing-slot-marker.md +0 -0
- /package/{docs → guide}/rules/no-bare-css-import.md +0 -0
- /package/{docs → guide}/rules/no-client-directive-in-contract.md +0 -0
- /package/{docs → guide}/rules/no-client-runtime-in-server.md +0 -0
- /package/{docs → guide}/rules/no-css-import-from-render-path.md +0 -0
- /package/{docs → guide}/rules/no-hex.md +0 -0
- /package/{docs → guide}/rules/no-inline-style.md +0 -0
- /package/{docs → guide}/rules/no-nondeterminism.md +0 -0
- /package/{docs → guide}/rules/no-templates.md +0 -0
- /package/{docs → guide}/rules/parse-error.md +0 -0
- /package/{docs → guide}/rules/props-from-schema.md +0 -0
- /package/{docs → guide}/rules/schema-invalid.md +0 -0
- /package/{docs → guide}/rules/serializable-island-props.md +0 -0
- /package/{docs → guide}/rules/server-vs-client.md +0 -0
- /package/{docs → guide}/rules/sidebar-order.md +0 -0
- /package/{docs → guide}/rules/single-react.md +0 -0
- /package/{docs → guide}/rules/size-assets.md +0 -0
- /package/{docs → guide}/rules/size-island-bundle.md +0 -0
- /package/{docs → guide}/rules/size-renderer-bundle.md +0 -0
- /package/{docs → guide}/rules/size-section-css.md +0 -0
- /package/{docs → guide}/rules/slot-group-marker.md +0 -0
- /package/{docs → guide}/rules/slot-marker-literal.md +0 -0
- /package/{docs → guide}/rules/typecheck.md +0 -0
- /package/{docs → guide}/rules/unlayered-fence.md +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# @homepages/template-kit
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ac0d622: Fixtures: replace the single `sampleProperty` dataset with three named scenarios
|
|
8
|
+
(`scenarios.luxuryMultiUnit` / `sparseSingleFamily` / `forRentCondo`) plus fact-level
|
|
9
|
+
helpers (`primaryContact`, `contactCards`, `poisByCategory`, `propertyMetrics`). The raw
|
|
10
|
+
single-property exports (`sampleProperty`, `allPhotos`, `samplePois`, `sampleContacts`,
|
|
11
|
+
`sampleAmenities`, `sampleHeroSlots`) are removed — build fixtures from a scenario.
|
|
12
|
+
`mkResponsive` and the data types are unchanged.
|
|
13
|
+
|
|
14
|
+
A scenario's org name and logos live on `ContactFixture["fields"]`
|
|
15
|
+
(`org_name`/`logo_light`/`logo_dark`), matching where the real facts schema and
|
|
16
|
+
`SOURCE_FIELDS` (`logo_dark`/`logo_light`, `container: "contact"`) put them — there is
|
|
17
|
+
no separate `brand` field or `BrandFixture` type. `propertyMetrics`'s cross-unit
|
|
18
|
+
aggregates (`bedsMin`/`bedsMax`/`bathsMin`/`bathsMax`/`sqftMax`) are `string | null`,
|
|
19
|
+
matching the canonical `units.bedsMin`/etc. transforms' formatted output (e.g. `"3,800"`,
|
|
20
|
+
not `3800`); `beds`/`baths`/`sqft`/`price` (the lowest-position unit's raw figures) are
|
|
21
|
+
unchanged.
|
|
22
|
+
|
|
23
|
+
- 0907653: The shipped authoring corpus moved from `docs/` to `guide/` inside the published
|
|
24
|
+
package (e.g. `node_modules/@homepages/template-kit/guide/quickstart.md`). Every
|
|
25
|
+
rule-id doc path (`docsPathForRule`, printed in `check`/lint failures) and the
|
|
26
|
+
`link`/`unlink`/`status` overlay now resolve against `guide/` as well. Update any
|
|
27
|
+
tooling or bookmarks that hardcoded the old `docs/` path.
|
|
28
|
+
- a0712da: Add the `video` and `video_collection` slot types and the `<Video>` primitive — hosted
|
|
29
|
+
video a section declares like an image, not a link to a third-party player.
|
|
30
|
+
|
|
31
|
+
A `video` slot's value is `{ video_id, url, poster, alt, width?, height? }`; a
|
|
32
|
+
`video_collection` is an array of those. `url` always points at a single transcoded
|
|
33
|
+
progressive MP4 rendition, so — unlike `ImageValue` — there is no `responsive` ladder and
|
|
34
|
+
no `mobile` variant. Video slots have **no cropping or editing of any kind**: a video is
|
|
35
|
+
selected, never edited, so none of `ImageValue`'s `rect`/`ar`/`crop_key` re-edit state
|
|
36
|
+
exists, and authoring `crop` on a video slot is rejected. Defaults are non-null
|
|
37
|
+
(`{ url: "", poster: "", alt: "" }` and `[]`) so a renderer with a non-nullable slot
|
|
38
|
+
cannot crash on `slots.x.url`.
|
|
39
|
+
|
|
40
|
+
Playback is **author-declared per slot** via a `playback` config alongside `frame` —
|
|
41
|
+
a silent hero loop and a narrated tour need opposite behavior and the platform cannot
|
|
42
|
+
infer which from the asset:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
hero_loop: {
|
|
46
|
+
type: "video",
|
|
47
|
+
frame: { breakpoint: 768, desktop: { aspect: 16 / 9 } },
|
|
48
|
+
playback: { autoplay: true, muted: true, loop: true, controls: false, preload: "auto" },
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**`autoplay` requires `muted`** — every browser blocks unmuted autoplay, so declaring one
|
|
53
|
+
without the other fails `check` rather than shipping a video that silently never plays.
|
|
54
|
+
`preload: "none"` is the bandwidth lever for a below-fold video most visitors never reach.
|
|
55
|
+
|
|
56
|
+
**Upgrading: raw `<video>` is now banned.** `<Video>` is the sole lint-legal emitter of
|
|
57
|
+
the tag — `no-raw-element` bans it exactly as it does `<img>`, with no island exemption.
|
|
58
|
+
If a section of yours writes a raw `<video>`, `check` will fail until it moves to
|
|
59
|
+
`<Video>`; that is the one backward-incompatible part of this release.
|
|
60
|
+
|
|
61
|
+
`<Video>` renders the poster, passes playback through verbatim,
|
|
62
|
+
and — like `<Image>` — keeps its aspect-ratio box and fallback rect when `url` is empty,
|
|
63
|
+
which is what both an unfilled slot and a still-transcoding video look like. With no
|
|
64
|
+
`playback` declared it falls back to a plain controls player.
|
|
65
|
+
|
|
66
|
+
### Patch Changes
|
|
67
|
+
|
|
68
|
+
- aaa2e2f: Islands: `hydrateIslands({ applyEditorProps: true })` now hydrates an island's
|
|
69
|
+
server-rendered HTML instead of discarding it. Applying `editor.props` previously issued a
|
|
70
|
+
root-level re-render before hydration had flushed, which made React drop the SSR tree,
|
|
71
|
+
client-render the island from scratch, and log `This root received an early update, before
|
|
72
|
+
anything was able hydrate` straight to the console — where the loader's `onError` could not
|
|
73
|
+
intercept it. The props are now merged in a layout effect after the hydration commit, so
|
|
74
|
+
the SSR tree survives and the console stays clean.
|
|
75
|
+
|
|
76
|
+
Because the island is now hydrated rather than replaced, `editor.props` arrive as an update
|
|
77
|
+
after mount rather than as part of the initial mount. A prop read only at mount time — a
|
|
78
|
+
`useState` initializer, an `<input defaultValue>` — must be read during render instead, or
|
|
79
|
+
the editor-only value will not apply.
|
|
80
|
+
|
|
81
|
+
- c79d75a: Adds a guide page on upgrading: what a patch vs a minor release means while the kit
|
|
82
|
+
is pre-1.0, why a 0.x caret cannot cross a minor on its own, and how long submissions
|
|
83
|
+
built on the previous kit line keep being accepted. `npm create homepages-workspace` is
|
|
84
|
+
now published, and always carries the same version as the kit it scaffolds against.
|
|
85
|
+
|
|
86
|
+
## 0.4.1
|
|
87
|
+
|
|
88
|
+
### Patch Changes
|
|
89
|
+
|
|
90
|
+
- 1e35f40: Fixes `wrapClientModuleSource`/`islandTransformEsbuildPlugin` (the `./ssr` subpath's
|
|
91
|
+
island codemod) throwing `MagicString is not a constructor` under `tsx`. `magic-string`
|
|
92
|
+
was missing from `dependencies`, so tsdown vendored an unpackaged private copy into the
|
|
93
|
+
kit's own `dist/node_modules/` (no `package.json` alongside it) instead of externalizing
|
|
94
|
+
the bare import — that copy's module type depended on ancestor-directory inference, which
|
|
95
|
+
diverges between plain Node and tsx's resolver. `magic-string` is now a declared runtime
|
|
96
|
+
dependency, so the shipped code imports it as a normal bare specifier resolved from the
|
|
97
|
+
consumer's own `node_modules`.
|
|
98
|
+
|
|
3
99
|
## 0.4.0
|
|
4
100
|
|
|
5
101
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ are complete — a section is authorable end to end against the kit. The `templa
|
|
|
9
9
|
CLI's `check`, `theme`, `dev`, and `pack` commands are implemented — see
|
|
10
10
|
[Checking a workspace](#checking-a-workspace), [Generating a theme](#generating-a-theme),
|
|
11
11
|
and [Previewing a workspace](#previewing-a-workspace) below — and `new` / `new-section`
|
|
12
|
-
scaffold a template or a section into a workspace (see [
|
|
12
|
+
scaffold a template or a section into a workspace (see [guide/new.md](guide/new.md)).
|
|
13
13
|
|
|
14
14
|
## Quick start
|
|
15
15
|
|
|
@@ -24,7 +24,7 @@ npm run dev # preview the starter; the island hydrates
|
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Add more templates with `npx template-kit new <template>` and sections with
|
|
27
|
-
`npx template-kit new-section <template>/<section>` — see [
|
|
27
|
+
`npx template-kit new-section <template>/<section>` — see [guide/new.md](guide/new.md).
|
|
28
28
|
Prefer to wire a workspace by hand? See **Install** below.
|
|
29
29
|
|
|
30
30
|
## Install
|
|
@@ -37,7 +37,7 @@ npm install -D typescript eslint @typescript-eslint/parser esbuild # the autho
|
|
|
37
37
|
React 19 is a **peer dependency** — the kit never bundles its own copy.
|
|
38
38
|
|
|
39
39
|
The kit has exactly **one runtime dependency** (`zod`). The authoring toolchain the
|
|
40
|
-
[`check` command](
|
|
40
|
+
[`check` command](guide/check.md) and the [ESLint preset](guide/eslint.md) run on is
|
|
41
41
|
yours: both resolve TypeScript, ESLint, the parser and esbuild from your workspace, so
|
|
42
42
|
your pinned versions are the ones that judge your code. The kit does not depend on them
|
|
43
43
|
at all — which is why a linter never lands in the production install of an app that
|
|
@@ -65,30 +65,34 @@ export type Props = SectionProps<typeof schema>; // slots.headline is `string`
|
|
|
65
65
|
|
|
66
66
|
Property facts come from a closed vocabulary, so `direct("listing_intentt")` is a
|
|
67
67
|
compile error, not a runtime surprise. Full contract:
|
|
68
|
-
**[
|
|
68
|
+
**[guide/schema-system.md](guide/schema-system.md)**.
|
|
69
69
|
|
|
70
|
-
Your fixtures build on the kit's golden
|
|
71
|
-
`
|
|
70
|
+
Your fixtures build on the kit's golden scenarios (`scenarios.luxuryMultiUnit`,
|
|
71
|
+
`sparseSingleFamily`, `forRentCondo`, plus helpers like `propertyMetrics()`) — real,
|
|
72
|
+
self-consistent properties spanning multi/single unit and sale/rent, long labels and
|
|
73
|
+
missing values included.
|
|
72
74
|
|
|
73
75
|
## The primitives
|
|
74
76
|
|
|
75
|
-
The kit ships **
|
|
76
|
-
root), `Slot` and `SlotItem` and `SlotGroup` (the editor's marker attributes),
|
|
77
|
-
`Image` (responsive `<picture>` + a deterministic empty-state box)
|
|
77
|
+
The kit ships **six components and nothing else**: `Section` (the full-bleed section
|
|
78
|
+
root), `Slot` and `SlotItem` and `SlotGroup` (the editor's marker attributes),
|
|
79
|
+
`Image` (responsive `<picture>` + a deterministic empty-state box), and `Video` (poster
|
|
80
|
+
frame + author-declared playback + the same empty-state box). Each earns its place
|
|
78
81
|
by emitting something the platform breaks without. Presentation — headings, links,
|
|
79
82
|
layout, icons — is your template's own JSX.
|
|
80
83
|
|
|
81
84
|
```tsx
|
|
82
|
-
import { Image, Section, Slot, SlotItem } from "@homepages/template-kit";
|
|
85
|
+
import { Image, Section, Slot, SlotItem, Video } from "@homepages/template-kit";
|
|
83
86
|
```
|
|
84
87
|
|
|
85
|
-
Full contract: **[
|
|
88
|
+
Full contract: **[guide/primitives.md](guide/primitives.md)**.
|
|
86
89
|
|
|
87
90
|
## Exports
|
|
88
91
|
|
|
89
92
|
| Entry | Use |
|
|
90
93
|
|---|---|
|
|
91
|
-
| `@homepages/template-kit` | Authoring API — schema system, primitives, marker contract, theme types
|
|
94
|
+
| `@homepages/template-kit` | Authoring API — schema system, primitives, marker contract, theme types |
|
|
95
|
+
| `@homepages/template-kit/fixtures` | The golden scenarios + helpers for `fixtures.ts` — a separate entry so dev-only sample data never reaches a published renderer bundle |
|
|
92
96
|
| `@homepages/template-kit/island-runtime` | Browser loader that hydrates a page's islands |
|
|
93
97
|
| `@homepages/template-kit/browser` | Browser helpers for published pages (site config, signed ingest) |
|
|
94
98
|
| `@homepages/template-kit/styles.css` | The global Tailwind preamble: the breakpoint ladder, the spacing base unit, the motion defaults, kit source registration |
|
|
@@ -96,8 +100,10 @@ Full contract: **[docs/primitives.md](docs/primitives.md)**.
|
|
|
96
100
|
| `@homepages/template-kit/eslint` | ESLint flat-config preset |
|
|
97
101
|
| `@homepages/template-kit/tsconfig` | tsconfig preset (`"extends"`) |
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
One specifier for everything you write a section against. The exceptions are
|
|
104
|
+
deliberate: the two CSS entries, and `/fixtures` — the golden scenarios, which only
|
|
105
|
+
`fixtures.ts` imports, kept off the root barrel so dev-only sample data can never
|
|
106
|
+
reach a published renderer's import graph.
|
|
101
107
|
|
|
102
108
|
`styles.css` does **not** import Tailwind. Import it *after* Tailwind in your entry, then
|
|
103
109
|
your generated theme after that:
|
|
@@ -113,15 +119,15 @@ belongs in this entry and nowhere else: it carries an `@theme` block, a Tailwind
|
|
|
113
119
|
that is inert outside the build graph. `base.css` is not part of that entry — it loads as
|
|
114
120
|
its own stylesheet on the page. `base.css` ships no reset of its own — it depends on the
|
|
115
121
|
preflight your entry's `@import "tailwindcss"` brings. Both must be on the page. Full
|
|
116
|
-
contract: **[
|
|
122
|
+
contract: **[guide/theme-and-css.md](guide/theme-and-css.md)**.
|
|
117
123
|
|
|
118
124
|
## Rules
|
|
119
125
|
|
|
120
126
|
The lint preset and the `template-kit check` CLI share one id namespace,
|
|
121
127
|
`template-kit/<id>`. A rule-id printed by either resolves to a page —
|
|
122
|
-
`
|
|
123
|
-
Start at **[
|
|
124
|
-
from **[
|
|
128
|
+
`guide/rules/<id>.md` — giving the rule, the reason it exists, and a before/after fix.
|
|
129
|
+
Start at **[guide/rules/INDEX.md](guide/rules/INDEX.md)**; the full doc set is routed
|
|
130
|
+
from **[guide/INDEX.md](guide/INDEX.md)**.
|
|
125
131
|
|
|
126
132
|
## Generating a theme
|
|
127
133
|
|
|
@@ -133,7 +139,7 @@ template-kit theme --all # generate every template's theme.css
|
|
|
133
139
|
`theme` compiles a template's `theme.ts` into `templates/<key>/theme.css` — the file your
|
|
134
140
|
Tailwind entry imports. The output is generated: don't hand-edit it, and re-run the
|
|
135
141
|
command whenever `theme.ts` changes. Full contract:
|
|
136
|
-
**[
|
|
142
|
+
**[guide/theme-and-css.md](guide/theme-and-css.md#generating-themecss)**.
|
|
137
143
|
|
|
138
144
|
## Previewing a workspace
|
|
139
145
|
|
|
@@ -152,7 +158,7 @@ section also has a standalone page, and so does the whole template at
|
|
|
152
158
|
payload), and `/api/screenshot` (a full-page PNG, section or whole template). Like
|
|
153
159
|
`check`, it runs **your** toolchain — Vite and Tailwind resolved from your workspace,
|
|
154
160
|
installed with `npm i -D vite @vitejs/plugin-react tailwindcss @tailwindcss/cli` — not
|
|
155
|
-
shipped by the kit. Full contract: **[
|
|
161
|
+
shipped by the kit. Full contract: **[guide/dev.md](guide/dev.md)**.
|
|
156
162
|
|
|
157
163
|
## Checking a workspace
|
|
158
164
|
|
|
@@ -167,7 +173,7 @@ contracts, runs a determinism render probe, and enforces per-section byte budget
|
|
|
167
173
|
plus workspace-wide dependency gates (lockfile health, a single React, license and
|
|
168
174
|
vulnerability checks) evaluated once per run. It exits non-zero on any failure and is
|
|
169
175
|
the same gate the platform re-runs when you submit a template. Full contract:
|
|
170
|
-
**[
|
|
176
|
+
**[guide/check.md](guide/check.md)**.
|
|
171
177
|
|
|
172
178
|
## Development
|
|
173
179
|
|
|
@@ -199,9 +205,13 @@ token anywhere, and adding one would silently disable OIDC.
|
|
|
199
205
|
```bash
|
|
200
206
|
npm run changeset
|
|
201
207
|
```
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
The kit is pre-1.0, so **minor is the breaking axis** — everything
|
|
209
|
+
non-breaking, features and fixes alike, is a **patch**. Never pick major
|
|
210
|
+
while 0.x: changesets offers it in its prompt, but taking it cuts 1.0.0, a
|
|
211
|
+
deliberate decision with its own issue, never a side effect of a
|
|
212
|
+
changeset. Write a human-readable summary — it becomes the changelog
|
|
213
|
+
entry that template authors read. Commit the generated file in
|
|
214
|
+
`.changeset/`.
|
|
205
215
|
|
|
206
216
|
2. **Merge to `main`.** The release workflow opens (or updates) a
|
|
207
217
|
**"Version Packages"** PR that consumes the changeset files, bumps
|
|
@@ -210,9 +220,10 @@ token anywhere, and adding one would silently disable OIDC.
|
|
|
210
220
|
3. **Merge the Version Packages PR.** That merge publishes to npm.
|
|
211
221
|
|
|
212
222
|
Semver contract: the platform vocabulary (slot types, sources, transforms) is
|
|
213
|
-
closed and versioned
|
|
214
|
-
|
|
215
|
-
|
|
223
|
+
closed and versioned, and the kit is pre-1.0, so **minor is the breaking
|
|
224
|
+
axis** — a breaking change to the vocabulary is a **minor**; a new optional
|
|
225
|
+
capability, and any bug fix, is a **patch**. Design tokens are *not* part of
|
|
226
|
+
that vocabulary — your template names its own.
|
|
216
227
|
|
|
217
228
|
### Bumping the version by hand
|
|
218
229
|
|
package/dist/base.css
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - The cascade-layer order declaration above — the one place it is stated
|
|
10
10
|
* - The three reset gaps Tailwind's preflight leaves (below)
|
|
11
11
|
* - The section box model (.tr-section, emitted by the <Section> primitive)
|
|
12
|
-
* - The <Image> frame's placeholder fill (.tr-image-frame)
|
|
12
|
+
* - The <Image>/<Video> frame's placeholder fill (.tr-image-frame)
|
|
13
13
|
* - Universal tokens: motion
|
|
14
14
|
*
|
|
15
15
|
* REQUIRES A TAILWIND ENTRY ON THE PAGE. Your entry's `@import "tailwindcss"` brings
|
|
@@ -55,9 +55,11 @@
|
|
|
55
55
|
margin: 0;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/* The <Image> frame's placeholder fill, showing while an
|
|
59
|
-
* It is a plain class rather than a `bg-*` utility on the primitive because a
|
|
60
|
-
* names its own tokens — there is no colour token
|
|
58
|
+
/* The <Image>/<Video> frame's placeholder fill, showing while an asset is absent or
|
|
59
|
+
* loading. It is a plain class rather than a `bg-*` utility on the primitive because a
|
|
60
|
+
* template names its own tokens — there is no colour token these primitives may assume
|
|
61
|
+
* exists. Both frames share this one class (and one token) deliberately: the fill is the
|
|
62
|
+
* same neutral rect, so a template retints it once.
|
|
61
63
|
* Retint it by setting --tr-image-frame-bg in your theme's layout tokens. */
|
|
62
64
|
.tr-image-frame {
|
|
63
65
|
background-color: var(--tr-image-frame-bg, oklch(0.97 0 0));
|
|
@@ -3,13 +3,15 @@ const LIST_TYPES = /* @__PURE__ */ new Set([
|
|
|
3
3
|
"list",
|
|
4
4
|
"poi_list",
|
|
5
5
|
"image_collection",
|
|
6
|
+
"video_collection",
|
|
6
7
|
"object_list"
|
|
7
8
|
]);
|
|
9
|
+
const ASSET_TYPES = /* @__PURE__ */ new Set(["image", "video"]);
|
|
8
10
|
/** Whether a resolved slot value carries real content for its type. */
|
|
9
11
|
function slotHasContent(value, type) {
|
|
10
12
|
if (value === null || value === void 0) return false;
|
|
11
13
|
if (LIST_TYPES.has(type)) return Array.isArray(value) && value.length > 0;
|
|
12
|
-
if (type
|
|
14
|
+
if (ASSET_TYPES.has(type)) return typeof value === "object" && Boolean(value.url);
|
|
13
15
|
if (typeof value === "string") return value.trim().length > 0;
|
|
14
16
|
return true;
|
|
15
17
|
}
|
|
@@ -44,9 +46,9 @@ function truncate(s, n = 80) {
|
|
|
44
46
|
}
|
|
45
47
|
function valuePreview(value, type) {
|
|
46
48
|
if (LIST_TYPES.has(type)) return Array.isArray(value) ? `[${value.length} items]` : void 0;
|
|
47
|
-
if (type
|
|
49
|
+
if (ASSET_TYPES.has(type)) {
|
|
48
50
|
const url = value?.url;
|
|
49
|
-
return url ?
|
|
51
|
+
return url ? `${type}:${truncate(url)}` : void 0;
|
|
50
52
|
}
|
|
51
53
|
if (typeof value === "string") return truncate(value);
|
|
52
54
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
package/dist/cli/link/overlay.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { FixtureModule } from "@homepages/template-kit";
|
|
2
|
-
import {
|
|
2
|
+
import { luxuryMultiUnit, mkResponsive } from "@homepages/template-kit/fixtures";
|
|
3
3
|
|
|
4
4
|
// Base content the section is designed for. Nullability coverage (each optional
|
|
5
5
|
// slot rendered empty) is derived automatically from schema.ts — no fixture
|
|
6
6
|
// needed for it. Add a `states` entry only for a designed scenario that
|
|
7
7
|
// combines overrides at once (a content-length or option branch the section
|
|
8
8
|
// renders differently).
|
|
9
|
-
const heroImage =
|
|
9
|
+
const heroImage = luxuryMultiUnit.photos[19]!; // exterior facade shot
|
|
10
10
|
|
|
11
11
|
const fixtures: FixtureModule = {
|
|
12
12
|
base: {
|
|
13
13
|
slots: {
|
|
14
|
-
headline:
|
|
14
|
+
headline: luxuryMultiUnit.facts.address,
|
|
15
15
|
hero_image: {
|
|
16
16
|
photo_id: heroImage.id,
|
|
17
17
|
url: heroImage.url,
|
|
@@ -19,7 +19,7 @@ const fixtures: FixtureModule = {
|
|
|
19
19
|
// Height set to a 16:9 ratio to match the slot's locked crop.
|
|
20
20
|
responsive: mkResponsive(heroImage.url, 2048, 1152),
|
|
21
21
|
},
|
|
22
|
-
blurb: "A
|
|
22
|
+
blurb: "A 12-unit mansard Victorian in Jamaica Plain, moments from the Emerald Necklace.",
|
|
23
23
|
},
|
|
24
24
|
options: { align: "center" },
|
|
25
25
|
},
|
|
@@ -30,9 +30,9 @@ const fixtures: FixtureModule = {
|
|
|
30
30
|
"long-blurb": {
|
|
31
31
|
slots: {
|
|
32
32
|
blurb:
|
|
33
|
-
"A
|
|
34
|
-
"
|
|
35
|
-
"
|
|
33
|
+
"A 12-unit mansard Victorian in Jamaica Plain, with a terrazzo lobby, an " +
|
|
34
|
+
"on-site gym, quartz kitchens throughout, and a top-floor penthouse with a " +
|
|
35
|
+
"private rooftop terrace and skyline views — all moments from the Emerald Necklace.",
|
|
36
36
|
},
|
|
37
37
|
},
|
|
38
38
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/contracts/slot-catalog.d.ts
|
|
2
|
-
declare const CONTENT_SLOT_TYPES: readonly ["text", "number", "select", "image", "image_collection", "list", "object_list", "poi_list", "url"];
|
|
2
|
+
declare const CONTENT_SLOT_TYPES: readonly ["text", "number", "select", "image", "image_collection", "list", "object_list", "poi_list", "url", "video", "video_collection"];
|
|
3
3
|
declare const REF_SLOT_TYPES: readonly [];
|
|
4
|
-
declare const SLOT_TYPES: readonly ["text", "number", "select", "image", "image_collection", "list", "object_list", "poi_list", "url"];
|
|
4
|
+
declare const SLOT_TYPES: readonly ["text", "number", "select", "image", "image_collection", "list", "object_list", "poi_list", "url", "video", "video_collection"];
|
|
5
5
|
type ContentSlotType = (typeof CONTENT_SLOT_TYPES)[number];
|
|
6
6
|
type RefSlotType = (typeof REF_SLOT_TYPES)[number];
|
|
7
7
|
type SlotType = (typeof SLOT_TYPES)[number];
|
|
@@ -3,13 +3,14 @@ import { docsPathForRule } from "../../rules/registry.js";
|
|
|
3
3
|
//#region src/eslint/rules/no-raw-element.ts
|
|
4
4
|
const FORBIDDEN = {
|
|
5
5
|
section: "<Section>",
|
|
6
|
-
img: "<Image>"
|
|
6
|
+
img: "<Image>",
|
|
7
|
+
video: "<Video>"
|
|
7
8
|
};
|
|
8
9
|
const noRawElement = {
|
|
9
10
|
meta: {
|
|
10
11
|
type: "problem",
|
|
11
12
|
docs: {
|
|
12
|
-
description: "Ban raw <section> and <
|
|
13
|
+
description: "Ban raw <section>, <img> and <video> in section JSX",
|
|
13
14
|
url: docsPathForRule("no-raw-element")
|
|
14
15
|
},
|
|
15
16
|
schema: [],
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ContactFixture, PoiFixture } from "./sample-property.js";
|
|
2
|
+
import { Scenario } from "./scenario.js";
|
|
3
|
+
//#region src/fixtures/helpers.d.ts
|
|
4
|
+
declare function primaryContact(s: Scenario): ContactFixture;
|
|
5
|
+
declare function contactCards(s: Scenario): ContactFixture[];
|
|
6
|
+
declare function poisByCategory(s: Scenario): Record<PoiFixture["category"], PoiFixture[]>;
|
|
7
|
+
type PropertyMetrics = {
|
|
8
|
+
beds: number | null;
|
|
9
|
+
baths: number | null;
|
|
10
|
+
sqft: number | null;
|
|
11
|
+
price: string | null;
|
|
12
|
+
bedsMin: string | null;
|
|
13
|
+
bedsMax: string | null;
|
|
14
|
+
bathsMin: string | null;
|
|
15
|
+
bathsMax: string | null;
|
|
16
|
+
sqftMax: string | null;
|
|
17
|
+
priceStart: string | null;
|
|
18
|
+
addressLine1: string;
|
|
19
|
+
addressLine2: string;
|
|
20
|
+
};
|
|
21
|
+
declare function propertyMetrics(s: Scenario): PropertyMetrics;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { PropertyMetrics, contactCards, poisByCategory, primaryContact, propertyMetrics };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { TRANSFORMS } from "../contracts/transforms.js";
|
|
2
|
+
|
|
3
|
+
//#region src/fixtures/helpers.ts
|
|
4
|
+
const POI_CATEGORIES = [
|
|
5
|
+
"restaurant",
|
|
6
|
+
"park",
|
|
7
|
+
"transit",
|
|
8
|
+
"shopping",
|
|
9
|
+
"coffee",
|
|
10
|
+
"fitness"
|
|
11
|
+
];
|
|
12
|
+
function primaryContact(s) {
|
|
13
|
+
const first = s.contacts[0];
|
|
14
|
+
if (!first) throw new Error(`scenario ${s.key} has no contacts`);
|
|
15
|
+
return first;
|
|
16
|
+
}
|
|
17
|
+
function contactCards(s) {
|
|
18
|
+
return s.contacts;
|
|
19
|
+
}
|
|
20
|
+
function poisByCategory(s) {
|
|
21
|
+
const out = Object.fromEntries(POI_CATEGORIES.map((c) => [c, []]));
|
|
22
|
+
for (const poi of s.pois) out[poi.category].push(poi);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function propertyMetrics(s) {
|
|
26
|
+
const units = s.facts.units;
|
|
27
|
+
const lines = s.facts.address_lines ?? [s.facts.address, ""];
|
|
28
|
+
const primary = [...units].sort((a, b) => a.position - b.position)[0] ?? null;
|
|
29
|
+
const fmt = (name) => {
|
|
30
|
+
const v = TRANSFORMS[name].impl({ units });
|
|
31
|
+
return v == null ? null : String(v);
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
beds: primary?.beds ?? null,
|
|
35
|
+
baths: primary?.baths ?? null,
|
|
36
|
+
sqft: primary?.sqft ?? null,
|
|
37
|
+
price: primary?.price ?? null,
|
|
38
|
+
bedsMin: fmt("units.bedsMin"),
|
|
39
|
+
bedsMax: fmt("units.bedsMax"),
|
|
40
|
+
bathsMin: fmt("units.bathsMin"),
|
|
41
|
+
bathsMax: fmt("units.bathsMax"),
|
|
42
|
+
sqftMax: fmt("units.sqftMax"),
|
|
43
|
+
priceStart: fmt("units.priceStart"),
|
|
44
|
+
addressLine1: lines[0] ?? "",
|
|
45
|
+
addressLine2: lines[1] ?? ""
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { contactCards, poisByCategory, primaryContact, propertyMetrics };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ResponsiveImage } from "../contracts/image-descriptor.js";
|
|
2
|
-
import { PropertyFacts } from "../schema/property-facts.js";
|
|
3
2
|
//#region src/fixtures/sample-property.d.ts
|
|
4
3
|
type PhotoFixture = {
|
|
5
4
|
id: string;
|
|
@@ -10,18 +9,19 @@ type PhotoFixture = {
|
|
|
10
9
|
category?: string;
|
|
11
10
|
};
|
|
12
11
|
};
|
|
13
|
-
declare const allPhotos: PhotoFixture[];
|
|
14
12
|
declare function mkResponsive(url: string, width: number, height: number): ResponsiveImage;
|
|
15
13
|
type PoiFixture = {
|
|
16
14
|
id: string;
|
|
17
15
|
category: "restaurant" | "park" | "transit" | "shopping" | "coffee" | "fitness";
|
|
18
16
|
name: string;
|
|
17
|
+
place_id: string;
|
|
18
|
+
address: string;
|
|
19
19
|
distance: string;
|
|
20
|
+
distance_m: number;
|
|
20
21
|
lat: number;
|
|
21
22
|
lng: number;
|
|
22
23
|
url: string;
|
|
23
24
|
};
|
|
24
|
-
declare const samplePois: PoiFixture[];
|
|
25
25
|
type ContactFixture = {
|
|
26
26
|
library_item_id: string;
|
|
27
27
|
category: "contact";
|
|
@@ -36,13 +36,15 @@ type ContactFixture = {
|
|
|
36
36
|
email: string;
|
|
37
37
|
phone: string;
|
|
38
38
|
address: string;
|
|
39
|
+
org_name: string;
|
|
40
|
+
logo_light: string | null;
|
|
41
|
+
logo_dark: string | null;
|
|
39
42
|
socials: Array<{
|
|
40
43
|
platform: "instagram" | "linkedin" | "x" | "facebook";
|
|
41
44
|
url: string;
|
|
42
45
|
}>;
|
|
43
46
|
};
|
|
44
47
|
};
|
|
45
|
-
declare const sampleContacts: ContactFixture[];
|
|
46
48
|
type AmenityItem = {
|
|
47
49
|
id: string;
|
|
48
50
|
label: string;
|
|
@@ -51,17 +53,5 @@ type AmenityItem = {
|
|
|
51
53
|
description: string;
|
|
52
54
|
items: string[];
|
|
53
55
|
};
|
|
54
|
-
declare const sampleAmenities: AmenityItem[];
|
|
55
|
-
declare const sampleProperty: PropertyFacts;
|
|
56
|
-
declare const sampleHeroSlots: {
|
|
57
|
-
readonly headline: "A mansard victorian single-family home in the heart of Jamaica Plain";
|
|
58
|
-
readonly hero_image: {
|
|
59
|
-
readonly photo_id: string;
|
|
60
|
-
readonly url: string;
|
|
61
|
-
readonly alt: string;
|
|
62
|
-
};
|
|
63
|
-
readonly brand_logo: null;
|
|
64
|
-
readonly listing_status: "for_sale";
|
|
65
|
-
};
|
|
66
56
|
//#endregion
|
|
67
|
-
export { AmenityItem, ContactFixture, PhotoFixture, PoiFixture,
|
|
57
|
+
export { AmenityItem, ContactFixture, PhotoFixture, PoiFixture, mkResponsive };
|