@homepages/template-kit 0.8.1-dev-20260718224903 → 0.9.0-dev-20260719224319

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 (112) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/README.md +2 -2
  3. package/dist/canvas-core/chrome-css.d.ts +24 -0
  4. package/dist/canvas-core/chrome-css.js +403 -0
  5. package/dist/canvas-core/chrome.d.ts +49 -0
  6. package/dist/canvas-core/chrome.js +147 -0
  7. package/dist/canvas-core/frame.d.ts +20 -0
  8. package/dist/canvas-core/frame.js +23 -0
  9. package/dist/canvas-core/hidden-tabs.d.ts +16 -0
  10. package/dist/canvas-core/hidden-tabs.js +84 -0
  11. package/dist/canvas-core/resolve-target.js +55 -0
  12. package/dist/canvas-core/scroll.d.ts +10 -0
  13. package/dist/canvas-core/scroll.js +15 -0
  14. package/dist/canvas-core/section-dom.d.ts +6 -0
  15. package/dist/canvas-core/section-dom.js +43 -0
  16. package/dist/canvas-core/srcdoc.d.ts +8 -0
  17. package/dist/canvas-core/srcdoc.js +77 -0
  18. package/dist/canvas-core/types.d.ts +76 -0
  19. package/dist/canvas-core.d.ts +9 -0
  20. package/dist/canvas-core.js +9 -0
  21. package/dist/cli/check/css.js +2 -2
  22. package/dist/cli/check/loader.js +1 -1
  23. package/dist/cli/check/stages/deps.js +1 -1
  24. package/dist/cli/check/stages/render.js +3 -2
  25. package/dist/cli/check/stages/size.js +2 -2
  26. package/dist/cli/check/stages/tree.js +1 -1
  27. package/dist/cli/check/stages/validate/index.js +1 -1
  28. package/dist/cli/check/stages/validate/nav-contract.js +4 -3
  29. package/dist/cli/check/stages/validate/orchestrator.js +1 -1
  30. package/dist/cli/check/workspace.js +2 -2
  31. package/dist/cli/dev/build-css.js +1 -1
  32. package/dist/cli/dev/discover.js +2 -2
  33. package/dist/cli/dev/inspect.js +1 -1
  34. package/dist/cli/dev/island-bootstrap.js +1 -1
  35. package/dist/cli/dev/island-canvas-bootstrap.js +37 -5
  36. package/dist/cli/dev/section-page.js +1 -1
  37. package/dist/cli/dev/server.js +12 -5
  38. package/dist/cli/dev/tailwind.js +1 -1
  39. package/dist/cli/dev/tsconfig-paths-plugin.js +1 -1
  40. package/dist/cli/dev/workspace.js +2 -2
  41. package/dist/cli/link/index.js +1 -1
  42. package/dist/cli/link/overlay.js +1 -1
  43. package/dist/cli/new/emit.js +1 -1
  44. package/dist/cli/new/index.js +1 -1
  45. package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +4 -4
  46. package/dist/cli/new/scaffold-assets.js +2 -2
  47. package/dist/cli/pack/collect.js +1 -1
  48. package/dist/cli/pack/guards.js +1 -1
  49. package/dist/cli/pack/index.js +1 -1
  50. package/dist/cli/theme/generate.js +1 -1
  51. package/dist/cli/theme/load-theme.js +1 -1
  52. package/dist/dev-client/assets/index-CJa9G1Na.css +1 -0
  53. package/dist/dev-client/assets/index-C_1atZs4.js +450 -0
  54. package/dist/dev-client/index.html +2 -2
  55. package/dist/fixtures/sample-property.d.ts +4 -7
  56. package/dist/fixtures/sample-property.js +41 -97
  57. package/dist/fixtures/scenario.d.ts +1 -1
  58. package/dist/fixtures/scenarios/for-rent-condo.js +24 -44
  59. package/dist/fixtures/scenarios/luxury-multi-unit.js +1 -1
  60. package/dist/fixtures/scenarios/sparse-single-family.js +14 -26
  61. package/dist/fixtures.d.ts +2 -2
  62. package/dist/fixtures.js +1 -2
  63. package/dist/islands/client-directive.d.ts +7 -0
  64. package/dist/{eslint/is-client-file.js → islands/client-directive.js} +6 -3
  65. package/dist/islands/detect.d.ts +1 -1
  66. package/dist/islands/detect.js +1 -1
  67. package/dist/islands/discover.js +1 -1
  68. package/dist/islands/esbuild-plugin.js +1 -1
  69. package/dist/islands/wrap.js +1 -1
  70. package/dist/manifest.json +940 -0
  71. package/dist/media/pack.d.ts +49 -0
  72. package/dist/media/pack.js +69 -0
  73. package/dist/media/resolve-fixture-media.js +73 -0
  74. package/dist/media/resolve-media.js +195 -0
  75. package/dist/package.js +1 -1
  76. package/dist/rules.d.ts +176 -0
  77. package/dist/rules.js +209 -0
  78. package/dist/schema/property-facts.d.ts +2 -2
  79. package/dist/ssr.d.ts +14 -2
  80. package/dist/ssr.js +28 -13
  81. package/dist/tokens.css +21 -0
  82. package/guide/INDEX.md +2 -2
  83. package/guide/check.md +5 -5
  84. package/guide/dev.md +39 -30
  85. package/guide/eslint.md +13 -10
  86. package/guide/llms.txt +4 -3
  87. package/guide/quickstart.md +1 -1
  88. package/guide/recipes/image-slot-crop.md +6 -6
  89. package/guide/recipes/pick-a-scenario.md +13 -5
  90. package/guide/rules/INDEX.md +3 -2
  91. package/guide/schema-system.md +36 -12
  92. package/guide/upgrading.md +18 -2
  93. package/package.json +8 -4
  94. package/tsconfig.json +1 -1
  95. package/dist/dev-client/assets/index-3Gn34X_P.js +0 -50
  96. package/dist/dev-client/assets/index-DxQeiHfu.css +0 -1
  97. package/dist/eslint/is-client-file.d.ts +0 -4
  98. package/dist/eslint/rules/image-bare-needs-reason.js +0 -43
  99. package/dist/eslint/rules/no-client-directive-in-contract.js +0 -31
  100. package/dist/eslint/rules/no-client-runtime-in-server.js +0 -103
  101. package/dist/eslint/rules/no-css-import-from-render-path.js +0 -29
  102. package/dist/eslint/rules/no-hex.js +0 -188
  103. package/dist/eslint/rules/no-inline-style.js +0 -27
  104. package/dist/eslint/rules/no-nondeterminism.js +0 -48
  105. package/dist/eslint/rules/no-raw-element.js +0 -38
  106. package/dist/eslint/rules/props-from-schema.js +0 -37
  107. package/dist/eslint/rules/require-island-editor.js +0 -74
  108. package/dist/eslint/rules/serializable-island-props.js +0 -112
  109. package/dist/eslint/rules/slot-marker-literal.js +0 -38
  110. package/dist/eslint.d.ts +0 -6
  111. package/dist/eslint.js +0 -80
  112. package/dist/rules/registry.js +0 -16
