@homepages/template-kit 0.0.1 → 0.2.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 +251 -0
- package/README.md +77 -11
- package/dist/base.css +64 -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 +308 -0
- package/dist/design-system/theme.js +102 -0
- package/dist/eslint/is-client-file.js +79 -0
- package/dist/eslint/rules/image-bare-needs-reason.js +43 -0
- package/dist/eslint/rules/no-client-directive-in-contract.js +31 -0
- package/dist/eslint/rules/no-client-runtime-in-server.js +103 -0
- package/dist/eslint/rules/no-css-import-from-render-path.js +29 -0
- package/dist/eslint/rules/no-hex.js +116 -0
- package/dist/eslint/rules/no-inline-style.js +27 -0
- package/dist/eslint/rules/no-nondeterminism.js +48 -0
- package/dist/eslint/rules/no-raw-element.js +37 -0
- package/dist/eslint/rules/props-from-schema.js +37 -0
- package/dist/eslint/rules/serializable-island-props.js +112 -0
- package/dist/eslint/rules/slot-marker-literal.js +38 -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 +28 -12
- 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/rules/registry.js +16 -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 +7 -4
- package/docs/eslint.md +55 -0
- package/docs/islands.md +150 -0
- package/docs/llms.txt +33 -3
- package/docs/primitives.md +214 -0
- package/docs/rules/INDEX.md +58 -0
- package/docs/rules/audit-severity.md +91 -0
- package/docs/rules/bundle-incomplete.md +117 -0
- package/docs/rules/css-reason.md +94 -0
- package/docs/rules/determinism-drift.md +112 -0
- package/docs/rules/image-bare-needs-reason.md +117 -0
- package/docs/rules/license-denied.md +106 -0
- package/docs/rules/lockfile-missing.md +68 -0
- package/docs/rules/lockfile-stale.md +88 -0
- package/docs/rules/missing-slot-marker.md +140 -0
- package/docs/rules/no-bare-css-import.md +128 -0
- package/docs/rules/no-client-directive-in-contract.md +111 -0
- package/docs/rules/no-client-runtime-in-server.md +143 -0
- package/docs/rules/no-css-import-from-render-path.md +118 -0
- package/docs/rules/no-hex.md +97 -0
- package/docs/rules/no-inline-style.md +121 -0
- package/docs/rules/no-nondeterminism.md +100 -0
- package/docs/rules/no-raw-element.md +100 -0
- package/docs/rules/props-from-schema.md +119 -0
- package/docs/rules/render-invariant.md +165 -0
- package/docs/rules/serializable-island-props.md +146 -0
- package/docs/rules/server-vs-client.md +105 -0
- package/docs/rules/sidebar-order.md +109 -0
- package/docs/rules/single-react.md +97 -0
- package/docs/rules/size-renderer-bundle.md +109 -0
- package/docs/rules/size-section-css.md +95 -0
- package/docs/rules/slot-group-marker.md +127 -0
- package/docs/rules/slot-marker-literal.md +148 -0
- package/docs/rules/typecheck.md +103 -0
- package/docs/rules/unlayered-fence.md +113 -0
- package/docs/schema-system.md +240 -0
- package/docs/theme-and-css.md +225 -0
- package/package.json +30 -6
- package/tsconfig.base.json +16 -0
- package/tsconfig.json +2 -13
- package/dist/src-dZr5N30y.js +0 -35
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { CONTENT_SLOT_TYPES, REFERRAL_FIELDS, REF_SLOT_TYPES, SLOT_TYPES } from "../contracts/slot-catalog.js";
|
|
2
|
+
import { ContentSlot, SlotDef, defaultForSlotType } from "./slot-types.js";
|
|
3
|
+
import { derived, direct } from "./source.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
//#region src/schema/section-schema.ts
|
|
7
|
+
const optionAuthorityShape = {
|
|
8
|
+
user_editable: z.boolean().default(false),
|
|
9
|
+
template_overridable: z.boolean().default(false),
|
|
10
|
+
planner_settable: z.boolean().default(false),
|
|
11
|
+
affects_decisions: z.array(z.string()).default([]),
|
|
12
|
+
affects_slots: z.array(z.string()).default([])
|
|
13
|
+
};
|
|
14
|
+
const OptionEnum = z.object({
|
|
15
|
+
type: z.literal("enum"),
|
|
16
|
+
values: z.array(z.string()).min(1),
|
|
17
|
+
default: z.string(),
|
|
18
|
+
...optionAuthorityShape
|
|
19
|
+
}).strict();
|
|
20
|
+
const OptionBool = z.object({
|
|
21
|
+
type: z.literal("bool"),
|
|
22
|
+
default: z.boolean(),
|
|
23
|
+
...optionAuthorityShape
|
|
24
|
+
}).strict();
|
|
25
|
+
const OptionInt = z.object({
|
|
26
|
+
type: z.literal("int"),
|
|
27
|
+
default: z.number().int(),
|
|
28
|
+
min: z.number().int().optional(),
|
|
29
|
+
max: z.number().int().optional(),
|
|
30
|
+
...optionAuthorityShape
|
|
31
|
+
}).strict();
|
|
32
|
+
const OptionString = z.object({
|
|
33
|
+
type: z.literal("string"),
|
|
34
|
+
default: z.string(),
|
|
35
|
+
...optionAuthorityShape
|
|
36
|
+
}).strict();
|
|
37
|
+
const OptionJson = z.object({
|
|
38
|
+
type: z.literal("json"),
|
|
39
|
+
default: z.unknown(),
|
|
40
|
+
...optionAuthorityShape
|
|
41
|
+
}).strict();
|
|
42
|
+
const OptionDef = z.discriminatedUnion("type", [
|
|
43
|
+
OptionEnum,
|
|
44
|
+
OptionBool,
|
|
45
|
+
OptionInt,
|
|
46
|
+
OptionString,
|
|
47
|
+
OptionJson
|
|
48
|
+
]);
|
|
49
|
+
const SlotGroupDef = z.object({
|
|
50
|
+
id: z.string(),
|
|
51
|
+
label: z.string(),
|
|
52
|
+
members: z.array(z.union([z.string(), z.tuple([z.string(), z.string()])])).min(1)
|
|
53
|
+
}).strict();
|
|
54
|
+
const SectionMeta = z.object({
|
|
55
|
+
displayName: z.string().min(1),
|
|
56
|
+
anchor: z.string().min(1).optional(),
|
|
57
|
+
groups: z.array(SlotGroupDef).optional(),
|
|
58
|
+
variants: z.record(z.string().min(1), z.object({
|
|
59
|
+
cases: z.array(z.string().min(1)).min(1),
|
|
60
|
+
default: z.string().min(1),
|
|
61
|
+
select: z.custom((v) => typeof v === "function", { message: "variant axis `select` must be a function" })
|
|
62
|
+
}).strict()).optional()
|
|
63
|
+
}).strict();
|
|
64
|
+
const SectionSchema = z.object({
|
|
65
|
+
meta: SectionMeta,
|
|
66
|
+
slots: z.record(z.string(), SlotDef),
|
|
67
|
+
options: z.record(z.string(), OptionDef).default({}),
|
|
68
|
+
slot_order: z.array(z.string()).optional()
|
|
69
|
+
}).strict();
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { CONTENT_SLOT_TYPES, ContentSlot, OptionDef, REFERRAL_FIELDS, REF_SLOT_TYPES, SLOT_TYPES, SectionMeta, SectionSchema, SlotDef, SlotGroupDef, defaultForSlotType, derived, direct };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { SlotType } from "../contracts/slot-catalog.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/schema/slot-types.d.ts
|
|
4
|
+
type SlotTypeDescriptor = {
|
|
5
|
+
shape: z.ZodRawShape;
|
|
6
|
+
default: () => unknown;
|
|
7
|
+
};
|
|
8
|
+
declare const SLOT_TYPE_DESCRIPTORS: Record<SlotType, SlotTypeDescriptor>;
|
|
9
|
+
declare const SlotDef: z.ZodType<Slot>;
|
|
10
|
+
declare function slotDefTypes(): SlotType[];
|
|
11
|
+
declare const ContentSlot: z.ZodObject<{
|
|
12
|
+
type: z.ZodEnum<["text", "number", "select", "image", "image_collection", "list", "object_list", "poi_list", "url"]>;
|
|
13
|
+
editable_by_user: z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
required: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
produced_by: z.ZodOptional<z.ZodString>;
|
|
16
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
}, "strict", z.ZodTypeAny, {
|
|
19
|
+
type: "number" | "select" | "text" | "image" | "image_collection" | "list" | "object_list" | "poi_list" | "url";
|
|
20
|
+
required: boolean;
|
|
21
|
+
editable_by_user: boolean;
|
|
22
|
+
min?: number | undefined;
|
|
23
|
+
max?: number | undefined;
|
|
24
|
+
produced_by?: string | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
type: "number" | "select" | "text" | "image" | "image_collection" | "list" | "object_list" | "poi_list" | "url";
|
|
27
|
+
min?: number | undefined;
|
|
28
|
+
max?: number | undefined;
|
|
29
|
+
required?: boolean | undefined;
|
|
30
|
+
editable_by_user?: boolean | undefined;
|
|
31
|
+
produced_by?: string | undefined;
|
|
32
|
+
}>;
|
|
33
|
+
type SlotBase = {
|
|
34
|
+
required?: boolean;
|
|
35
|
+
editable_by_user?: boolean;
|
|
36
|
+
produced_by?: string;
|
|
37
|
+
source?: {
|
|
38
|
+
kind: string;
|
|
39
|
+
field?: string;
|
|
40
|
+
transform?: string;
|
|
41
|
+
};
|
|
42
|
+
display_name?: string;
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
variant?: {
|
|
45
|
+
axis: string;
|
|
46
|
+
cases: readonly string[];
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
type TextSlot = SlotBase & {
|
|
50
|
+
type: "text";
|
|
51
|
+
size?: string;
|
|
52
|
+
format?: unknown;
|
|
53
|
+
length_cap?: number;
|
|
54
|
+
};
|
|
55
|
+
type NumberSlot = SlotBase & {
|
|
56
|
+
type: "number";
|
|
57
|
+
prefix?: string;
|
|
58
|
+
suffix?: string;
|
|
59
|
+
thousands_sep?: boolean;
|
|
60
|
+
decimals?: boolean;
|
|
61
|
+
max_digits?: number;
|
|
62
|
+
format?: unknown;
|
|
63
|
+
};
|
|
64
|
+
type SelectSlot = SlotBase & {
|
|
65
|
+
type: "select";
|
|
66
|
+
values?: string[];
|
|
67
|
+
options?: {
|
|
68
|
+
source: string;
|
|
69
|
+
};
|
|
70
|
+
catalog?: unknown;
|
|
71
|
+
creatable?: boolean;
|
|
72
|
+
fallback_icon?: string;
|
|
73
|
+
assigns?: Record<string, string>;
|
|
74
|
+
};
|
|
75
|
+
type ImageSlot = SlotBase & {
|
|
76
|
+
type: "image";
|
|
77
|
+
min?: number;
|
|
78
|
+
max?: number;
|
|
79
|
+
source_filter?: string;
|
|
80
|
+
recrop?: boolean;
|
|
81
|
+
crop?: unknown;
|
|
82
|
+
frame?: unknown;
|
|
83
|
+
};
|
|
84
|
+
type ImageCollectionSlot = SlotBase & {
|
|
85
|
+
type: "image_collection";
|
|
86
|
+
min?: number;
|
|
87
|
+
max?: number;
|
|
88
|
+
crop?: unknown;
|
|
89
|
+
frame?: unknown;
|
|
90
|
+
};
|
|
91
|
+
type ListSlot = SlotBase & {
|
|
92
|
+
type: "list";
|
|
93
|
+
min?: number;
|
|
94
|
+
max?: number;
|
|
95
|
+
element?: Slot;
|
|
96
|
+
};
|
|
97
|
+
type PoiListSlot = SlotBase & {
|
|
98
|
+
type: "poi_list";
|
|
99
|
+
min?: number;
|
|
100
|
+
max?: number;
|
|
101
|
+
};
|
|
102
|
+
type UrlSlot = SlotBase & {
|
|
103
|
+
type: "url";
|
|
104
|
+
};
|
|
105
|
+
type ObjectListSlot = SlotBase & {
|
|
106
|
+
type: "object_list";
|
|
107
|
+
min?: number;
|
|
108
|
+
max?: number;
|
|
109
|
+
item?: Record<string, Slot>;
|
|
110
|
+
source_title_field?: string;
|
|
111
|
+
item_order?: string[];
|
|
112
|
+
item_layout?: (string | [string, string])[];
|
|
113
|
+
};
|
|
114
|
+
type Slot = TextSlot | NumberSlot | SelectSlot | ImageSlot | ImageCollectionSlot | ListSlot | PoiListSlot | UrlSlot | ObjectListSlot;
|
|
115
|
+
declare function defaultForSlotType(type: string): unknown;
|
|
116
|
+
//#endregion
|
|
117
|
+
export { ContentSlot, SLOT_TYPE_DESCRIPTORS, Slot, SlotDef, defaultForSlotType, slotDefTypes };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { CONTENT_SLOT_TYPES, SLOT_TYPES } from "../contracts/slot-catalog.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/schema/slot-types.ts
|
|
5
|
+
const slotHeadShape = {
|
|
6
|
+
editable_by_user: z.boolean().default(false),
|
|
7
|
+
required: z.boolean().default(false),
|
|
8
|
+
produced_by: z.string().optional()
|
|
9
|
+
};
|
|
10
|
+
const cropConfigShape = { crop: z.object({
|
|
11
|
+
mode: z.enum(["locked", "free"]),
|
|
12
|
+
aspect: z.number().positive().optional(),
|
|
13
|
+
aspectLabel: z.string().optional()
|
|
14
|
+
}).strict().refine((c) => c.mode !== "locked" || c.aspect != null, { message: "locked crop requires an aspect" }).optional() };
|
|
15
|
+
const frameConfigShape = { frame: z.object({
|
|
16
|
+
breakpoint: z.union([
|
|
17
|
+
z.literal(350),
|
|
18
|
+
z.literal(390),
|
|
19
|
+
z.literal(425),
|
|
20
|
+
z.literal(512),
|
|
21
|
+
z.literal(768),
|
|
22
|
+
z.literal(1024),
|
|
23
|
+
z.literal(1440),
|
|
24
|
+
z.literal(1920)
|
|
25
|
+
]),
|
|
26
|
+
desktop: z.object({
|
|
27
|
+
aspect: z.number().positive(),
|
|
28
|
+
nominal: z.boolean().optional()
|
|
29
|
+
}).strict(),
|
|
30
|
+
mobile: z.object({
|
|
31
|
+
aspect: z.number().positive(),
|
|
32
|
+
nominal: z.boolean().optional()
|
|
33
|
+
}).strict().optional()
|
|
34
|
+
}).strict().optional() };
|
|
35
|
+
const slotMetadataShape = {
|
|
36
|
+
display_name: z.string().optional(),
|
|
37
|
+
placeholder: z.string().optional(),
|
|
38
|
+
variant: z.object({
|
|
39
|
+
axis: z.string().min(1),
|
|
40
|
+
cases: z.array(z.string().min(1)).min(1)
|
|
41
|
+
}).strict().optional(),
|
|
42
|
+
source: z.discriminatedUnion("kind", [
|
|
43
|
+
z.object({ kind: z.literal("authored") }),
|
|
44
|
+
z.object({
|
|
45
|
+
kind: z.literal("direct"),
|
|
46
|
+
field: z.string()
|
|
47
|
+
}).strict(),
|
|
48
|
+
z.object({
|
|
49
|
+
kind: z.literal("derived"),
|
|
50
|
+
transform: z.string()
|
|
51
|
+
}).strict()
|
|
52
|
+
]).optional()
|
|
53
|
+
};
|
|
54
|
+
const FormatRef = z.union([z.string(), z.object({ fn: z.string() }).passthrough()]);
|
|
55
|
+
const SLOT_TYPE_DESCRIPTORS = {
|
|
56
|
+
text: {
|
|
57
|
+
shape: {
|
|
58
|
+
size: z.enum([
|
|
59
|
+
"short",
|
|
60
|
+
"medium",
|
|
61
|
+
"long"
|
|
62
|
+
]).optional(),
|
|
63
|
+
format: FormatRef.optional(),
|
|
64
|
+
length_cap: z.number().int().positive().optional()
|
|
65
|
+
},
|
|
66
|
+
default: () => ""
|
|
67
|
+
},
|
|
68
|
+
number: {
|
|
69
|
+
shape: {
|
|
70
|
+
prefix: z.string().optional(),
|
|
71
|
+
suffix: z.string().optional(),
|
|
72
|
+
thousands_sep: z.boolean().optional(),
|
|
73
|
+
decimals: z.boolean().optional(),
|
|
74
|
+
max_digits: z.number().int().positive().optional(),
|
|
75
|
+
format: FormatRef.optional()
|
|
76
|
+
},
|
|
77
|
+
default: () => null
|
|
78
|
+
},
|
|
79
|
+
select: {
|
|
80
|
+
shape: {
|
|
81
|
+
values: z.array(z.string()).optional(),
|
|
82
|
+
options: z.object({ source: z.string() }).strict().optional(),
|
|
83
|
+
catalog: z.unknown().optional(),
|
|
84
|
+
creatable: z.boolean().optional(),
|
|
85
|
+
fallback_icon: z.string().optional(),
|
|
86
|
+
assigns: z.record(z.string(), z.string()).optional()
|
|
87
|
+
},
|
|
88
|
+
default: () => null
|
|
89
|
+
},
|
|
90
|
+
image: {
|
|
91
|
+
shape: {
|
|
92
|
+
min: z.number().int().nonnegative().optional(),
|
|
93
|
+
max: z.number().int().positive().optional(),
|
|
94
|
+
source_filter: z.string().optional(),
|
|
95
|
+
recrop: z.boolean().optional(),
|
|
96
|
+
...cropConfigShape,
|
|
97
|
+
...frameConfigShape
|
|
98
|
+
},
|
|
99
|
+
default: () => ({
|
|
100
|
+
url: "",
|
|
101
|
+
alt: ""
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
image_collection: {
|
|
105
|
+
shape: {
|
|
106
|
+
min: z.number().int().nonnegative().optional(),
|
|
107
|
+
max: z.number().int().positive().optional(),
|
|
108
|
+
...cropConfigShape,
|
|
109
|
+
...frameConfigShape
|
|
110
|
+
},
|
|
111
|
+
default: () => []
|
|
112
|
+
},
|
|
113
|
+
list: {
|
|
114
|
+
shape: {
|
|
115
|
+
min: z.number().int().nonnegative().optional(),
|
|
116
|
+
max: z.number().int().positive().optional(),
|
|
117
|
+
element: z.lazy(() => SlotDef).optional()
|
|
118
|
+
},
|
|
119
|
+
default: () => []
|
|
120
|
+
},
|
|
121
|
+
poi_list: {
|
|
122
|
+
shape: {
|
|
123
|
+
min: z.number().int().nonnegative().optional(),
|
|
124
|
+
max: z.number().int().positive().optional()
|
|
125
|
+
},
|
|
126
|
+
default: () => []
|
|
127
|
+
},
|
|
128
|
+
url: {
|
|
129
|
+
shape: {},
|
|
130
|
+
default: () => ""
|
|
131
|
+
},
|
|
132
|
+
object_list: {
|
|
133
|
+
shape: {
|
|
134
|
+
min: z.number().int().nonnegative().optional(),
|
|
135
|
+
max: z.number().int().positive().optional(),
|
|
136
|
+
item: z.record(z.string(), z.lazy(() => SlotDef)).optional(),
|
|
137
|
+
source_title_field: z.string().optional(),
|
|
138
|
+
item_order: z.array(z.string()).optional(),
|
|
139
|
+
item_layout: z.array(z.union([z.string(), z.tuple([z.string(), z.string()])])).optional()
|
|
140
|
+
},
|
|
141
|
+
default: () => []
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
function slotMember(type, shape) {
|
|
145
|
+
return z.object({
|
|
146
|
+
type: z.literal(type),
|
|
147
|
+
...slotHeadShape,
|
|
148
|
+
...shape,
|
|
149
|
+
...slotMetadataShape
|
|
150
|
+
}).strict();
|
|
151
|
+
}
|
|
152
|
+
const SLOT_DEF_MEMBERS = [
|
|
153
|
+
slotMember("text", SLOT_TYPE_DESCRIPTORS.text.shape),
|
|
154
|
+
slotMember("number", SLOT_TYPE_DESCRIPTORS.number.shape),
|
|
155
|
+
slotMember("select", SLOT_TYPE_DESCRIPTORS.select.shape),
|
|
156
|
+
slotMember("image", SLOT_TYPE_DESCRIPTORS.image.shape),
|
|
157
|
+
slotMember("image_collection", SLOT_TYPE_DESCRIPTORS.image_collection.shape),
|
|
158
|
+
slotMember("list", SLOT_TYPE_DESCRIPTORS.list.shape),
|
|
159
|
+
slotMember("poi_list", SLOT_TYPE_DESCRIPTORS.poi_list.shape),
|
|
160
|
+
slotMember("url", SLOT_TYPE_DESCRIPTORS.url.shape),
|
|
161
|
+
slotMember("object_list", SLOT_TYPE_DESCRIPTORS.object_list.shape)
|
|
162
|
+
];
|
|
163
|
+
const SlotDef = z.discriminatedUnion("type", [...SLOT_DEF_MEMBERS]);
|
|
164
|
+
function slotDefTypes() {
|
|
165
|
+
return SLOT_DEF_MEMBERS.map((m) => m.shape.type.value);
|
|
166
|
+
}
|
|
167
|
+
const ContentSlot = z.object({
|
|
168
|
+
type: z.enum(CONTENT_SLOT_TYPES),
|
|
169
|
+
editable_by_user: z.boolean().default(false),
|
|
170
|
+
required: z.boolean().default(false),
|
|
171
|
+
produced_by: z.string().optional(),
|
|
172
|
+
min: z.number().int().nonnegative().optional(),
|
|
173
|
+
max: z.number().int().positive().optional()
|
|
174
|
+
}).strict();
|
|
175
|
+
function defaultForSlotType(type) {
|
|
176
|
+
if (!SLOT_TYPES.includes(type)) return null;
|
|
177
|
+
return SLOT_TYPE_DESCRIPTORS[type].default();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
export { ContentSlot, SLOT_TYPE_DESCRIPTORS, SlotDef, defaultForSlotType, slotDefTypes };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SourceFieldName } from "../contracts/slot-catalog.js";
|
|
2
|
+
import { TransformName } from "../contracts/transforms.js";
|
|
3
|
+
//#region src/schema/source.d.ts
|
|
4
|
+
declare const direct: (field: SourceFieldName) => {
|
|
5
|
+
kind: "direct";
|
|
6
|
+
field: SourceFieldName;
|
|
7
|
+
};
|
|
8
|
+
declare const derived: (transform: TransformName) => {
|
|
9
|
+
kind: "derived";
|
|
10
|
+
transform: TransformName;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { derived, direct };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "./fixture-schema.js";
|
|
2
|
+
//#region src/schema/synthesize-nullable.d.ts
|
|
3
|
+
type VariantAxes = Record<string, {
|
|
4
|
+
cases: string[];
|
|
5
|
+
default: string;
|
|
6
|
+
}>;
|
|
7
|
+
type SchemaForSynth = {
|
|
8
|
+
slots: Record<string, {
|
|
9
|
+
type: string;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
meta?: {
|
|
13
|
+
variants?: VariantAxes | undefined;
|
|
14
|
+
} | undefined;
|
|
15
|
+
variants?: VariantAxes | undefined;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { SchemaForSynth };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { resolveBaseVariants } from "./fixture-schema.js";
|
|
2
|
+
|
|
3
|
+
//#region src/schema/synthesize-nullable.ts
|
|
4
|
+
const LIST_TYPES = /* @__PURE__ */ new Set([
|
|
5
|
+
"list",
|
|
6
|
+
"poi_list",
|
|
7
|
+
"image_collection",
|
|
8
|
+
"object_list"
|
|
9
|
+
]);
|
|
10
|
+
function emptyValueFor(type) {
|
|
11
|
+
return LIST_TYPES.has(type) ? [] : null;
|
|
12
|
+
}
|
|
13
|
+
function isAlreadyEmpty(value, type) {
|
|
14
|
+
return LIST_TYPES.has(type) ? Array.isArray(value) && value.length === 0 : value === null || value === void 0;
|
|
15
|
+
}
|
|
16
|
+
function synthesizeNullableFixtures(schema, module, selfAnchor) {
|
|
17
|
+
const baseSlots = module.base.slots;
|
|
18
|
+
const baseOptions = module.base.options ?? {};
|
|
19
|
+
const baseAnchors = module.base.anchors ?? {};
|
|
20
|
+
const baseVariants = resolveBaseVariants(schema, module);
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const [slotKey, slotDef] of Object.entries(schema.slots)) {
|
|
23
|
+
if (slotDef.required) continue;
|
|
24
|
+
if (isAlreadyEmpty(baseSlots[slotKey], slotDef.type)) continue;
|
|
25
|
+
const empty = emptyValueFor(slotDef.type);
|
|
26
|
+
out.push({
|
|
27
|
+
id: `null_${slotKey}`,
|
|
28
|
+
label: `${slotKey} = ${Array.isArray(empty) ? "empty" : "null"}`,
|
|
29
|
+
props: {
|
|
30
|
+
slots: {
|
|
31
|
+
...baseSlots,
|
|
32
|
+
[slotKey]: empty
|
|
33
|
+
},
|
|
34
|
+
options: { ...baseOptions },
|
|
35
|
+
nav: {
|
|
36
|
+
selfAnchor,
|
|
37
|
+
anchors: { ...baseAnchors }
|
|
38
|
+
},
|
|
39
|
+
variants: { ...baseVariants }
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { synthesizeNullableFixtures };
|
package/dist/styles.css
CHANGED
|
@@ -1,16 +1,138 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* @homepages/template-kit — theme tokens + kit source registration.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* This file must NOT import Tailwind itself: two imports in one graph
|
|
6
|
-
* double-emit preflight.
|
|
4
|
+
* Import this AFTER `@import "tailwindcss";` in your own CSS entry:
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* @import "tailwindcss";
|
|
7
|
+
* @import "@homepages/template-kit/styles.css";
|
|
8
|
+
*
|
|
9
|
+
* This file must NOT import Tailwind itself. Exactly one `@import "tailwindcss"` may
|
|
10
|
+
* exist per build graph — your entry owns it. Two double-emit Tailwind's preflight
|
|
11
|
+
* reset (box-sizing declared twice, ~4 KB of duplicate base layer).
|
|
12
|
+
*
|
|
13
|
+
* The @source below resolves relative to THIS FILE'S SHIPPED LOCATION (dist/styles.css),
|
|
14
|
+
* registering the kit's compiled JS for class scanning — that is how the utility classes
|
|
15
|
+
* baked into the kit's primitives reach your compiled CSS. It also overrides Tailwind's
|
|
16
|
+
* default exclusion of node_modules.
|
|
17
|
+
*
|
|
18
|
+
* It is scoped to `primitives/` because those are the ONLY shipped modules that emit
|
|
19
|
+
* markup. Tailwind's scanner is a plain candidate-extractor — it cannot tell code from a
|
|
20
|
+
* comment — and the kit ships unminified (deliberately: it keeps these class-name string
|
|
21
|
+
* literals scannable). Pointed at all of dist/, it therefore compiled prose out of the
|
|
22
|
+
* CLI's and the ESLint preset's doc comments into every consumer's stylesheet: 8 dead
|
|
23
|
+
* utilities and 19 stray @property registrations, ~2.5 KB. If a primitive ever moves out
|
|
24
|
+
* of primitives/, widen this — an unstyled <Image> in every template is the failure mode,
|
|
25
|
+
* and scripts/verify-consumer.mjs is what catches it.
|
|
26
|
+
*
|
|
27
|
+
* The @theme block aliases Tailwind's utility namespace onto the `--tr-*` custom
|
|
28
|
+
* properties your template's theme.ts supplies (`text-ink` → `var(--tr-color-ink)`).
|
|
29
|
+
* The aliases are the cross-template contract; the values behind them are yours.
|
|
11
30
|
*/
|
|
12
|
-
@source "
|
|
31
|
+
@source "./primitives/**/*.js";
|
|
32
|
+
|
|
33
|
+
@theme inline {
|
|
34
|
+
/* ---------- Colors (alias to the --tr-color-* values a theme.ts supplies) ---------- */
|
|
35
|
+
--color-ink: var(--tr-color-ink);
|
|
36
|
+
--color-ink-soft: var(--tr-color-ink-soft);
|
|
37
|
+
--color-ink-mute: var(--tr-color-ink-mute);
|
|
38
|
+
--color-background: var(--tr-color-background);
|
|
39
|
+
--color-surface: var(--tr-color-surface);
|
|
40
|
+
--color-surface-alt: var(--tr-color-surface-alt);
|
|
41
|
+
--color-border: var(--tr-color-border);
|
|
42
|
+
--color-border-strong: var(--tr-color-border-strong);
|
|
43
|
+
--color-accent: var(--tr-color-accent);
|
|
44
|
+
--color-accent-strong: var(--tr-color-accent-strong);
|
|
45
|
+
--color-accent-contrast: var(--tr-color-accent-contrast);
|
|
46
|
+
--color-primary: var(--tr-color-primary);
|
|
47
|
+
--color-primary-fg: var(--tr-color-primary-fg);
|
|
48
|
+
--color-primary-darker: var(--tr-color-primary-darker);
|
|
49
|
+
--color-overlay: var(--tr-color-overlay);
|
|
50
|
+
--color-overlay-strong: var(--tr-color-overlay-strong);
|
|
51
|
+
--color-overlay-light: var(--tr-color-overlay-light);
|
|
52
|
+
--color-footer-bg: var(--tr-color-footer-bg);
|
|
53
|
+
--color-footer-fg: var(--tr-color-footer-fg);
|
|
54
|
+
--color-footer-fg-mute: var(--tr-color-footer-fg-mute);
|
|
55
|
+
--color-footer-divider: var(--tr-color-footer-divider);
|
|
56
|
+
|
|
57
|
+
/* Decorative brand swatches — the open `palette` escape hatch in TokenTheme.
|
|
58
|
+
* A theme that omits one leaves its utility resolving to an unset custom
|
|
59
|
+
* property; only reach for these when a semantic role genuinely doesn't fit. */
|
|
60
|
+
--color-yellow-deep: var(--tr-color-yellow-deep);
|
|
61
|
+
--color-yellow-200: var(--tr-color-yellow-200);
|
|
62
|
+
--color-yellow-100: var(--tr-color-yellow-100);
|
|
63
|
+
--color-brown: var(--tr-color-brown);
|
|
64
|
+
--color-green: var(--tr-color-green);
|
|
65
|
+
--color-blue: var(--tr-color-blue);
|
|
66
|
+
--color-rule: var(--tr-color-rule);
|
|
67
|
+
--color-gray-light: var(--tr-color-gray-light);
|
|
68
|
+
--color-team-gold: var(--tr-color-team-gold);
|
|
69
|
+
--color-mute: var(--tr-color-mute);
|
|
70
|
+
--color-scrim: var(--tr-color-scrim);
|
|
71
|
+
--color-footer-line: var(--tr-color-footer-line);
|
|
72
|
+
--color-footer-mute: var(--tr-color-footer-mute);
|
|
73
|
+
--color-divider-40: var(--tr-color-divider-40);
|
|
74
|
+
--color-contact-deep: var(--tr-color-contact-deep);
|
|
75
|
+
--color-header-deep: var(--tr-color-header-deep);
|
|
76
|
+
|
|
77
|
+
/* ---------- Fonts ---------- */
|
|
78
|
+
--font-sans: var(--tr-font-sans);
|
|
79
|
+
--font-display: var(--tr-font-sans);
|
|
80
|
+
--font-serif: var(--tr-font-serif);
|
|
81
|
+
--font-num: var(--tr-font-num);
|
|
82
|
+
--font-label: var(--tr-font-label);
|
|
83
|
+
|
|
84
|
+
/* ---------- Type scale (mobile baseline; layer up with md:/lg:) ---------- */
|
|
85
|
+
--text-xs: var(--tr-text-xs);
|
|
86
|
+
--text-sm: var(--tr-text-sm);
|
|
87
|
+
--text-base: var(--tr-text-base);
|
|
88
|
+
--text-lg: var(--tr-text-lg);
|
|
89
|
+
--text-xl: var(--tr-text-xl);
|
|
90
|
+
--text-2xl: var(--tr-text-2xl);
|
|
91
|
+
--text-3xl: var(--tr-text-3xl);
|
|
92
|
+
--text-4xl: var(--tr-text-4xl);
|
|
93
|
+
--text-5xl: var(--tr-text-5xl);
|
|
94
|
+
--text-display: var(--tr-text-display);
|
|
95
|
+
|
|
96
|
+
/* ---------- Spacing (Tailwind's own scale — no custom aliases) ---------- */
|
|
97
|
+
--spacing: 0.25rem; /* v4 base unit; Tailwind multiplies (e.g. p-4 = 1rem) */
|
|
98
|
+
|
|
99
|
+
/* ---------- Radii ---------- */
|
|
100
|
+
--radius-sm: var(--tr-radius-sm);
|
|
101
|
+
--radius-md: var(--tr-radius-md);
|
|
102
|
+
--radius-lg: var(--tr-radius-lg);
|
|
103
|
+
--radius-full: var(--tr-radius-full);
|
|
104
|
+
|
|
105
|
+
/* ---------- Shadows ---------- */
|
|
106
|
+
--shadow-sm: var(--tr-shadow-sm);
|
|
107
|
+
--shadow-md: var(--tr-shadow-md);
|
|
108
|
+
--shadow-lg: var(--tr-shadow-lg);
|
|
109
|
+
|
|
110
|
+
/* ---------- Motion ---------- */
|
|
111
|
+
--default-transition-duration: var(--tr-duration-fast);
|
|
112
|
+
--default-transition-timing-function: var(--tr-ease);
|
|
13
113
|
|
|
14
|
-
|
|
15
|
-
|
|
114
|
+
/* ---------- Canonical breakpoint ladder ----------
|
|
115
|
+
* Eight canonical widths — the same set the preview grid and the screenshot
|
|
116
|
+
* harness verify a section against (VERIFICATION_WIDTHS is the matching TS
|
|
117
|
+
* constant). 350 is the mobile-first BASE (no prefix); the other seven are
|
|
118
|
+
* named breakpoints:
|
|
119
|
+
*
|
|
120
|
+
* bp390 (390) · bp425 (425) · bp512 (512) · md (768) · lg (1024)
|
|
121
|
+
* · bp1440 (1440) · bp1920 (1920)
|
|
122
|
+
*
|
|
123
|
+
* `md` and `lg` keep Tailwind's own names and values. The off-ladder defaults
|
|
124
|
+
* (sm/xl/2xl) are reset to `initial` so a section cannot come to depend on a
|
|
125
|
+
* width nobody screenshots. A swap-heavy section may still reach for a raw
|
|
126
|
+
* `@media` micro-breakpoint at an off-ladder width (e.g. 640 / 769).
|
|
127
|
+
*/
|
|
128
|
+
--breakpoint-sm: initial;
|
|
129
|
+
--breakpoint-bp390: 390px;
|
|
130
|
+
--breakpoint-bp425: 425px;
|
|
131
|
+
--breakpoint-bp512: 512px;
|
|
132
|
+
--breakpoint-md: 768px;
|
|
133
|
+
--breakpoint-lg: 1024px;
|
|
134
|
+
--breakpoint-bp1440: 1440px;
|
|
135
|
+
--breakpoint-bp1920: 1920px;
|
|
136
|
+
--breakpoint-xl: initial;
|
|
137
|
+
--breakpoint-2xl: initial;
|
|
16
138
|
}
|
package/docs/INDEX.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: Router for the docs corpus that ships inside the @homepages/template-kit npm package.
|
|
3
3
|
status: living
|
|
4
4
|
related: []
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-14
|
|
6
6
|
---
|
|
7
7
|
# template-kit docs — router
|
|
8
8
|
|
|
@@ -11,9 +11,12 @@ This tree **ships in the npm tarball** (`docs/` is listed in `package.json`'s
|
|
|
11
11
|
LLM agents, reading it from inside `node_modules`. Keep it free of internal-only
|
|
12
12
|
detail.
|
|
13
13
|
|
|
14
|
-
Cross-service contracts, product, and data model live in `../../docs/` (umbrella)
|
|
15
|
-
and are not shipped.
|
|
16
|
-
|
|
17
14
|
| Doc | What it covers | Read when you're touching... |
|
|
18
15
|
|---|---|---|
|
|
19
16
|
| `llms.txt` | Machine-readable index of this corpus for an agent working inside an installed kit | orienting an agent in a consuming workspace |
|
|
17
|
+
| `schema-system.md` | The section authoring contract: `schema.ts` / `fill-spec.ts` / `fixtures.ts` / `Renderer.tsx`, `SectionProps` inference, the closed source/transform vocabulary, and the golden property fixtures | authoring or changing any section |
|
|
18
|
+
| `primitives.md` | The five contract primitives (`Section`, `Slot`, `SlotItem`, `SlotGroup`, `Image`) and the marker attributes they emit | writing a section's markup |
|
|
19
|
+
| `theme-and-css.md` | The two CSS entries, the one-Tailwind-import rule, `TokenTheme`, the breakpoint ladder, and the opt-in `content-visibility` recipe for long, image-heavy pages | a template's theme, its CSS entry, or a long page with a live map or full-screen overlay |
|
|
20
|
+
| `islands.md` | Interactivity: `"use client"` components, the serializable-props rule, editor options, the loader | anything interactive in a section |
|
|
21
|
+
| `eslint.md` | The `@homepages/template-kit/eslint` preset: zero-config setup and its glob scope | wiring up lint in a template workspace |
|
|
22
|
+
| `rules/` | One page per rule-id (`template-kit/*`), across both venues — the id printed by a lint or `check` failure resolves here. `rules/INDEX.md` routes them; `rules/server-vs-client.md` is the concept the server-rendering rules assume | any lint or `check` failure |
|