@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.
Files changed (114) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +38 -27
  3. package/dist/base.css +6 -4
  4. package/dist/cli/dev/fill-state.js +5 -3
  5. package/dist/cli/link/overlay.js +1 -1
  6. package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +7 -7
  7. package/dist/contracts/slot-catalog.d.ts +2 -2
  8. package/dist/contracts/slot-catalog.js +3 -1
  9. package/dist/eslint/rules/no-raw-element.js +3 -2
  10. package/dist/fixtures/helpers.d.ts +23 -0
  11. package/dist/fixtures/helpers.js +50 -0
  12. package/dist/fixtures/sample-property.d.ts +7 -17
  13. package/dist/fixtures/sample-property.js +179 -57
  14. package/dist/fixtures/scenario.d.ts +20 -0
  15. package/dist/fixtures/scenarios/for-rent-condo.d.ts +5 -0
  16. package/dist/fixtures/scenarios/for-rent-condo.js +364 -0
  17. package/dist/fixtures/scenarios/index.d.ts +8 -0
  18. package/dist/fixtures/scenarios/index.js +13 -0
  19. package/dist/fixtures/scenarios/luxury-multi-unit.d.ts +5 -0
  20. package/dist/fixtures/scenarios/luxury-multi-unit.js +19 -0
  21. package/dist/fixtures/scenarios/sparse-single-family.d.ts +5 -0
  22. package/dist/fixtures/scenarios/sparse-single-family.js +233 -0
  23. package/dist/fixtures.d.ts +8 -2
  24. package/dist/fixtures.js +7 -2
  25. package/dist/index.d.ts +3 -2
  26. package/dist/index.js +2 -1
  27. package/dist/island-runtime.js +22 -7
  28. package/dist/islands/wrap.d.ts +1 -0
  29. package/dist/islands/wrap.js +1 -1
  30. package/dist/package.js +1 -1
  31. package/dist/primitives/Image.d.ts +1 -1
  32. package/dist/primitives/Video.d.ts +38 -0
  33. package/dist/primitives/Video.js +63 -0
  34. package/dist/primitives/index.d.ts +2 -1
  35. package/dist/rules/registry.js +3 -3
  36. package/dist/schema/index.d.ts +2 -2
  37. package/dist/schema/runtime-values.d.ts +20 -1
  38. package/dist/schema/section-props.d.ts +8 -1
  39. package/dist/schema/section-schema.d.ts +2 -2
  40. package/dist/schema/slot-types.d.ts +15 -1
  41. package/dist/schema/slot-types.js +34 -0
  42. package/dist/schema/synthesize-nullable.js +1 -0
  43. package/{docs → guide}/INDEX.md +7 -6
  44. package/{docs → guide}/check.md +2 -2
  45. package/{docs → guide}/islands.md +6 -1
  46. package/{docs → guide}/llms.txt +34 -14
  47. package/{docs → guide}/overview.md +4 -4
  48. package/{docs → guide}/pack.md +2 -2
  49. package/{docs → guide}/primitives.md +59 -11
  50. package/{docs → guide}/recipes/INDEX.md +1 -0
  51. package/{docs → guide}/recipes/collection-slot.md +10 -8
  52. package/guide/recipes/fixture-states.md +109 -0
  53. package/{docs → guide}/recipes/image-slot-crop.md +5 -3
  54. package/guide/recipes/pick-a-scenario.md +119 -0
  55. package/{docs → guide}/rules/INDEX.md +2 -2
  56. package/{docs → guide}/rules/fixtures-invalid.md +2 -2
  57. package/{docs → guide}/rules/no-raw-element.md +13 -8
  58. package/{docs → guide}/rules/render-invariant.md +4 -3
  59. package/{docs → guide}/schema-system.md +97 -16
  60. package/{docs → guide}/theme-and-css.md +3 -2
  61. package/guide/upgrading.md +61 -0
  62. package/{docs → guide}/vocabulary.md +3 -1
  63. package/package.json +9 -3
  64. package/dist/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +0 -77
  65. package/dist/node_modules/magic-string/dist/magic-string.es.js +0 -939
  66. package/docs/recipes/fixture-states.md +0 -68
  67. /package/{docs → guide}/assets.md +0 -0
  68. /package/{docs → guide}/dev.md +0 -0
  69. /package/{docs → guide}/eslint.md +0 -0
  70. /package/{docs → guide}/new.md +0 -0
  71. /package/{docs → guide}/quickstart.md +0 -0
  72. /package/{docs → guide}/recipes/bind-property-fact.md +0 -0
  73. /package/{docs → guide}/recipes/fill-spec-decision.md +0 -0
  74. /package/{docs → guide}/recipes/interactive-island.md +0 -0
  75. /package/{docs → guide}/recipes/organize-section-folder.md +0 -0
  76. /package/{docs → guide}/recipes/prepare-submission.md +0 -0
  77. /package/{docs → guide}/recipes/second-template.md +0 -0
  78. /package/{docs → guide}/recipes/select-slot.md +0 -0
  79. /package/{docs → guide}/recipes/static-asset.md +0 -0
  80. /package/{docs → guide}/recipes/third-party-package.md +0 -0
  81. /package/{docs → guide}/rules/audit-severity.md +0 -0
  82. /package/{docs → guide}/rules/bundle-binary-asset.md +0 -0
  83. /package/{docs → guide}/rules/bundle-incomplete.md +0 -0
  84. /package/{docs → guide}/rules/css-reason.md +0 -0
  85. /package/{docs → guide}/rules/determinism-drift.md +0 -0
  86. /package/{docs → guide}/rules/image-bare-needs-reason.md +0 -0
  87. /package/{docs → guide}/rules/license-denied.md +0 -0
  88. /package/{docs → guide}/rules/lockfile-missing.md +0 -0
  89. /package/{docs → guide}/rules/lockfile-stale.md +0 -0
  90. /package/{docs → guide}/rules/manifest-invalid.md +0 -0
  91. /package/{docs → guide}/rules/missing-slot-marker.md +0 -0
  92. /package/{docs → guide}/rules/no-bare-css-import.md +0 -0
  93. /package/{docs → guide}/rules/no-client-directive-in-contract.md +0 -0
  94. /package/{docs → guide}/rules/no-client-runtime-in-server.md +0 -0
  95. /package/{docs → guide}/rules/no-css-import-from-render-path.md +0 -0
  96. /package/{docs → guide}/rules/no-hex.md +0 -0
  97. /package/{docs → guide}/rules/no-inline-style.md +0 -0
  98. /package/{docs → guide}/rules/no-nondeterminism.md +0 -0
  99. /package/{docs → guide}/rules/no-templates.md +0 -0
  100. /package/{docs → guide}/rules/parse-error.md +0 -0
  101. /package/{docs → guide}/rules/props-from-schema.md +0 -0
  102. /package/{docs → guide}/rules/schema-invalid.md +0 -0
  103. /package/{docs → guide}/rules/serializable-island-props.md +0 -0
  104. /package/{docs → guide}/rules/server-vs-client.md +0 -0
  105. /package/{docs → guide}/rules/sidebar-order.md +0 -0
  106. /package/{docs → guide}/rules/single-react.md +0 -0
  107. /package/{docs → guide}/rules/size-assets.md +0 -0
  108. /package/{docs → guide}/rules/size-island-bundle.md +0 -0
  109. /package/{docs → guide}/rules/size-renderer-bundle.md +0 -0
  110. /package/{docs → guide}/rules/size-section-css.md +0 -0
  111. /package/{docs → guide}/rules/slot-group-marker.md +0 -0
  112. /package/{docs → guide}/rules/slot-marker-literal.md +0 -0
  113. /package/{docs → guide}/rules/typecheck.md +0 -0
  114. /package/{docs → guide}/rules/unlayered-fence.md +0 -0
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ import { AddressParts, TRANSFORMS, TRANSFORM_NAMES, TransformDescriptor, Transfo
6
6
  import { VariantSelectorFn, VariantsMeta, makeResolveVariants } from "./schema/resolve-variants.js";
7
7
  import { ContentSlot, SLOT_TYPE_DESCRIPTORS, Slot as Slot$1, SlotDef, defaultForSlotType, slotDefTypes } from "./schema/slot-types.js";
8
8
  import { VERIFICATION_WIDTHS, VerificationWidth } from "./design-system/breakpoints.js";
9
- import { FrameConfig, ImageCollectionValue, ImageValue, PoiRow } from "./schema/runtime-values.js";
9
+ import { FrameConfig, ImageCollectionValue, ImageValue, PlaybackConfig, PoiRow, VideoCollectionValue, VideoValue } from "./schema/runtime-values.js";
10
10
  import { SectionNav } from "./schema/section-nav.js";
11
11
  import { OptionValue, SectionProps, SlotValue } from "./schema/section-props.js";
12
12
  import { derived, direct } from "./schema/source.js";
@@ -23,6 +23,7 @@ import { Section } from "./primitives/Section.js";
23
23
  import { Slot } from "./primitives/Slot.js";
24
24
  import { SlotGroup } from "./primitives/SlotGroup.js";
25
25
  import { SlotItem } from "./primitives/SlotItem.js";
26
+ import { Video } from "./primitives/Video.js";
26
27
  import "./primitives/index.js";
27
28
  import { ATTR_SECTION_INSTANCE_ID, ATTR_SLOT_GROUP, ATTR_SLOT_ID, ATTR_SLOT_ITEM, ATTR_SLOT_TEXT_LEAF, ATTR_TR_ENHANCE } from "./contracts/markers.js";
28
29
  import { TokenNameSchema, TokenTheme, TokenThemeSchema, compileThemeToCss } from "./design-system/theme.js";
@@ -38,4 +39,4 @@ import { TokenNameSchema, TokenTheme, TokenThemeSchema, compileThemeToCss } from
38
39
  */
39
40
  declare const KIT_VERSION: string;
40
41
  //#endregion
41
- export { ATTR_ISLAND_ID, ATTR_ISLAND_KEY, ATTR_ISLAND_PROPS, ATTR_SECTION_INSTANCE_ID, ATTR_SLOT_GROUP, ATTR_SLOT_ID, ATTR_SLOT_ITEM, ATTR_SLOT_TEXT_LEAF, ATTR_TR_ENHANCE, type AddressParts, COLLECTIONS, CONTENT_SLOT_TYPES, type CollectionField, type CollectionIdKey, type CollectionOwnership, ContentSlot, type ContentSlotType, type Coordinates, DECISION_TYPES, DERIVED_INPUT_FIELDS, Decision, type DecisionType, type DerivedInputName, FORMATTER_NAMES, FillSpec, type FillSpecShape, type FixtureModule, FixtureModuleShape, type FixtureOverride, type FloorplanAssignment, type FormatterName, type FrameConfig, ISLAND_ELEMENT, Image, type ImageCollectionValue, type ImageValue, IslandComponent, IslandEditorOptions, IslandMarker, type IslandMarkerProps, IslandModule, IslandProps, IslandPropsError, KIT_VERSION, OptionDef, type OptionValue, type PicturePlan, type PictureSourceSpec, type PoiRow, type PropertyFacts, REFERRAL_FIELDS, REFERRAL_IMAGE_FIELDS, REF_SLOT_TYPES, type RefSlotType, type ReferralField, type ReferralImageField, type ResolvedFixture, type ResponsiveImage, type ResponsiveSource, SLOT_TYPES, SLOT_TYPE_DESCRIPTORS, SOURCE_FIELDS, Section, SectionMeta, type SectionMetaShape, type SectionNav, type SectionProps, SectionSchema, type SectionSchemaResolved, type SectionSchemaShape, ShieldMode, Slot, SlotDef, type Slot$1 as SlotDefinition, SlotGroup, SlotGroupDef, type SlotGroupDefShape, SlotItem, type SlotType, type SlotValue, type SourceFieldContainer, type SourceFieldDescriptor, type SourceFieldName, type SourceFieldValueType, TRANSFORMS, TRANSFORM_NAMES, TemplateManifest, type TemplateManifestShape, TokenNameSchema, type TokenTheme, TokenThemeSchema, type TransformDescriptor, type TransformFn, type TransformInput, type TransformName, type TransformUnit, type UnitRow, VERIFICATION_WIDTHS, type VariantSelectorFn, type VariantsMeta, type VerificationWidth, type WritebackTarget, buildFixtureSet, buildInvariantFixtures, collectionForSlot, compileThemeToCss, defaultForSlotType, derived, deserializeIslandProps, direct, islandId, makeResolveVariants, serializeIslandProps, slotDefTypes };
42
+ export { ATTR_ISLAND_ID, ATTR_ISLAND_KEY, ATTR_ISLAND_PROPS, ATTR_SECTION_INSTANCE_ID, ATTR_SLOT_GROUP, ATTR_SLOT_ID, ATTR_SLOT_ITEM, ATTR_SLOT_TEXT_LEAF, ATTR_TR_ENHANCE, type AddressParts, COLLECTIONS, CONTENT_SLOT_TYPES, type CollectionField, type CollectionIdKey, type CollectionOwnership, ContentSlot, type ContentSlotType, type Coordinates, DECISION_TYPES, DERIVED_INPUT_FIELDS, Decision, type DecisionType, type DerivedInputName, FORMATTER_NAMES, FillSpec, type FillSpecShape, type FixtureModule, FixtureModuleShape, type FixtureOverride, type FloorplanAssignment, type FormatterName, type FrameConfig, ISLAND_ELEMENT, Image, type ImageCollectionValue, type ImageValue, IslandComponent, IslandEditorOptions, IslandMarker, type IslandMarkerProps, IslandModule, IslandProps, IslandPropsError, KIT_VERSION, OptionDef, type OptionValue, type PicturePlan, type PictureSourceSpec, type PlaybackConfig, type PoiRow, type PropertyFacts, REFERRAL_FIELDS, REFERRAL_IMAGE_FIELDS, REF_SLOT_TYPES, type RefSlotType, type ReferralField, type ReferralImageField, type ResolvedFixture, type ResponsiveImage, type ResponsiveSource, SLOT_TYPES, SLOT_TYPE_DESCRIPTORS, SOURCE_FIELDS, Section, SectionMeta, type SectionMetaShape, type SectionNav, type SectionProps, SectionSchema, type SectionSchemaResolved, type SectionSchemaShape, ShieldMode, Slot, SlotDef, type Slot$1 as SlotDefinition, SlotGroup, SlotGroupDef, type SlotGroupDefShape, SlotItem, type SlotType, type SlotValue, type SourceFieldContainer, type SourceFieldDescriptor, type SourceFieldName, type SourceFieldValueType, TRANSFORMS, TRANSFORM_NAMES, TemplateManifest, type TemplateManifestShape, TokenNameSchema, type TokenTheme, TokenThemeSchema, type TransformDescriptor, type TransformFn, type TransformInput, type TransformName, type TransformUnit, type UnitRow, VERIFICATION_WIDTHS, type VariantSelectorFn, type VariantsMeta, type VerificationWidth, Video, type VideoCollectionValue, type VideoValue, type WritebackTarget, buildFixtureSet, buildInvariantFixtures, collectionForSlot, compileThemeToCss, defaultForSlotType, derived, deserializeIslandProps, direct, islandId, makeResolveVariants, serializeIslandProps, slotDefTypes };
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ import { Section } from "./primitives/Section.js";
17
17
  import { Slot } from "./primitives/Slot.js";
18
18
  import { SlotGroup } from "./primitives/SlotGroup.js";
19
19
  import { SlotItem } from "./primitives/SlotItem.js";
20
+ import { Video } from "./primitives/Video.js";
20
21
  import { TokenNameSchema, TokenThemeSchema, compileThemeToCss } from "./design-system/theme.js";
21
22
 
22
23
  //#region src/index.ts
@@ -32,4 +33,4 @@ import { TokenNameSchema, TokenThemeSchema, compileThemeToCss } from "./design-s
32
33
  const KIT_VERSION = version;
33
34
 
34
35
  //#endregion
35
- export { ATTR_ISLAND_ID, ATTR_ISLAND_KEY, ATTR_ISLAND_PROPS, ATTR_SECTION_INSTANCE_ID, ATTR_SLOT_GROUP, ATTR_SLOT_ID, ATTR_SLOT_ITEM, ATTR_SLOT_TEXT_LEAF, ATTR_TR_ENHANCE, COLLECTIONS, CONTENT_SLOT_TYPES, ContentSlot, DECISION_TYPES, DERIVED_INPUT_FIELDS, Decision, FORMATTER_NAMES, FillSpec, FixtureModuleShape, ISLAND_ELEMENT, Image, IslandMarker, IslandPropsError, KIT_VERSION, OptionDef, REFERRAL_FIELDS, REFERRAL_IMAGE_FIELDS, REF_SLOT_TYPES, SLOT_TYPES, SLOT_TYPE_DESCRIPTORS, SOURCE_FIELDS, Section, SectionMeta, SectionSchema, Slot, SlotDef, SlotGroup, SlotGroupDef, SlotItem, TRANSFORMS, TRANSFORM_NAMES, TemplateManifest, TokenNameSchema, TokenThemeSchema, VERIFICATION_WIDTHS, buildFixtureSet, buildInvariantFixtures, collectionForSlot, compileThemeToCss, defaultForSlotType, derived, deserializeIslandProps, direct, islandId, makeResolveVariants, serializeIslandProps, slotDefTypes };
36
+ export { ATTR_ISLAND_ID, ATTR_ISLAND_KEY, ATTR_ISLAND_PROPS, ATTR_SECTION_INSTANCE_ID, ATTR_SLOT_GROUP, ATTR_SLOT_ID, ATTR_SLOT_ITEM, ATTR_SLOT_TEXT_LEAF, ATTR_TR_ENHANCE, COLLECTIONS, CONTENT_SLOT_TYPES, ContentSlot, DECISION_TYPES, DERIVED_INPUT_FIELDS, Decision, FORMATTER_NAMES, FillSpec, FixtureModuleShape, ISLAND_ELEMENT, Image, IslandMarker, IslandPropsError, KIT_VERSION, OptionDef, REFERRAL_FIELDS, REFERRAL_IMAGE_FIELDS, REF_SLOT_TYPES, SLOT_TYPES, SLOT_TYPE_DESCRIPTORS, SOURCE_FIELDS, Section, SectionMeta, SectionSchema, Slot, SlotDef, SlotGroup, SlotGroupDef, SlotItem, TRANSFORMS, TRANSFORM_NAMES, TemplateManifest, TokenNameSchema, TokenThemeSchema, VERIFICATION_WIDTHS, Video, buildFixtureSet, buildInvariantFixtures, collectionForSlot, compileThemeToCss, defaultForSlotType, derived, deserializeIslandProps, direct, islandId, makeResolveVariants, serializeIslandProps, slotDefTypes };
@@ -2,6 +2,19 @@ import { ATTR_ISLAND_ID, ATTR_ISLAND_KEY, ATTR_ISLAND_PROPS, ISLAND_ELEMENT, Isl
2
2
 
3
3
  //#region src/islands/runtime.ts
4
4
  const roots = /* @__PURE__ */ new WeakMap();
5
+ function createEditorPropsBridge(react) {
6
+ const { createElement, useLayoutEffect, useState } = react;
7
+ return function EditorPropsBridge({ component, serverProps, editorProps }) {
8
+ const [props, setProps] = useState(serverProps);
9
+ useLayoutEffect(() => {
10
+ setProps({
11
+ ...serverProps,
12
+ ...editorProps
13
+ });
14
+ }, [serverProps, editorProps]);
15
+ return createElement(component, props);
16
+ };
17
+ }
5
18
  async function hydrateIslands(options) {
6
19
  const root = options.root ?? document;
7
20
  const markers = Array.from(root.querySelectorAll(`[${ATTR_ISLAND_KEY}]`)).filter((element) => !roots.has(element));
@@ -14,7 +27,9 @@ async function hydrateIslands(options) {
14
27
  if (payloads.has(id)) duplicateIds.add(id);
15
28
  payloads.set(id, script.textContent ?? "");
16
29
  }
17
- const [{ createElement }, { hydrateRoot }] = await Promise.all([import("react"), import("react-dom/client")]);
30
+ const [react, { hydrateRoot }] = await Promise.all([import("react"), import("react-dom/client")]);
31
+ const { createElement } = react;
32
+ const EditorPropsBridge = createEditorPropsBridge(react);
18
33
  const handles = [];
19
34
  for (const element of markers) {
20
35
  const key = element.getAttribute("data-tr-island") ?? "";
@@ -34,7 +49,12 @@ async function hydrateIslands(options) {
34
49
  if (options.shouldHydrate && !options.shouldHydrate(ctx)) continue;
35
50
  if (roots.has(element)) continue;
36
51
  if (!element.isConnected) continue;
37
- const reactRoot = hydrateRoot(element, createElement(module.default, props), {
52
+ const editorProps = options.applyEditorProps ? module.editor?.props : void 0;
53
+ const reactRoot = hydrateRoot(element, editorProps ? createElement(EditorPropsBridge, {
54
+ component: module.default,
55
+ serverProps: props,
56
+ editorProps
57
+ }) : createElement(module.default, props), {
38
58
  onUncaughtError: (renderError) => {
39
59
  options.onError?.(renderError, ctx);
40
60
  },
@@ -43,11 +63,6 @@ async function hydrateIslands(options) {
43
63
  }
44
64
  });
45
65
  roots.set(element, reactRoot);
46
- const editorProps = options.applyEditorProps ? module.editor?.props : void 0;
47
- if (editorProps) reactRoot.render(createElement(module.default, {
48
- ...props,
49
- ...editorProps
50
- }));
51
66
  handles.push(ctx);
52
67
  } catch (error) {
53
68
  options.onError?.(error, ctx);
@@ -1,3 +1,4 @@
1
+ import MagicString from "magic-string";
1
2
  //#region src/islands/wrap.d.ts
2
3
  /**
3
4
  * Rewrite a client module's source so its default export renders through
@@ -1,5 +1,5 @@
1
1
  import { hasClientDirective } from "../eslint/is-client-file.js";
2
- import MagicString from "../node_modules/magic-string/dist/magic-string.es.js";
2
+ import MagicString from "magic-string";
3
3
 
4
4
  //#region src/islands/wrap.ts
5
5
  const DEFAULT_EXPORT = /export\s+default\s+/;
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.4.0";
2
+ var version = "0.5.0";
3
3
 
4
4
  //#endregion
5
5
  export { version };
@@ -44,6 +44,6 @@ type Props = Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt"> & {
44
44
  * no descriptor it degrades to a plain single `<img src>`. All `<picture>` planning
45
45
  * lives in `buildPictureSources`.
46
46
  */
47
- declare function Image({ src, alt, aspectRatio, slotId, bare, srcMobile, mobileBreakpoint, responsive, responsiveMobile, sizes, maxRungWidth, className, loading, style, ...rest }: Props): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
47
+ declare function Image({ src, alt, aspectRatio, slotId, bare, srcMobile, mobileBreakpoint, responsive, responsiveMobile, sizes, maxRungWidth, className, loading, style, ...rest }: Props): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
48
48
  //#endregion
49
49
  export { Image };
@@ -0,0 +1,38 @@
1
+ import { PlaybackConfig } from "../schema/runtime-values.js";
2
+ import { VideoHTMLAttributes } from "react";
3
+ //#region src/primitives/Video.d.ts
4
+ type Props = Omit<VideoHTMLAttributes<HTMLVideoElement>, "src" | "poster" | "autoPlay" | "loop" | "muted" | "controls" | "playsInline" | "preload"> & {
5
+ /** Rendition URL. Null/empty renders the fallback rect — a supported state. */
6
+ src: string | null | undefined;
7
+ /** Poster frame URL — the first paint, and the whole render when `src` is empty. */
8
+ poster?: string | null;
9
+ /** Required accessible description. Empty string for a purely decorative loop. */
10
+ alt: string;
11
+ /** CSS aspect-ratio reserved on the wrapper; survives missing `src`. */
12
+ aspectRatio?: string;
13
+ /** Slot binding — emits `data-slot-id` on the wrapper for the editor click resolver. */
14
+ slotId?: string;
15
+ /** Author-declared playback (the slot's `playback` field). Absent ⇒ a plain controls player. */
16
+ playback?: PlaybackConfig | null;
17
+ /** Intrinsic rendition dimensions (VideoValue.width/height) — reserve the box, avoid a jump. */
18
+ width?: number;
19
+ height?: number;
20
+ };
21
+ /**
22
+ * Video — the hosted-video primitive. Sections never write a raw `<video>`.
23
+ *
24
+ * A wrapper div carries the `data-slot-id` marker and the aspect-ratio box, renders the
25
+ * poster (or a neutral fallback rect) when `src` is empty, and absolutely-positions the
26
+ * inner `<video>` with `object-cover` — the same framed contract as `<Image>`.
27
+ *
28
+ * **Playback is author-declared**, passed through verbatim from the slot's `playback`
29
+ * config; the primitive does not infer behavior from the asset. `autoplay` requires
30
+ * `muted` — that invariant is enforced on the schema at `check` time, so a video that
31
+ * reaches here with `autoplay` is already muted.
32
+ *
33
+ * There is no cropping, no editing, and no responsive ladder: one progressive MP4 is
34
+ * served per video, and the poster is baked upstream.
35
+ */
36
+ declare function Video({ src, poster, alt, aspectRatio, slotId, playback, width, height, className, ...rest }: Props): import("react").JSX.Element;
37
+ //#endregion
38
+ export { Video };
@@ -0,0 +1,63 @@
1
+ import { ATTR_SLOT_ID } from "../contracts/markers.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+
4
+ //#region src/primitives/Video.tsx
5
+ const DEFAULT_PLAYBACK = { controls: true };
6
+ /**
7
+ * Video — the hosted-video primitive. Sections never write a raw `<video>`.
8
+ *
9
+ * A wrapper div carries the `data-slot-id` marker and the aspect-ratio box, renders the
10
+ * poster (or a neutral fallback rect) when `src` is empty, and absolutely-positions the
11
+ * inner `<video>` with `object-cover` — the same framed contract as `<Image>`.
12
+ *
13
+ * **Playback is author-declared**, passed through verbatim from the slot's `playback`
14
+ * config; the primitive does not infer behavior from the asset. `autoplay` requires
15
+ * `muted` — that invariant is enforced on the schema at `check` time, so a video that
16
+ * reaches here with `autoplay` is already muted.
17
+ *
18
+ * There is no cropping, no editing, and no responsive ladder: one progressive MP4 is
19
+ * served per video, and the poster is baked upstream.
20
+ */
21
+ function Video({ src, poster, alt, aspectRatio, slotId, playback, width, height, className, ...rest }) {
22
+ const containerCls = ["tr-image-frame relative block w-full overflow-hidden", className].filter(Boolean).join(" ");
23
+ const containerStyle = aspectRatio ? { aspectRatio } : void 0;
24
+ const slotAttrs = slotId ? { [ATTR_SLOT_ID]: slotId } : {};
25
+ if (!src) return /* @__PURE__ */ jsx("div", {
26
+ className: containerCls,
27
+ style: containerStyle,
28
+ role: "img",
29
+ "aria-label": alt || void 0,
30
+ ...slotAttrs,
31
+ children: poster ? /* @__PURE__ */ jsx("img", {
32
+ src: poster,
33
+ alt: "",
34
+ loading: "lazy",
35
+ decoding: "async",
36
+ className: "absolute inset-0 h-full w-full object-cover"
37
+ }) : null
38
+ });
39
+ const p = playback ?? DEFAULT_PLAYBACK;
40
+ return /* @__PURE__ */ jsx("div", {
41
+ className: containerCls,
42
+ style: containerStyle,
43
+ ...slotAttrs,
44
+ children: /* @__PURE__ */ jsx("video", {
45
+ src,
46
+ poster: poster ?? void 0,
47
+ "aria-label": alt || void 0,
48
+ width,
49
+ height,
50
+ autoPlay: p.autoplay,
51
+ loop: p.loop,
52
+ muted: p.muted,
53
+ controls: p.controls,
54
+ playsInline: p.playsinline,
55
+ preload: p.preload,
56
+ className: "absolute inset-0 h-full w-full object-cover",
57
+ ...rest
58
+ })
59
+ });
60
+ }
61
+
62
+ //#endregion
63
+ export { Video };
@@ -4,4 +4,5 @@ import { Section } from "./Section.js";
4
4
  import { Slot } from "./Slot.js";
5
5
  import { SlotGroup } from "./SlotGroup.js";
6
6
  import { SlotItem } from "./SlotItem.js";
7
- export { Image, type PicturePlan, type PictureSourceSpec, Section, Slot, SlotGroup, SlotItem };
7
+ import { Video } from "./Video.js";
8
+ export { Image, type PicturePlan, type PictureSourceSpec, Section, Slot, SlotGroup, SlotItem, Video };
@@ -1,15 +1,15 @@
1
1
  //#region src/rules/registry.ts
2
2
  /**
3
- * `no-hex` → `docs/rules/no-hex.md`.
3
+ * `no-hex` → `guide/rules/no-hex.md`.
4
4
  *
5
5
  * Package-relative on purpose: the caller decides what to resolve it against. A
6
6
  * printer prefixes the resolved package root so an author sees a path they can open
7
- * with no network (`node_modules/@homepages/template-kit/docs/rules/no-hex.md`);
7
+ * with no network (`node_modules/@homepages/template-kit/guide/rules/no-hex.md`);
8
8
  * ESLint's `meta.docs.url` uses it as-is. This module never touches the filesystem —
9
9
  * the ESLint preset imports it.
10
10
  */
11
11
  function docsPathForRule(id) {
12
- return `docs/rules/${id}.md`;
12
+ return `guide/rules/${id}.md`;
13
13
  }
14
14
 
15
15
  //#endregion
@@ -5,7 +5,7 @@ import { AddressParts, TRANSFORMS, TRANSFORM_NAMES, TransformDescriptor, Transfo
5
5
  import { VariantSelectorFn, VariantsMeta, makeResolveVariants } from "./resolve-variants.js";
6
6
  import { ContentSlot, SLOT_TYPE_DESCRIPTORS, Slot, SlotDef, defaultForSlotType, slotDefTypes } from "./slot-types.js";
7
7
  import { VERIFICATION_WIDTHS, VerificationWidth } from "../design-system/breakpoints.js";
8
- import { FrameConfig, ImageCollectionValue, ImageValue, PoiRow } from "./runtime-values.js";
8
+ import { FrameConfig, ImageCollectionValue, ImageValue, PlaybackConfig, PoiRow, VideoCollectionValue, VideoValue } from "./runtime-values.js";
9
9
  import { SectionNav } from "./section-nav.js";
10
10
  import { OptionValue, SectionProps, SlotValue } from "./section-props.js";
11
11
  import { derived, direct } from "./source.js";
@@ -13,4 +13,4 @@ import { OptionDef, SectionMeta, SectionMetaShape, SectionSchema, SectionSchemaR
13
13
  import { Decision, FillSpec, FillSpecShape } from "./fill-spec.js";
14
14
  import { FixtureModule, FixtureModuleShape, FixtureOverride, ResolvedFixture, buildFixtureSet, buildInvariantFixtures } from "./fixture-schema.js";
15
15
  import { TemplateManifest, TemplateManifestShape } from "./manifest.js";
16
- export { type AddressParts, COLLECTIONS, CONTENT_SLOT_TYPES, type CollectionField, type CollectionIdKey, type CollectionOwnership, ContentSlot, type ContentSlotType, type Coordinates, DECISION_TYPES, DERIVED_INPUT_FIELDS, Decision, type DecisionType, type DerivedInputName, FORMATTER_NAMES, FillSpec, type FillSpecShape, type FixtureModule, FixtureModuleShape, type FixtureOverride, type FloorplanAssignment, type FormatterName, type FrameConfig, type ImageCollectionValue, type ImageValue, OptionDef, type OptionValue, type PoiRow, type PropertyFacts, REFERRAL_FIELDS, REFERRAL_IMAGE_FIELDS, REF_SLOT_TYPES, type RefSlotType, type ReferralField, type ReferralImageField, type ResolvedFixture, type ResponsiveImage, type ResponsiveSource, SLOT_TYPES, SLOT_TYPE_DESCRIPTORS, SOURCE_FIELDS, SectionMeta, type SectionMetaShape, type SectionNav, type SectionProps, SectionSchema, type SectionSchemaResolved, type SectionSchemaShape, type Slot, SlotDef, SlotGroupDef, type SlotGroupDefShape, type SlotType, type SlotValue, type SourceFieldContainer, type SourceFieldDescriptor, type SourceFieldName, type SourceFieldValueType, TRANSFORMS, TRANSFORM_NAMES, TemplateManifest, type TemplateManifestShape, type TransformDescriptor, type TransformFn, type TransformInput, type TransformName, type TransformUnit, type UnitRow, VERIFICATION_WIDTHS, type VariantSelectorFn, type VariantsMeta, type VerificationWidth, type WritebackTarget, buildFixtureSet, buildInvariantFixtures, collectionForSlot, defaultForSlotType, derived, direct, makeResolveVariants, slotDefTypes };
16
+ export { type AddressParts, COLLECTIONS, CONTENT_SLOT_TYPES, type CollectionField, type CollectionIdKey, type CollectionOwnership, ContentSlot, type ContentSlotType, type Coordinates, DECISION_TYPES, DERIVED_INPUT_FIELDS, Decision, type DecisionType, type DerivedInputName, FORMATTER_NAMES, FillSpec, type FillSpecShape, type FixtureModule, FixtureModuleShape, type FixtureOverride, type FloorplanAssignment, type FormatterName, type FrameConfig, type ImageCollectionValue, type ImageValue, OptionDef, type OptionValue, type PlaybackConfig, type PoiRow, type PropertyFacts, REFERRAL_FIELDS, REFERRAL_IMAGE_FIELDS, REF_SLOT_TYPES, type RefSlotType, type ReferralField, type ReferralImageField, type ResolvedFixture, type ResponsiveImage, type ResponsiveSource, SLOT_TYPES, SLOT_TYPE_DESCRIPTORS, SOURCE_FIELDS, SectionMeta, type SectionMetaShape, type SectionNav, type SectionProps, SectionSchema, type SectionSchemaResolved, type SectionSchemaShape, type Slot, SlotDef, SlotGroupDef, type SlotGroupDefShape, type SlotType, type SlotValue, type SourceFieldContainer, type SourceFieldDescriptor, type SourceFieldName, type SourceFieldValueType, TRANSFORMS, TRANSFORM_NAMES, TemplateManifest, type TemplateManifestShape, type TransformDescriptor, type TransformFn, type TransformInput, type TransformName, type TransformUnit, type UnitRow, VERIFICATION_WIDTHS, type VariantSelectorFn, type VariantsMeta, type VerificationWidth, type VideoCollectionValue, type VideoValue, type WritebackTarget, buildFixtureSet, buildInvariantFixtures, collectionForSlot, defaultForSlotType, derived, direct, makeResolveVariants, slotDefTypes };
@@ -32,6 +32,25 @@ type ImageValue = {
32
32
  responsive?: ResponsiveImage | null;
33
33
  };
34
34
  type ImageCollectionValue = ImageValue[];
35
+ type PlaybackConfig = {
36
+ autoplay?: boolean;
37
+ loop?: boolean;
38
+ muted?: boolean;
39
+ controls?: boolean;
40
+ playsinline?: boolean;
41
+ /** Bandwidth lever: "none" for a below-fold video most visitors never reach. */
42
+ preload?: "none" | "metadata" | "auto";
43
+ };
44
+ type VideoValue = {
45
+ video_id: string;
46
+ url: string;
47
+ /** Poster frame URL — always present; the transcode derives one (D9). */
48
+ poster: string;
49
+ alt: string;
50
+ width?: number;
51
+ height?: number;
52
+ };
53
+ type VideoCollectionValue = VideoValue[];
35
54
  type PoiRow = {
36
55
  id: string;
37
56
  place_id: string;
@@ -42,4 +61,4 @@ type PoiRow = {
42
61
  distance_m: number | null;
43
62
  };
44
63
  //#endregion
45
- export { type Coordinates, type FloorplanAssignment, FrameConfig, ImageCollectionValue, ImageValue, PoiRow, type PropertyFacts, type UnitRow };
64
+ export { type Coordinates, type FloorplanAssignment, FrameConfig, ImageCollectionValue, ImageValue, PlaybackConfig, PoiRow, type PropertyFacts, type UnitRow, VideoCollectionValue, VideoValue };
@@ -1,4 +1,4 @@
1
- import { ImageCollectionValue, ImageValue, PoiRow } from "./runtime-values.js";
1
+ import { ImageCollectionValue, ImageValue, PoiRow, VideoCollectionValue, VideoValue } from "./runtime-values.js";
2
2
  import { SectionNav } from "./section-nav.js";
3
3
  import { SectionSchemaShape } from "./section-schema.js";
4
4
  //#region src/schema/section-props.d.ts
@@ -25,6 +25,13 @@ type SlotValue<S> = S extends {
25
25
  } ? ImageValue | null : S extends {
26
26
  type: "image_collection";
27
27
  } ? ImageCollectionValue : S extends {
28
+ type: "video";
29
+ required: true;
30
+ } ? VideoValue : S extends {
31
+ type: "video";
32
+ } ? VideoValue | null : S extends {
33
+ type: "video_collection";
34
+ } ? VideoCollectionValue : S extends {
28
35
  type: "list";
29
36
  element: {
30
37
  type: "text";
@@ -2,7 +2,7 @@ import { Coordinates, FloorplanAssignment, PropertyFacts, UnitRow } from "./prop
2
2
  import { CONTENT_SLOT_TYPES, REFERRAL_FIELDS, REF_SLOT_TYPES, SLOT_TYPES, SlotType } from "../contracts/slot-catalog.js";
3
3
  import { VariantSelectorFn } from "./resolve-variants.js";
4
4
  import { ContentSlot, Slot, SlotDef, defaultForSlotType } from "./slot-types.js";
5
- import { FrameConfig, ImageCollectionValue, ImageValue, PoiRow } from "./runtime-values.js";
5
+ import { FrameConfig, ImageCollectionValue, ImageValue, PlaybackConfig, PoiRow, VideoCollectionValue, VideoValue } from "./runtime-values.js";
6
6
  import { OptionValue, SectionProps, SlotValue } from "./section-props.js";
7
7
  import { derived, direct } from "./source.js";
8
8
  import { z } from "zod";
@@ -144,4 +144,4 @@ type SectionSchemaResolved = Omit<z.infer<typeof SectionSchema>, "meta"> & {
144
144
  }>;
145
145
  };
146
146
  //#endregion
147
- export { CONTENT_SLOT_TYPES, ContentSlot, type Coordinates, type FloorplanAssignment, type FrameConfig, type ImageCollectionValue, type ImageValue, OptionDef, type OptionValue, type PoiRow, type PropertyFacts, REFERRAL_FIELDS, REF_SLOT_TYPES, SLOT_TYPES, SectionMeta, SectionMetaShape, type SectionProps, SectionSchema, SectionSchemaResolved, SectionSchemaShape, type Slot, SlotDef, SlotGroupDef, SlotGroupDefShape, type SlotType, type SlotValue, type UnitRow, defaultForSlotType, derived, direct };
147
+ export { CONTENT_SLOT_TYPES, ContentSlot, type Coordinates, type FloorplanAssignment, type FrameConfig, type ImageCollectionValue, type ImageValue, OptionDef, type OptionValue, type PlaybackConfig, type PoiRow, type PropertyFacts, REFERRAL_FIELDS, REF_SLOT_TYPES, SLOT_TYPES, SectionMeta, SectionMetaShape, type SectionProps, SectionSchema, SectionSchemaResolved, SectionSchemaShape, type Slot, SlotDef, SlotGroupDef, SlotGroupDefShape, type SlotType, type SlotValue, type UnitRow, type VideoCollectionValue, type VideoValue, defaultForSlotType, derived, direct };
@@ -19,6 +19,8 @@ declare const ContentSlot: z.ZodObject<{
19
19
  object_list: "object_list";
20
20
  poi_list: "poi_list";
21
21
  url: "url";
22
+ video: "video";
23
+ video_collection: "video_collection";
22
24
  }>;
23
25
  editable_by_user: z.ZodDefault<z.ZodBoolean>;
24
26
  required: z.ZodDefault<z.ZodBoolean>;
@@ -84,6 +86,18 @@ type ImageCollectionSlot = SlotBase & {
84
86
  crop?: unknown;
85
87
  frame?: unknown;
86
88
  };
89
+ type VideoSlot = SlotBase & {
90
+ type: "video";
91
+ frame?: unknown;
92
+ playback?: unknown;
93
+ };
94
+ type VideoCollectionSlot = SlotBase & {
95
+ type: "video_collection";
96
+ min?: number;
97
+ max?: number;
98
+ frame?: unknown;
99
+ playback?: unknown;
100
+ };
87
101
  type ListSlot = SlotBase & {
88
102
  type: "list";
89
103
  min?: number;
@@ -107,7 +121,7 @@ type ObjectListSlot = SlotBase & {
107
121
  item_order?: string[];
108
122
  item_layout?: (string | [string, string])[];
109
123
  };
110
- type Slot = TextSlot | NumberSlot | SelectSlot | ImageSlot | ImageCollectionSlot | ListSlot | PoiListSlot | UrlSlot | ObjectListSlot;
124
+ type Slot = TextSlot | NumberSlot | SelectSlot | ImageSlot | ImageCollectionSlot | VideoSlot | VideoCollectionSlot | ListSlot | PoiListSlot | UrlSlot | ObjectListSlot;
111
125
  declare function defaultForSlotType(type: string): unknown;
112
126
  //#endregion
113
127
  export { ContentSlot, SLOT_TYPE_DESCRIPTORS, Slot, SlotDef, defaultForSlotType, slotDefTypes };
@@ -32,6 +32,18 @@ const frameConfigShape = { frame: z.object({
32
32
  nominal: z.boolean().optional()
33
33
  }).strict().optional()
34
34
  }).strict().optional() };
35
+ const playbackConfigShape = { playback: z.object({
36
+ autoplay: z.boolean().optional(),
37
+ loop: z.boolean().optional(),
38
+ muted: z.boolean().optional(),
39
+ controls: z.boolean().optional(),
40
+ playsinline: z.boolean().optional(),
41
+ preload: z.enum([
42
+ "none",
43
+ "metadata",
44
+ "auto"
45
+ ]).optional()
46
+ }).strict().refine((p) => p.autoplay !== true || p.muted === true, { message: "autoplay requires muted (browsers block unmuted autoplay)" }).optional() };
35
47
  const slotMetadataShape = {
36
48
  display_name: z.string().optional(),
37
49
  placeholder: z.string().optional(),
@@ -110,6 +122,26 @@ const SLOT_TYPE_DESCRIPTORS = {
110
122
  },
111
123
  default: () => []
112
124
  },
125
+ video: {
126
+ shape: {
127
+ ...frameConfigShape,
128
+ ...playbackConfigShape
129
+ },
130
+ default: () => ({
131
+ url: "",
132
+ poster: "",
133
+ alt: ""
134
+ })
135
+ },
136
+ video_collection: {
137
+ shape: {
138
+ min: z.number().int().nonnegative().optional(),
139
+ max: z.number().int().positive().optional(),
140
+ ...frameConfigShape,
141
+ ...playbackConfigShape
142
+ },
143
+ default: () => []
144
+ },
113
145
  list: {
114
146
  shape: {
115
147
  min: z.number().int().nonnegative().optional(),
@@ -155,6 +187,8 @@ const SLOT_DEF_MEMBERS = [
155
187
  slotMember("select", SLOT_TYPE_DESCRIPTORS.select.shape),
156
188
  slotMember("image", SLOT_TYPE_DESCRIPTORS.image.shape),
157
189
  slotMember("image_collection", SLOT_TYPE_DESCRIPTORS.image_collection.shape),
190
+ slotMember("video", SLOT_TYPE_DESCRIPTORS.video.shape),
191
+ slotMember("video_collection", SLOT_TYPE_DESCRIPTORS.video_collection.shape),
158
192
  slotMember("list", SLOT_TYPE_DESCRIPTORS.list.shape),
159
193
  slotMember("poi_list", SLOT_TYPE_DESCRIPTORS.poi_list.shape),
160
194
  slotMember("url", SLOT_TYPE_DESCRIPTORS.url.shape),
@@ -5,6 +5,7 @@ const LIST_TYPES = /* @__PURE__ */ new Set([
5
5
  "list",
6
6
  "poi_list",
7
7
  "image_collection",
8
+ "video_collection",
8
9
  "object_list"
9
10
  ]);
10
11
  function emptyValueFor(type) {
@@ -1,12 +1,12 @@
1
1
  ---
2
- purpose: Router for the docs corpus that ships inside the @homepages/template-kit npm package.
2
+ purpose: Router for the guide corpus that ships inside the @homepages/template-kit npm package.
3
3
  status: living
4
4
  related: []
5
5
  updated: 2026-07-16
6
6
  ---
7
- # template-kit docs — router
7
+ # template-kit guide — router
8
8
 
9
- This tree **ships in the npm tarball** (`docs/` is listed in `package.json`'s
9
+ This tree **ships in the npm tarball** (`guide/` is listed in `package.json`'s
10
10
  `files`), so it is written for an outside audience: template authors and their
11
11
  LLM agents, reading it from inside `node_modules`. Keep it free of internal-only
12
12
  detail.
@@ -16,9 +16,9 @@ detail.
16
16
  | `quickstart.md` | The cold-start tutorial: create a workspace, preview the starter's `hero` section, read its four files, make one edit, and check it — owns the author-loop anchor every recipe links to | your first section, end to end |
17
17
  | `overview.md` | The one-page mental model: workspace → templates → sections, the four reserved files, and the server-vs-client mapping | orienting yourself before your first section |
18
18
  | `llms.txt` | Machine-readable index of this corpus for an agent working inside an installed kit | orienting an agent in a consuming workspace |
19
- | `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 |
20
- | `vocabulary.md` | The closed authoring vocabulary — the 9 slot types, the `direct()` source fields, and the `derived()` transforms, with selection semantics | choosing which slot type / source / transform to use |
21
- | `primitives.md` | The five contract primitives (`Section`, `Slot`, `SlotItem`, `SlotGroup`, `Image`) and the marker attributes they emit | writing a section's markup |
19
+ | `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 scenario API + fill-by-source rule | authoring or changing any section |
20
+ | `vocabulary.md` | The closed authoring vocabulary — the 11 slot types, the `direct()` source fields, and the `derived()` transforms, with selection semantics | choosing which slot type / source / transform to use |
21
+ | `primitives.md` | The six contract primitives (`Section`, `Slot`, `SlotItem`, `SlotGroup`, `Image`, `Video`) and the marker attributes they emit | writing a section's markup |
22
22
  | `assets.md` | Static assets (svg/images/fonts): where they live (template level or a section folder, if referenced), referencing them from a section's CSS with `url()`/`@font-face` or importing them into a component, and the asset-vs-content-image distinction | shipping a logo, texture, icon, or brand font with a template |
23
23
  | `theme-and-css.md` | The two CSS entries, the one-Tailwind-import rule, `TokenTheme` (your own token names → your own Tailwind utilities), the `template-kit theme` command that generates `theme.css` from `theme.ts`, 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 |
24
24
  | `islands.md` | Interactivity: `"use client"` components, the serializable-props rule, editor options, the loader | anything interactive in a section |
@@ -27,5 +27,6 @@ detail.
27
27
  | `dev.md` | The `template-kit dev` CLI command: the local preview server, its home launcher and two tool cards (full template viewer, canvas mirror), the canvas mirror's selection overlay and per-slot edit panel, live reload, the content-override protocol, and the render/inspect/screenshot endpoints | previewing sections as you author them |
28
28
  | `new.md` | The `template-kit new` / `new-section` CLI commands: scaffolding a new template with a working starter section, or adding a bare section to an existing template | adding a template or a section to a workspace |
29
29
  | `pack.md` | The `template-kit pack` CLI command: builds a template's submission zip — template source, the workspace lockfile, and a stamped `pack-manifest.json`; refuses on a failing `check`, a missing lockfile, or a local-protocol dependency | packaging a template for submission |
30
+ | `upgrading.md` | Moving to a new kit version: what a patch vs a minor means while the kit is pre-1.0, why `npm update` cannot cross a minor, and how long the previous line keeps being accepted | a new kit version shipped, or your kit version was refused at submission |
30
31
  | `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 |
31
32
  | `recipes/` | Task recipes: how do I add a slot, bind a fact, add an island, package a template… Each is a minimal delta from the starter's `hero` section, ending in the author loop. `recipes/INDEX.md` routes them by task | doing something specific and not sure which file to touch |
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  purpose: The `template-kit check` command — what it runs, its flags, its output shape, and its exit code.
3
3
  status: living
4
- related: [INDEX.md, rules/INDEX.md, eslint.md, theme-and-css.md, dev.md]
5
- updated: 2026-07-15
4
+ related: [INDEX.md, rules/INDEX.md, eslint.md, theme-and-css.md, dev.md, upgrading.md]
5
+ updated: 2026-07-16
6
6
  ---
7
7
  # template-kit check
8
8
 
@@ -2,7 +2,7 @@
2
2
  purpose: How interactivity works in a template — client components ("islands"), their props contract, and their editor options.
3
3
  status: living
4
4
  related: [INDEX.md, eslint.md, dev.md]
5
- updated: 2026-07-15
5
+ updated: 2026-07-16
6
6
  ---
7
7
 
8
8
  # Islands: interactivity in a template
@@ -99,6 +99,11 @@ export const editor: IslandEditorOptions = {
99
99
  };
100
100
  ```
101
101
 
102
+ `props` merge in as an update immediately after the island hydrates, not as part of its
103
+ first mount — so an island must read them during render. A value read only at mount time
104
+ (a `useState` initializer, an `<input defaultValue>`) sees the real prop, and the
105
+ editor-only override never reaches it.
106
+
102
107
  Omit `editor` entirely and the island simply renders as static markup in the editor —
103
108
  which is the right choice for most islands.
104
109
 
@@ -4,7 +4,9 @@
4
4
  > inside the npm package so an agent can read it locally, versioned in lockstep
5
5
  > with the code it describes.
6
6
 
7
- Status: 0.0.x. The schema system, the contract primitives, and the theme system are
7
+ Status: 0.x — pre-1.0, so a minor is the breaking axis and a patch carries features
8
+ as well as fixes; see [Upgrading](upgrading.md). The schema system, the contract
9
+ primitives, and the theme system are
8
10
  complete — a section is authorable end to end against the kit. The `template-kit`
9
11
  CLI's `check`, `theme`, `dev`, `pack`, `new`, and `new-section` commands are
10
12
  implemented — see [Check](check.md), [Theme and CSS](theme-and-css.md#generating-themecss),
@@ -12,8 +14,10 @@ implemented — see [Check](check.md), [Theme and CSS](theme-and-css.md#generati
12
14
  commands arrive with the surfaces they drive.
13
15
 
14
16
  Everything an author writes a section against is imported from the package root —
15
- `@homepages/template-kit`, no subpath imports. The two CSS entries
16
- (`./styles.css`, `./base.css`) are the deliberate exception.
17
+ `@homepages/template-kit`. The exceptions are deliberate: the two CSS entries
18
+ (`./styles.css`, `./base.css`), and `./fixtures` — the golden scenarios, which only
19
+ `fixtures.ts` imports, kept off the root barrel so dev-only sample data can never
20
+ reach a published renderer's import graph.
17
21
 
18
22
  ## Start here
19
23
 
@@ -23,6 +27,16 @@ read [Overview](overview.md). For the closed sets — slot types, `direct()`
23
27
  sources, `derived()` transforms — see [Vocabulary](vocabulary.md). When a lint
24
28
  or `check` prints a rule id, resolve it at `rules/<id>.md`.
25
29
 
30
+ Writing or editing a section's `fixtures.ts`? Fold fixture creation into the same
31
+ edit, by default — don't invent placeholder content. Pick one of the kit's three
32
+ scenarios (`@homepages/template-kit/fixtures`) and fill every slot by its
33
+ fill-spec source: a `direct()` slot reads the fact straight off the scenario, a
34
+ `derived()` slot goes through a helper (`propertyMetrics`, `poisByCategory`,
35
+ `contactCards`, `primaryContact`), and an AI-filled slot gets a short sample
36
+ grounded in the scenario's facts — never lorem ipsum. See
37
+ [Schema system § fixtures.ts](schema-system.md#fixturests--content-for-local-preview)
38
+ and [Pick a scenario](recipes/pick-a-scenario.md).
39
+
26
40
  ## Docs
27
41
 
28
42
  - [Quickstart](quickstart.md): the cold-start tutorial — create a workspace,
@@ -32,18 +46,19 @@ or `check` prints a rule id, resolve it at `rules/<id>.md`.
32
46
  sections, the four reserved files, and the server-vs-client mapping.
33
47
  - [Schema system](schema-system.md): the section authoring contract. A section is
34
48
  four files — `schema.ts` (slots + options + `meta.groups`), `fill-spec.ts` (how AI
35
- fills them), `fixtures.ts` (preview content, built on the kit's golden property
36
- dataset), `Renderer.tsx` (markup, with props DERIVED from the schema via
37
- `SectionProps<typeof schema>`). Covers the `as const satisfies` requirement, binding
38
- slots to property facts with `direct()`/`derived()` over a closed vocabulary, and
39
- fact-driven variants.
40
- - [Vocabulary](vocabulary.md): the closed authoring vocabulary — the 9 slot
49
+ fills them), `fixtures.ts` (preview content fill every slot from a scenario, by
50
+ its fill-spec source), `Renderer.tsx` (markup, with props DERIVED from the schema
51
+ via `SectionProps<typeof schema>`). Covers the `as const satisfies` requirement,
52
+ binding slots to property facts with `direct()`/`derived()` over a closed
53
+ vocabulary, fact-driven variants, and the scenario API + fill-by-source rule.
54
+ - [Vocabulary](vocabulary.md): the closed authoring vocabulary — the 11 slot
41
55
  types, the `direct()` source fields, and the `derived()` transforms, with
42
56
  selection semantics (which one to pick).
43
- - [Contract primitives](primitives.md): the only five components the kit ships —
57
+ - [Contract primitives](primitives.md): the only six components the kit ships —
44
58
  `Section` (full-bleed section root), `Slot` (`data-slot-id`), `SlotItem` (per-item
45
- handle in an `object_list` / `image_collection`), `SlotGroup` (`data-slot-group`),
46
- and `Image` (responsive `<picture>`, empty-state box). Plus the marker-attribute
59
+ handle in an `object_list` / `image_collection` / `video_collection`), `SlotGroup`
60
+ (`data-slot-group`), `Image` (responsive `<picture>`, empty-state box), and `Video`
61
+ (poster frame, author-declared playback, empty-state box). Plus the marker-attribute
47
62
  contract (`ATTR_SLOT_ID`, `ATTR_SLOT_ITEM`, …). Presentation is the template's own JSX.
48
63
  - [Theme and CSS](theme-and-css.md): the two CSS entries and how they load; import
49
64
  Tailwind exactly once; `base.css` ships no reset of its own — it requires the
@@ -79,14 +94,19 @@ or `check` prints a rule id, resolve it at `rules/<id>.md`.
79
94
  submission zip (the template's source, the workspace lockfile, and a stamped
80
95
  `pack-manifest.json`), and the conditions it refuses to pack under: a failing
81
96
  `check`, a missing lockfile, or a local `file:`/`link:` dependency.
97
+ - [Upgrading](upgrading.md): taking a new kit version. The kit is pre-1.0, so a
98
+ patch (0.4.0 → 0.4.1) carries features AND fixes and `npm update` takes it, while
99
+ a minor (0.4.x → 0.5.0) is breaking and needs an explicit
100
+ `npm install @homepages/template-kit@^0.5.0` — a 0.x caret never crosses a minor.
101
+ Submissions are accepted against the current kit line and the one before it.
82
102
  - [Adding templates](new.md): the `template-kit new <template>` and
83
103
  `template-kit new-section <template>/<section>` CLI commands — scaffolding a new
84
104
  template with a working starter section, or adding a bare section (the four
85
105
  reserved contract files, no island) to an existing template. Both refuse to
86
106
  overwrite an existing folder.
87
107
  - [Rules](rules/INDEX.md): every rule-id a lint or `check` failure can print, under the
88
- `template-kit/<id>` namespace, one page each at `docs/rules/<id>.md` — Rule / Reason /
89
- Fix / Before→After. **The loop:** see an id in a failure → read `docs/rules/<id>.md` →
108
+ `template-kit/<id>` namespace, one page each at `guide/rules/<id>.md` — Rule / Reason /
109
+ Fix / Before→After. **The loop:** see an id in a failure → read `guide/rules/<id>.md` →
90
110
  fix. Nothing else to search for; the id is the filename.
91
111
  - [Recipes](recipes/INDEX.md): task recipes routed by "how do I…" — each one a minimal,
92
112
  complete delta from the starter's `hero` section (never a from-scratch file), ending in