@@ -2,7 +2,7 @@
2
2
  purpose: Source a fixture's slots from real property data, by the fill-by-source rule.
3
3
  status: living
4
4
  related: [../schema-system.md, fixture-states.md, ../vocabulary.md]
5
- updated: 2026-07-16
5
+ updated: 2026-07-19
6
6
  ---
7
7
  # Pick a scenario for a fixture
8
8
 
@@ -59,7 +59,6 @@ nothing about the layout it's supposed to preview.
59
59
  ```ts
60
60
  import {
61
61
  contactCards,
62
- mkResponsive,
63
62
  poisByCategory,
64
63
  primaryContact,
65
64
  propertyMetrics,
@@ -75,7 +74,6 @@ const s = scenarios.luxuryMultiUnit;
75
74
  | `contactCards(s)` | Every attached contact, in order (`ContactFixture[]`) |
76
75
  | `poisByCategory(s)` | The scenario's POIs bucketed by category (`Record<PoiFixture["category"], PoiFixture[]>`) |
77
76
  | `propertyMetrics(s)` | `beds`/`baths`/`sqft`/`price` off the lowest-position unit, `bedsMin`/`bedsMax`/`bathsMin`/`bathsMax`/`sqftMax`/`priceStart` across all units, and `addressLine1`/`addressLine2` |
78
- | `mkResponsive(url, width, height)` | A deterministic `ResponsiveImage` descriptor so `<Image responsive>` can be previewed and snapshotted |
79
77
 
80
78
  `propertyMetrics` returns kit fact vocabulary — `addressLine1`/`addressLine2`, not a
81
79
  template's own slot names, so the kit never learns what a section calls its slots. Map
@@ -90,10 +88,20 @@ address_locality: m.addressLine2,
90
88
 
91
89
  Fields the helpers don't cover are readable straight off the scenario: `s.photos`,
92
90
  `s.pois`, `s.amenities`, `s.contacts`, `s.media`, and `s.facts` (the raw
93
- `PropertyFacts`). A contact's org name and logos (`logo_light`/`logo_dark`) live on
94
- `ContactFixture["fields"]`, not on a sibling "brand" object — read them off
91
+ `PropertyFacts`). A contact's org name and logos (`logo_light_id`/`logo_dark_id`) live
92
+ on `ContactFixture["fields"]`, not on a sibling "brand" object — read them off
95
93
  `primaryContact(s).fields` or `s.contacts`.
96
94
 
95
+ ### Media comes off the scenario as an id
96
+
97
+ Every media reference in a scenario is an **asset id**, the same thing a saved page
98
+ holds — `s.photos[n].id`, `primaryContact(s).fields.headshot_id` / `.logo_light_id` /
99
+ `.logo_dark_id`, `s.media.video_id`, and a floor plan ref's `id`. Put the id in the
100
+ fixture and stop there: the url, the `alt` fallback, and the responsive ladder are
101
+ attached for you when the fixture loads. Never state a url in a fixture.
102
+ (`s.media.tour_3d_url` is the one exception — a 3D tour is a third-party embed with no
103
+ asset behind it.)
104
+
97
105
  ### Add a scenario-driven state
98
106
 
99
107
  A `states` entry designed to look different against a sparser property sources from a
@@ -2,7 +2,7 @@
2
2
  purpose: Router for the rule corpus — every template-kit/<id> an author can hit, and the page that explains it.
3
3
  status: living
4
4
  related: [server-vs-client.md, ../eslint.md, ../check.md, ../INDEX.md]
5
- updated: 2026-07-17
5
+ updated: 2026-07-19
6
6
  ---
7
7
  # Rules
8
8
 
@@ -10,7 +10,8 @@ Every authoring rule the kit enforces is printed under one namespace, `template-
10
10
  and resolves to exactly one page here: `guide/rules/<id>.md`.
