@homepages/template-kit 0.5.1 → 0.6.0-dev-20260717012648
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 +36 -0
- package/dist/cli/check/diagnostics.js +5 -0
- package/dist/cli/check/index.js +8 -1
- package/dist/cli/check/loader.js +1 -1
- package/dist/cli/check/stages/validate/facts.js +1 -0
- package/dist/cli/check/workspace.js +15 -2
- package/dist/cli/link/index.js +1 -1
- package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +1 -1
- package/dist/cli/pack/collect.js +1 -1
- package/dist/cli/theme/load-theme.js +1 -1
- package/dist/fixtures/helpers.js +16 -3
- package/dist/fixtures/sample-property.js +8 -2
- package/dist/fixtures/scenarios/for-rent-condo.js +9 -2
- package/dist/fixtures/scenarios/sparse-single-family.js +8 -2
- package/dist/package.js +1 -1
- package/dist/schema/property-facts.d.ts +12 -3
- package/dist/schema/runtime-values.d.ts +1 -1
- package/guide/recipes/fixture-states.md +2 -2
- package/guide/recipes/pick-a-scenario.md +1 -1
- package/guide/schema-system.md +30 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @homepages/template-kit
|
|
2
2
|
|
|
3
|
+
## 0.6.0-dev-20260717012648
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 30458a9: Fixture facts now carry the canonical address shape.
|
|
8
|
+
|
|
9
|
+
`PropertyFacts["address"]` is an object (`display`, `street`, `street_no_type`, `city`,
|
|
10
|
+
`state`, `postal`, optional `unit`) instead of a string, and `address_lines` is removed.
|
|
11
|
+
Fill-specs seed from address sub-parts such as `${facts.address.street_no_type}` and
|
|
12
|
+
`${facts.address.city}`, which no scenario previously exposed — so fixtures had to
|
|
13
|
+
hand-derive them, and a fixture could not state what a correct fill-spec would produce.
|
|
14
|
+
|
|
15
|
+
Migration: a slot bound straight to `facts.address` must become `facts.address.display` —
|
|
16
|
+
otherwise the slot receives an object and React throws `Objects are not valid as a React
|
|
17
|
+
child`. Read any other sub-part off `facts.address` directly. For address LINES, use
|
|
18
|
+
`propertyMetrics(s).addressLine1` / `.addressLine2` — they derive from the sub-parts the
|
|
19
|
+
way the publishing pipeline does, so they need no change at the call site.
|
|
20
|
+
|
|
21
|
+
Two values move, both in the `for-rent-condo` scenario, which carries `unit: "Unit 4"`:
|
|
22
|
+
`facts.address.display` is `"845 Pearl Street, Unit 4, Denver, CO 80203"` (the string it
|
|
23
|
+
replaces read `"845 Pearl Street, Denver, CO 80203"`), and the derivation folds the unit
|
|
24
|
+
into line 1, so `propertyMetrics(forRentCondo).addressLine1` is `"845 Pearl Street, Unit
|
|
25
|
+
4"` (was `"845 Pearl Street"`). Snapshots over that scenario will show both diffs.
|
|
26
|
+
`addressLine2` is unchanged, and in `luxury-multi-unit` and `sparse-single-family`
|
|
27
|
+
`display` holds exactly the text the string did.
|
|
28
|
+
|
|
29
|
+
Also: the fixture-vs-canonical facts check now treats `videos` as an iterable array
|
|
30
|
+
field, not a scalar — matching how the rest of the fill pipeline already reads it. If
|
|
31
|
+
you write a fill-spec against `videos`, expect array semantics.
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- b0688c5: `check` now reports when a local kit overlay is active, naming the overlaid version and the version your workspace actually pins. Being overlaid while you develop is correct, so the report never affects the result — but a passing run under an overlay does not reflect your declared kit version, and that is worth seeing before you merge.
|
|
36
|
+
|
|
37
|
+
## 0.5.2
|
|
38
|
+
|
|
3
39
|
## 0.5.1
|
|
4
40
|
|
|
5
41
|
## 0.5.0
|
|
@@ -9,6 +9,11 @@ function formatOne(d) {
|
|
|
9
9
|
}
|
|
10
10
|
function formatHuman(report) {
|
|
11
11
|
const parts = [];
|
|
12
|
+
if (report.linkedKit !== void 0) {
|
|
13
|
+
const { kitVersion, linkedFrom, pin } = report.linkedKit;
|
|
14
|
+
parts.push(`⚠ linked kit ${kitVersion} from ${linkedFrom} — your pin is ${pin}.\n`);
|
|
15
|
+
parts.push(" This result does not reflect your pin. Run `template-kit unlink` before merging.\n");
|
|
16
|
+
}
|
|
12
17
|
for (const [key, t] of Object.entries(report.templates)) {
|
|
13
18
|
if (t.ok) {
|
|
14
19
|
parts.push(`✓ ${key}\n`);
|
package/dist/cli/check/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { KIT_VERSION } from "../../index.js";
|
|
2
|
+
import { readLinkMarker } from "../link/overlay.js";
|
|
2
3
|
import { formatHuman, toJson } from "./diagnostics.js";
|
|
3
4
|
import { loadEsbuild } from "./resolve-tool.js";
|
|
4
5
|
import { resetLoadCache } from "./loader.js";
|
|
@@ -9,7 +10,7 @@ import { sizeStage } from "./stages/size.js";
|
|
|
9
10
|
import { treeStage } from "./stages/tree.js";
|
|
10
11
|
import { typecheckStage } from "./stages/typecheck.js";
|
|
11
12
|
import { validateStage } from "./stages/validate/index.js";
|
|
12
|
-
import { findWorkspaceRoot, selectTemplates, templateFromCwd } from "./workspace.js";
|
|
13
|
+
import { declaredKitPin, findWorkspaceRoot, selectTemplates, templateFromCwd } from "./workspace.js";
|
|
13
14
|
import { parseArgs } from "node:util";
|
|
14
15
|
|
|
15
16
|
//#region src/cli/check/index.ts
|
|
@@ -71,9 +72,15 @@ async function runCheck(argv, cwd) {
|
|
|
71
72
|
ok: workspaceDiagnostics.length === 0,
|
|
72
73
|
diagnostics: workspaceDiagnostics
|
|
73
74
|
};
|
|
75
|
+
const marker = await readLinkMarker(root);
|
|
74
76
|
const report = {
|
|
75
77
|
ok: workspace.ok && Object.values(templates).every((t) => t.ok),
|
|
76
78
|
kitVersion: KIT_VERSION,
|
|
79
|
+
...marker === void 0 ? {} : { linkedKit: {
|
|
80
|
+
linkedFrom: marker.linkedFrom,
|
|
81
|
+
kitVersion: marker.kitVersion,
|
|
82
|
+
pin: await declaredKitPin(root)
|
|
83
|
+
} },
|
|
77
84
|
templates,
|
|
78
85
|
workspace
|
|
79
86
|
};
|
package/dist/cli/check/loader.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { loadEsbuild } from "./resolve-tool.js";
|
|
2
2
|
import { ASSET_LOADERS, ASSET_NAMES, ASSET_PUBLIC_PATH } from "./css.js";
|
|
3
3
|
import { join, relative } from "node:path";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
4
|
import { mkdir, rm, stat } from "node:fs/promises";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
8
|
//#region src/cli/check/loader.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dirname, join, resolve } from "node:path";
|
|
2
|
-
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
3
3
|
|
|
4
4
|
//#region src/cli/check/workspace.ts
|
|
5
5
|
const isDir = async (p) => {
|
|
@@ -26,6 +26,19 @@ async function findWorkspaceRoot(from) {
|
|
|
26
26
|
dir = parent;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The kit version the workspace DECLARES in package.json — deliberately not what is
|
|
31
|
+
* installed. The link overlay never touches package.json, so declared-vs-installed is
|
|
32
|
+
* precisely the gap the check warning exists to surface.
|
|
33
|
+
*/
|
|
34
|
+
async function declaredKitPin(root) {
|
|
35
|
+
try {
|
|
36
|
+
const raw = await readFile(join(root, "package.json"), "utf8");
|
|
37
|
+
return JSON.parse(raw).dependencies?.["@homepages/template-kit"] ?? "unknown";
|
|
38
|
+
} catch {
|
|
39
|
+
return "unknown";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
29
42
|
async function discoverTemplates(root) {
|
|
30
43
|
const templatesDir = join(root, "templates");
|
|
31
44
|
const entries = await readdir(templatesDir, { withFileTypes: true });
|
|
@@ -83,4 +96,4 @@ function templateFromCwd(root, cwd) {
|
|
|
83
96
|
}
|
|
84
97
|
|
|
85
98
|
//#endregion
|
|
86
|
-
export { discoverTemplates, findWorkspaceRoot, selectTemplates, templateFromCwd };
|
|
99
|
+
export { declaredKitPin, discoverTemplates, findWorkspaceRoot, selectTemplates, templateFromCwd };
|
package/dist/cli/link/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { findWorkspaceRoot } from "../check/workspace.js";
|
|
2
1
|
import { KIT_PKG_SUBPATH, findKitRoot, overlayKit, readLinkMarker } from "./overlay.js";
|
|
2
|
+
import { findWorkspaceRoot } from "../check/workspace.js";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { parseArgs, promisify } from "node:util";
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
@@ -11,7 +11,7 @@ const heroImage = luxuryMultiUnit.photos[19]!; // exterior facade shot
|
|
|
11
11
|
const fixtures: FixtureModule = {
|
|
12
12
|
base: {
|
|
13
13
|
slots: {
|
|
14
|
-
headline: luxuryMultiUnit.facts.address,
|
|
14
|
+
headline: luxuryMultiUnit.facts.address.display,
|
|
15
15
|
hero_image: {
|
|
16
16
|
photo_id: heroImage.id,
|
|
17
17
|
url: heroImage.url,
|
package/dist/cli/pack/collect.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join, relative, sep } from "node:path";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
2
|
import { readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
4
|
|
|
5
5
|
//#region src/cli/pack/collect.ts
|
|
6
6
|
const toPosix = (p) => p.split(sep).join("/");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadEsbuild } from "../check/resolve-tool.js";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
3
|
import { mkdir, readFile, rm, stat } from "node:fs/promises";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
|
|
7
7
|
//#region src/cli/theme/load-theme.ts
|
package/dist/fixtures/helpers.js
CHANGED
|
@@ -22,10 +22,23 @@ function poisByCategory(s) {
|
|
|
22
22
|
for (const poi of s.pois) out[poi.category].push(poi);
|
|
23
23
|
return out;
|
|
24
24
|
}
|
|
25
|
+
function addressLineParts(a) {
|
|
26
|
+
const street = a.street.trim();
|
|
27
|
+
const unit = (a.unit ?? "").trim();
|
|
28
|
+
const city = a.city.trim();
|
|
29
|
+
const state = a.state.trim();
|
|
30
|
+
const postal = a.postal.trim();
|
|
31
|
+
const streetLine = unit && street ? `${street}, ${unit}` : street;
|
|
32
|
+
const cityStateZip = [city, [state, postal].filter(Boolean).join(" ")].filter(Boolean).join(", ");
|
|
33
|
+
return {
|
|
34
|
+
line1: streetLine.length > 0 ? streetLine : null,
|
|
35
|
+
line2: cityStateZip.length > 0 ? cityStateZip : null
|
|
36
|
+
};
|
|
37
|
+
}
|
|
25
38
|
function propertyMetrics(s) {
|
|
26
39
|
const units = s.facts.units;
|
|
27
|
-
const lines = s.facts.address_lines ?? [s.facts.address, ""];
|
|
28
40
|
const primary = [...units].sort((a, b) => a.position - b.position)[0] ?? null;
|
|
41
|
+
const lines = addressLineParts(s.facts.address);
|
|
29
42
|
const fmt = (name) => {
|
|
30
43
|
const v = TRANSFORMS[name].impl({ units });
|
|
31
44
|
return v == null ? null : String(v);
|
|
@@ -41,8 +54,8 @@ function propertyMetrics(s) {
|
|
|
41
54
|
bathsMax: fmt("units.bathsMax"),
|
|
42
55
|
sqftMax: fmt("units.sqftMax"),
|
|
43
56
|
priceStart: fmt("units.priceStart"),
|
|
44
|
-
addressLine1: lines
|
|
45
|
-
addressLine2: lines
|
|
57
|
+
addressLine1: lines.line1 ?? "",
|
|
58
|
+
addressLine2: lines.line2 ?? ""
|
|
46
59
|
};
|
|
47
60
|
}
|
|
48
61
|
|
|
@@ -792,8 +792,14 @@ const sampleAmenities = [
|
|
|
792
792
|
}
|
|
793
793
|
];
|
|
794
794
|
const sampleProperty = {
|
|
795
|
-
address:
|
|
796
|
-
|
|
795
|
+
address: {
|
|
796
|
+
display: "101 Queensway, Jamaica Plain, MA 02130",
|
|
797
|
+
street: "101 Queensway",
|
|
798
|
+
street_no_type: "101 Queensway",
|
|
799
|
+
city: "Jamaica Plain",
|
|
800
|
+
state: "MA",
|
|
801
|
+
postal: "02130"
|
|
802
|
+
},
|
|
797
803
|
coordinates: {
|
|
798
804
|
lat: 42.31,
|
|
799
805
|
lng: -71.11
|
|
@@ -314,8 +314,15 @@ const contacts = [{
|
|
|
314
314
|
}
|
|
315
315
|
}];
|
|
316
316
|
const facts = {
|
|
317
|
-
address:
|
|
318
|
-
|
|
317
|
+
address: {
|
|
318
|
+
display: "845 Pearl Street, Unit 4, Denver, CO 80203",
|
|
319
|
+
street: "845 Pearl Street",
|
|
320
|
+
street_no_type: "845 Pearl",
|
|
321
|
+
unit: "Unit 4",
|
|
322
|
+
city: "Denver",
|
|
323
|
+
state: "CO",
|
|
324
|
+
postal: "80203"
|
|
325
|
+
},
|
|
319
326
|
coordinates: {
|
|
320
327
|
lat: 39.7273,
|
|
321
328
|
lng: -104.9808
|
|
@@ -183,8 +183,14 @@ const contacts = [{
|
|
|
183
183
|
}
|
|
184
184
|
}];
|
|
185
185
|
const facts = {
|
|
186
|
-
address:
|
|
187
|
-
|
|
186
|
+
address: {
|
|
187
|
+
display: "212 Tuscan Road, Maplewood, NJ 07040",
|
|
188
|
+
street: "212 Tuscan Road",
|
|
189
|
+
street_no_type: "212 Tuscan",
|
|
190
|
+
city: "Maplewood",
|
|
191
|
+
state: "NJ",
|
|
192
|
+
postal: "07040"
|
|
193
|
+
},
|
|
188
194
|
coordinates: {
|
|
189
195
|
lat: 40.7315,
|
|
190
196
|
lng: -74.2739
|
package/dist/package.js
CHANGED
|
@@ -26,9 +26,18 @@ type UnitRow = {
|
|
|
26
26
|
position: number;
|
|
27
27
|
floor_plans: FloorplanAssignment[];
|
|
28
28
|
};
|
|
29
|
+
type Address = {
|
|
30
|
+
display: string;
|
|
31
|
+
street: string;
|
|
32
|
+
street_no_type: string;
|
|
33
|
+
city: string;
|
|
34
|
+
state: string;
|
|
35
|
+
postal: string;
|
|
36
|
+
/** Address Line 2 (apt/suite/unit, e.g. "Unit 4B"); absent when none. */
|
|
37
|
+
unit?: string;
|
|
38
|
+
};
|
|
29
39
|
type PropertyFacts = {
|
|
30
|
-
address:
|
|
31
|
-
address_lines?: string[];
|
|
40
|
+
address: Address;
|
|
32
41
|
lat?: number | null;
|
|
33
42
|
lon?: number | null;
|
|
34
43
|
coordinates?: Coordinates | null;
|
|
@@ -41,4 +50,4 @@ type PropertyFacts = {
|
|
|
41
50
|
contacts?: Array<Record<string, unknown>>;
|
|
42
51
|
};
|
|
43
52
|
//#endregion
|
|
44
|
-
export { Coordinates, FloorplanAssignment, PropertyFacts, UnitRow };
|
|
53
|
+
export { Address, Coordinates, FloorplanAssignment, PropertyFacts, UnitRow };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ResponsiveImage } from "../contracts/image-descriptor.js";
|
|
2
|
-
import { Coordinates, FloorplanAssignment, PropertyFacts, UnitRow } from "./property-facts.js";
|
|
2
|
+
import { Address, Coordinates, FloorplanAssignment, PropertyFacts, UnitRow } from "./property-facts.js";
|
|
3
3
|
import { VerificationWidth } from "../design-system/breakpoints.js";
|
|
4
4
|
//#region src/schema/runtime-values.d.ts
|
|
5
5
|
type FrameConfig = {
|
|
@@ -67,7 +67,7 @@ const sparse = scenarios.sparseSingleFamily;
|
|
|
67
67
|
const fixtures: FixtureModule = {
|
|
68
68
|
base: {
|
|
69
69
|
slots: {
|
|
70
|
-
headline: luxury.facts.address,
|
|
70
|
+
headline: luxury.facts.address.display,
|
|
71
71
|
summary: "A 12-unit mansard Victorian moments from the Emerald Necklace.",
|
|
72
72
|
},
|
|
73
73
|
options: { align: "center" },
|
|
@@ -75,7 +75,7 @@ const fixtures: FixtureModule = {
|
|
|
75
75
|
states: {
|
|
76
76
|
sparse: {
|
|
77
77
|
slots: {
|
|
78
|
-
headline: sparse.facts.address,
|
|
78
|
+
headline: sparse.facts.address.display,
|
|
79
79
|
summary:
|
|
80
80
|
"A classic center-hall colonial with an updated kitchen and original " +
|
|
81
81
|
"hardwood floors throughout.",
|
|
@@ -46,7 +46,7 @@ production — `fixtures.ts` mirrors that:
|
|
|
46
46
|
|
|
47
47
|
| Slot's fill-spec source | How the fixture fills it |
|
|
48
48
|
|---|---|
|
|
49
|
-
| direct (a fact) | read it off the scenario — `s.facts.address`, `primaryContact(s).fields.phone` |
|
|
49
|
+
| direct (a fact) | read it off the scenario — `s.facts.address.display`, `primaryContact(s).fields.phone` |
|
|
50
50
|
| derived | a helper — `propertyMetrics(s)`, `poisByCategory(s)`, `contactCards(s)` |
|
|
51
51
|
| ai | a short sample fill **grounded in the scenario's facts** — a headline about *this* property, never lorem ipsum or invented facts |
|
|
52
52
|
|
package/guide/schema-system.md
CHANGED
|
@@ -229,7 +229,7 @@ export default fixtures;
|
|
|
229
229
|
### Build on a scenario, not on lorem ipsum
|
|
230
230
|
|
|
231
231
|
Preview is only as honest as the data behind it, so the kit ships three complete,
|
|
232
|
-
property-
|
|
232
|
+
property-shaped scenarios to build fixtures from — real addresses, unit counts, and
|
|
233
233
|
contacts, the cases that actually break a layout. Pick the one whose breadth matches the
|
|
234
234
|
state you're exercising:
|
|
235
235
|
|
|
@@ -260,7 +260,7 @@ rule says where to find it, keyed to the same `source` / `produced_by` a slot de
|
|
|
260
260
|
|
|
261
261
|
| Slot's fill-spec source | How the fixture fills it |
|
|
262
262
|
|---|---|
|
|
263
|
-
| direct (a fact) | read it off the scenario — `s.facts.address`, `primaryContact(s).fields.phone` |
|
|
263
|
+
| direct (a fact) | read it off the scenario — `s.facts.address.display`, `primaryContact(s).fields.phone` |
|
|
264
264
|
| derived | a helper — `propertyMetrics(s)`, `poisByCategory(s)`, `contactCards(s)` |
|
|
265
265
|
| ai | a short sample fill **grounded in the scenario's facts** — a headline about *this* property, never lorem ipsum or invented facts |
|
|
266
266
|
|
|
@@ -292,6 +292,34 @@ a sibling "brand" object — read them off `primaryContact(s).fields` or `s.cont
|
|
|
292
292
|
See [Pick a scenario](recipes/pick-a-scenario.md) for the full picker and helper
|
|
293
293
|
cheat-sheet.
|
|
294
294
|
|
|
295
|
+
### What a scenario is, and is not
|
|
296
|
+
|
|
297
|
+
A scenario is the **fixture view** of a property, not the fact bundle the AI-fill pipeline
|
|
298
|
+
reads. The difference matters when you write a `fill-spec.ts`:
|
|
299
|
+
|
|
300
|
+
- Scenarios carry values that only exist *after* fill and render — `UnitRow.description`
|
|
301
|
+
and `features` are AI-written copy; a floor plan's `url` and `alt` are attached when the
|
|
302
|
+
image is served. Nothing upstream produces these; they are here so your renderer has
|
|
303
|
+
something real to draw.
|
|
304
|
+
- Scenarios hoist some facts **out** of `facts` and onto the scenario itself, in the shapes
|
|
305
|
+
a renderer wants: `photos` → `s.photos` (`PhotoFixture[]`), `pois` → `s.pois`
|
|
306
|
+
(`PoiFixture[]`), and the fact bundle's `tours` → `s.media` — a single `MediaFixture`
|
|
307
|
+
**object** (`video_url`, `tour_3d_url`), not an array. Floor plans hoist too, but onto
|
|
308
|
+
each unit: `s.facts.units[*].floor_plans` (`FloorplanAssignment[]`).
|
|
309
|
+
- `contacts` is **duplicated, not hoisted**. `s.contacts` holds the render-shaped
|
|
310
|
+
`ContactFixture[]` your renderer draws, and `facts.contacts` still exists as well — so a
|
|
311
|
+
fill-spec that reads or iterates `facts.contacts` has a real counterpart in every
|
|
312
|
+
scenario.
|
|
313
|
+
- `facts.address` is the exception: it mirrors the real address contract field for field
|
|
314
|
+
(`display`, `street`, `street_no_type`, `city`, `state`, `postal`, and an optional
|
|
315
|
+
`unit`). A fill-spec that seeds from `${facts.address.city}` has a real counterpart in
|
|
316
|
+
every scenario. Note `street_no_type` drops the street-type suffix — "845 Pearl" for
|
|
317
|
+
"845 Pearl Street" — so never seed one from the other.
|
|
318
|
+
|
|
319
|
+
Address lines are **derived, never stored**: use `propertyMetrics(s).addressLine1` /
|
|
320
|
+
`addressLine2`, which compose the sub-parts exactly as the publishing pipeline does. Don't
|
|
321
|
+
hand-assemble or split them — a fixture must state the value the real pipeline bakes.
|
|
322
|
+
|
|
295
323
|
## Variants — rendering differently based on the facts
|
|
296
324
|
|
|
297
325
|
A variant axis picks a case from the property's facts, before any content is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homepages/template-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-dev-20260717012648",
|
|
4
4
|
"description": "Authoring kit for HomePages marketing-section templates: schema system, contract primitives, theme tokens, and the template-kit CLI.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|