11
11
 
12
12
  **One namespace, two venues.** Some rules are properties of **one file** (a `Date.now()`
13
- in a Renderer) and are enforced by the ESLint preset. Others are properties of the
13
+ in a Renderer) and are enforced by the [ESLint preset](../eslint.md), which ships as its
14
+ own package, `@homepages/eslint-plugin-template`. Others are properties of the
14
15
  **tree** (a slot declared in `schema.ts` but never marked in the JSX, a stylesheet, a
15
16
  dependency, a byte budget) and cannot be expressed one file at a time; those are enforced
16
17
  by [`template-kit check`](../check.md). You never need to know which engine produced a
@@ -2,7 +2,7 @@
2
2
  purpose: The section authoring contract — schema.ts, fill-spec.ts, fixtures.ts, and the closed source/transform vocabulary.
3
3
  status: living
4
4
  related: [primitives.md, theme-and-css.md, vocabulary.md, recipes/pick-a-scenario.md]
5
- updated: 2026-07-17
5
+ updated: 2026-07-19
6
6
  ---
7
7
  # The schema system
8
8
 
@@ -229,12 +229,16 @@ headline, an empty list).
229
229
 
230
230
  ```ts
231
231
  import type { FixtureModule } from "@homepages/template-kit";
232
+ import { scenarios } from "@homepages/template-kit/fixtures";
232
233
 
233
234
  const fixtures: FixtureModule = {
234
235
  base: {
235
236
  slots: {
236
237
  headline: "101 Queensway, Jamaica Plain, MA 02130",
237
- hero_image: { photo_id: "p1", url: "https://example.com/a.jpg", alt: "Front elevation" },
238
+ // An image slot states the asset id a real page holds — never a URL. The
239
+ // preview server resolves it to a url, an alt fallback, and a responsive
240
+ // ladder, exactly as the platform does when the page is published.
241
+ hero_image: { photo_id: scenarios.luxuryMultiUnit.photos[19]!.id, alt: "Front elevation" },
238
242
  blurb: "A bright corner home moments from the Emerald Necklace.",
239
243
  },
240
244
  options: { align: "center" },
@@ -255,7 +259,7 @@ contacts, the cases that actually break a layout. Pick the one whose breadth mat
255
259
  state you're exercising:
256
260
 
257
261
  ```ts
258
- import { scenarios, primaryContact, poisByCategory, propertyMetrics, contactCards, mkResponsive }
262
+ import { scenarios, primaryContact, poisByCategory, propertyMetrics, contactCards }
259
263
  from "@homepages/template-kit/fixtures";
260
264
 
261
265
  const s = scenarios.luxuryMultiUnit; // or sparseSingleFamily / forRentCondo
@@ -298,7 +302,6 @@ wants, without re-typing the scenario's data:
298
302
  | `contactCards(s)` | Every attached contact, in order (`ContactFixture[]`) |
299
303
  | `poisByCategory(s)` | The scenario's POIs bucketed by category (`Record<PoiFixture["category"], PoiFixture[]>`) |
300
304
  | `propertyMetrics(s)` | Beds/baths/sqft/price off the lowest-position unit, `*Min`/`*Max`/`priceStart` across all units, and `addressLine1`/`addressLine2` |
301
- | `mkResponsive(url, width, height)` | Synthesizes a deterministic `ResponsiveImage` descriptor so `<Image responsive>` can be previewed and snapshotted |
302
305
 
303
306
  `propertyMetrics` returns kit fact vocabulary — `addressLine1`/`addressLine2`, never a
304
307
  template's own slot names, so the kit never learns what a section calls its slots. Map it
@@ -307,8 +310,29 @@ to your slots at the call site: `address_street: m.addressLine1`.
307
310
  `s.photos`, `s.pois`, `s.amenities`, `s.contacts`, `s.media`, and `s.facts`
308
311
  (the raw `PropertyFacts`) are also readable straight off the scenario for slots that don't
309
312
  need a helper's projection — a gallery slot can bind directly to `s.photos`. A contact's
310
- org name and logos (`logo_light`/`logo_dark`) live on `ContactFixture["fields"]`, not on
311
- a sibling "brand" object — read them off `primaryContact(s).fields` or `s.contacts`.
313
+ org name and logos (`logo_light_id`/`logo_dark_id`) live on `ContactFixture["fields"]`,
314
+ not on a sibling "brand" object — read them off `primaryContact(s).fields` or
315
+ `s.contacts`.
316
+
317
+ ### Media is stated by id, never by URL
318
+
319
+ A fixture's job is to state what a **saved page** holds, and a saved page holds an
320
+ asset id — the platform turns that id into a served URL at render time. So a fixture
321
+ does the same: give an image slot a `photo_id` (a video slot a `video_id`), and the
322
+ url, the `alt` fallback, and the responsive ladder are attached for you when the
323
+ fixture loads, in the preview server and in `check` alike. A fixture that states a
324
+ URL is exercising a path real content never takes.
325
+
326
+ The ids to use are the scenario's own: `s.photos[n].id` for a photo,
327
+ `primaryContact(s).fields.headshot_id` / `.logo_light_id` / `.logo_dark_id` for a
328
+ contact's images, `s.media.video_id` for the hosted video. `s.media.tour_3d_url`
329
+ stays a URL on purpose — a 3D tour is a third-party embed the platform does not host,
330
+ so there is no asset to name. Floor plan refs
331
+ (`s.facts.units[*].floor_plans`) likewise carry only `id`/`floor_label`/`position`;
332
+ their `url`, `alt`, and `responsive` are optional and appear only after resolution.
333
+
334
+ An id that names nothing resolves to an empty image, which previews as an empty slot
335
+ rather than an error — so take the ids off the scenario rather than typing them.
312
336
 
313
337
  See [Pick a scenario](recipes/pick-a-scenario.md) for the full picker and helper
314
338
  cheat-sheet.
@@ -318,14 +342,14 @@ cheat-sheet.
318
342
  A scenario is the **fixture view** of a property, not the fact bundle the AI-fill pipeline
319
343
  reads. The difference matters when you write a `fill-spec.ts`:
320
344
 
321
- - Scenarios carry values that only exist *after* fill and render — `UnitRow.description`
322
- and `features` are AI-written copy; a floor plan's `url` and `alt` are attached when the
323
- image is served. Nothing upstream produces these; they are here so your renderer has
324
- something real to draw.
345
+ - Scenarios carry values that only exist *after* fill — `UnitRow.description` and
346
+ `features` are AI-written copy. Nothing upstream produces these; they are here so your
347
+ renderer has something real to draw.
325
348
  - Scenarios hoist some facts **out** of `facts` and onto the scenario itself, in the shapes
326
349
  a renderer wants: `photos` → `s.photos` (`PhotoFixture[]`), `pois` → `s.pois`
327
- (`PoiFixture[]`), and the fact bundle's `tours` → `s.media` — a single `MediaFixture`
328
- **object** (`video_url`, `tour_3d_url`), not an array. Floor plans hoist too, but onto
350
+ (`PoiFixture[]`), and the fact bundle's hosted videos and `tours` → `s.media` — a
351
+ single `MediaFixture` **object**, not an array: `video_id` names one hosted video
352
+ asset, `tour_3d_url` the third-party embed link. Floor plans hoist too, but onto
329
353
  each unit: `s.facts.units[*].floor_plans` (`FloorplanAssignment[]`).
330
354
  - `contacts` is **duplicated, not hoisted**. `s.contacts` holds the render-shaped
331
355
  `ContactFixture[]` your renderer draws, and `facts.contacts` still exists as well — so a
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  purpose: How to move a workspace to a new template-kit version, and what each kind of release means for you.
3
3
  status: living
4
- related: [check.md, pack.md, overview.md]
5
- updated: 2026-07-16
4
+ related: [check.md, pack.md, overview.md, eslint.md]
5
+ updated: 2026-07-19
6
6
  ---
7
7
  # Upgrading the kit
8
8
 
@@ -43,6 +43,20 @@ npm run check
43
43
  CHANGELOG in the package (`node_modules/@homepages/template-kit/CHANGELOG.md`) for
44
44
  what changed and why.
45
45
 
46
+ ## The lint preset upgrades separately
47
+
48
+ `@homepages/eslint-plugin-template` is its own package with its own version line,
49
+ so the kit's number tells you nothing about it and `npm update
50
+ @homepages/template-kit` never moves it. Update it the same way, on its own:
51
+
52
+ ```bash
53
+ npm update @homepages/eslint-plugin-template
54
+ ```
55
+
56
+ It declares the kit as a **peer dependency**, so its range says which kit line it
57
+ supports. If `npm install` reports a peer conflict after a kit minor, the preset is
58
+ the package to bump — not the kit to hold back.
59
+
46
60
  ## How long you have
47
61
 
48
62
  Submissions are accepted against the current kit line **and the one before it**.
@@ -59,3 +73,5 @@ message.
59
73
  npm ls @homepages/template-kit # what you have installed
60
74
  npm view @homepages/template-kit version # the current release
61
75
  ```
76
+
77
+ The same two commands work for `@homepages/eslint-plugin-template`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homepages/template-kit",
3
- "version": "0.8.1-dev-20260718224903",
3
+ "version": "0.9.0-dev-20260719224319",
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",
@@ -46,9 +46,13 @@
46
46
  "types": "./dist/ssr.d.ts",
47
47
  "default": "./dist/ssr.js"
48
48
  },
49
- "./eslint": {
50
- "types": "./dist/eslint.d.ts",
51
- "default": "./dist/eslint.js"
49
+ "./rules": {
50
+ "types": "./dist/rules.d.ts",
51
+ "default": "./dist/rules.js"
52
+ },
53
+ "./canvas-core": {
54
+ "types": "./dist/canvas-core.d.ts",
55
+ "default": "./dist/canvas-core.js"
52
56
  },
53
57
  "./styles.css": "./dist/styles.css",
54
58
  "./base.css": "./dist/base.css",
package/tsconfig.json CHANGED
@@ -3,5 +3,5 @@
3
3
  "compilerOptions": {
4
4
  "noEmit": true
5
5
  },
6
- "exclude": ["node_modules", "dist", ".tmp-consumer", "test/fixtures", "create-homepages-workspace"]
6
+ "exclude": ["node_modules", "dist", ".tmp-consumer", "test/fixtures", "create-workspace"]
7
7
  }