@homepages/template-kit 0.9.0-dev-20260719205406 → 0.9.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 (146) hide show
  1. package/CHANGELOG.md +60 -59
  2. package/README.md +14 -4
  3. package/dist/contracts/fill-treatments.d.ts +23 -0
  4. package/dist/fixtures/sample-property.d.ts +4 -7
  5. package/dist/fixtures/sample-property.js +41 -97
  6. package/dist/fixtures/scenario.d.ts +1 -1
  7. package/dist/fixtures/scenarios/for-rent-condo.js +24 -44
  8. package/dist/fixtures/scenarios/luxury-multi-unit.js +1 -1
  9. package/dist/fixtures/scenarios/sparse-single-family.js +14 -26
  10. package/dist/fixtures.d.ts +2 -2
  11. package/dist/fixtures.js +1 -2
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +2 -2
  14. package/dist/islands/discover.js +1 -1
  15. package/dist/islands/esbuild-plugin.js +1 -1
  16. package/dist/islands/wrap.d.ts +9 -1
  17. package/dist/manifest.json +940 -0
  18. package/dist/media/pack.d.ts +49 -0
  19. package/dist/media/pack.js +69 -0
  20. package/dist/media/resolve-fixture-media.d.ts +22 -0
  21. package/dist/media/resolve-fixture-media.js +73 -0
  22. package/dist/media/resolve-media.js +195 -0
  23. package/dist/package.js +1 -1
  24. package/dist/schema/property-facts.d.ts +2 -2
  25. package/dist/schema/resolve-section-ref.d.ts +4 -0
  26. package/dist/schema/rows.d.ts +9 -0
  27. package/dist/schema/section-nav.d.ts +5 -1
  28. package/dist/schema/slot-types.d.ts +1 -1
  29. package/dist/ssr.d.ts +19 -2
  30. package/dist/ssr.js +32 -13
  31. package/guide/INDEX.md +5 -0
  32. package/guide/check.md +1 -0
  33. package/guide/dev.md +1 -0
  34. package/guide/llms.txt +11 -3
  35. package/guide/recipes/image-slot-crop.md +6 -6
  36. package/guide/recipes/pick-a-scenario.md +13 -5
  37. package/guide/schema-system.md +36 -12
  38. package/guide/upgrading.md +12 -8
  39. package/package.json +4 -16
  40. package/dist/canvas-core/chrome-css.d.ts +0 -24
  41. package/dist/canvas-core/chrome-css.js +0 -403
  42. package/dist/canvas-core/chrome.d.ts +0 -49
  43. package/dist/canvas-core/chrome.js +0 -147
  44. package/dist/canvas-core/frame.d.ts +0 -20
  45. package/dist/canvas-core/frame.js +0 -23
  46. package/dist/canvas-core/hidden-tabs.d.ts +0 -16
  47. package/dist/canvas-core/hidden-tabs.js +0 -84
  48. package/dist/canvas-core/resolve-target.js +0 -55
  49. package/dist/canvas-core/scroll.d.ts +0 -10
  50. package/dist/canvas-core/scroll.js +0 -15
  51. package/dist/canvas-core/section-dom.d.ts +0 -6
  52. package/dist/canvas-core/section-dom.js +0 -43
  53. package/dist/canvas-core/srcdoc.d.ts +0 -8
  54. package/dist/canvas-core/srcdoc.js +0 -77
  55. package/dist/canvas-core/types.d.ts +0 -76
  56. package/dist/canvas-core.d.ts +0 -9
  57. package/dist/canvas-core.js +0 -9
  58. package/dist/cli/check/config.js +0 -41
  59. package/dist/cli/check/css.js +0 -131
  60. package/dist/cli/check/diagnostics.js +0 -37
  61. package/dist/cli/check/index.js +0 -94
  62. package/dist/cli/check/loader.js +0 -149
  63. package/dist/cli/check/paths.js +0 -15
  64. package/dist/cli/check/relativize.js +0 -18
  65. package/dist/cli/check/render-invariants.js +0 -24
  66. package/dist/cli/check/resolve-tool.js +0 -38
  67. package/dist/cli/check/section-assets.js +0 -58
  68. package/dist/cli/check/stages/deps.js +0 -337
  69. package/dist/cli/check/stages/lint.js +0 -46
  70. package/dist/cli/check/stages/render.js +0 -97
  71. package/dist/cli/check/stages/size.js +0 -274
  72. package/dist/cli/check/stages/tree.js +0 -206
  73. package/dist/cli/check/stages/typecheck.js +0 -46
  74. package/dist/cli/check/stages/validate/catalog-exhaustiveness.js +0 -18
  75. package/dist/cli/check/stages/validate/facts.js +0 -19
  76. package/dist/cli/check/stages/validate/formatter-contract.js +0 -14
  77. package/dist/cli/check/stages/validate/group-contract.js +0 -21
  78. package/dist/cli/check/stages/validate/index.js +0 -66
  79. package/dist/cli/check/stages/validate/list-scalar-contract.js +0 -22
  80. package/dist/cli/check/stages/validate/nav-contract.js +0 -92
  81. package/dist/cli/check/stages/validate/nested-slot-contract.js +0 -12
  82. package/dist/cli/check/stages/validate/object-list-contract.js +0 -31
  83. package/dist/cli/check/stages/validate/orchestrator.js +0 -241
  84. package/dist/cli/check/stages/validate/produced-by-contract.js +0 -18
  85. package/dist/cli/check/stages/validate/row-contract.js +0 -31
  86. package/dist/cli/check/stages/validate/select-contract.js +0 -13
  87. package/dist/cli/check/stages/validate/source-contract.js +0 -21
  88. package/dist/cli/check/stages/validate/variant-contract.js +0 -15
  89. package/dist/cli/check/workspace.js +0 -99
  90. package/dist/cli/dev/build-css.js +0 -97
  91. package/dist/cli/dev/catalog.js +0 -35
  92. package/dist/cli/dev/compose-template.js +0 -39
  93. package/dist/cli/dev/content-override.js +0 -58
  94. package/dist/cli/dev/diagnostics.js +0 -68
  95. package/dist/cli/dev/discover.js +0 -35
  96. package/dist/cli/dev/fill-state.js +0 -77
  97. package/dist/cli/dev/index.js +0 -42
  98. package/dist/cli/dev/inspect.js +0 -38
  99. package/dist/cli/dev/island-bootstrap.js +0 -38
  100. package/dist/cli/dev/island-canvas-bootstrap.js +0 -204
  101. package/dist/cli/dev/island-map.js +0 -35
  102. package/dist/cli/dev/island-transform.js +0 -31
  103. package/dist/cli/dev/manifest-instances.js +0 -31
  104. package/dist/cli/dev/render-section.js +0 -24
  105. package/dist/cli/dev/screenshot-target.js +0 -24
  106. package/dist/cli/dev/section-page.js +0 -91
  107. package/dist/cli/dev/server.js +0 -510
  108. package/dist/cli/dev/slot-schema.js +0 -12
  109. package/dist/cli/dev/structure-summary.js +0 -119
  110. package/dist/cli/dev/tailwind.js +0 -32
  111. package/dist/cli/dev/tsconfig-paths-plugin.js +0 -90
  112. package/dist/cli/dev/vite-server.js +0 -39
  113. package/dist/cli/dev/workspace.js +0 -63
  114. package/dist/cli/link/index.js +0 -74
  115. package/dist/cli/link/overlay.js +0 -59
  116. package/dist/cli/link/watch.js +0 -25
  117. package/dist/cli/new/emit.js +0 -48
  118. package/dist/cli/new/index.js +0 -69
  119. package/dist/cli/new/scaffold/section/_Renderer.tsx +0 -20
  120. package/dist/cli/new/scaffold/section/_fill-spec.ts +0 -18
  121. package/dist/cli/new/scaffold/section/_fixtures.ts +0 -13
  122. package/dist/cli/new/scaffold/section/_schema.ts +0 -24
  123. package/dist/cli/new/scaffold/template/_manifest.json +0 -9
  124. package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +0 -45
  125. package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +0 -40
  126. package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +0 -14
  127. package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +0 -29
  128. package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +0 -41
  129. package/dist/cli/new/scaffold/template/sections/hero/schema.ts +0 -60
  130. package/dist/cli/new/scaffold/template/theme.css +0 -24
  131. package/dist/cli/new/scaffold/template/theme.ts +0 -27
  132. package/dist/cli/new/scaffold-assets.js +0 -20
  133. package/dist/cli/pack/collect.js +0 -44
  134. package/dist/cli/pack/guards.js +0 -57
  135. package/dist/cli/pack/index.js +0 -66
  136. package/dist/cli/pack/manifest.js +0 -15
  137. package/dist/cli/pack/zip.js +0 -86
  138. package/dist/cli/theme/generate.js +0 -43
  139. package/dist/cli/theme/index.js +0 -33
  140. package/dist/cli/theme/load-theme.js +0 -69
  141. package/dist/cli.d.ts +0 -1
  142. package/dist/cli.js +0 -138
  143. package/dist/dev-client/assets/index-CJa9G1Na.css +0 -1
  144. package/dist/dev-client/assets/index-C_1atZs4.js +0 -450
  145. package/dist/dev-client/index.html +0 -13
  146. package/dist/tokens.css +0 -21
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @homepages/template-kit
2
2
 
3
- ## 0.9.0-dev-20260719205406
3
+ ## 0.9.0
4
4
 
5
5
  ### Minor Changes
6
6
 
@@ -24,71 +24,72 @@
24
24
  `resolveImportedFile`, `resetClientFileCache`) that the plugin's rules consume
25
25
  over the peer edge.
26
26
 
27
+ - f868a43: **Breaking:** the CLI has moved to its own package, `@homepages/template-cli`.
28
+ Installing the kit no longer installs the `template-kit` command, and the
29
+ `./canvas-core` subpath is gone.
30
+
31
+ The command itself is unchanged — it is still `template-kit dev`, `template-kit
32
+ check`, and so on. Add the new package as a devDependency alongside your existing
33
+ kit dependency:
34
+
35
+ ```
36
+ npm i -D @homepages/template-cli
37
+ ```
38
+
39
+ The kit stays the package your templates import, under the same name and with the
40
+ same authoring API, so no `import` in a workspace changes. The CLI declares the kit
41
+ as a `peerDependency`, which is what guarantees a workspace resolves exactly one
42
+ copy of it.
43
+
44
+ Why: the CLI and its dev playground were 53% of the kit's published bytes while the
45
+ code that reaches a visitor's browser was under 1%, and they change on playground
46
+ cadence rather than contract cadence. Sharing one version number meant a
47
+ playground fix and an authoring-contract change were indistinguishable in the
48
+ number a template author reads.
49
+
50
+ `./ssr` additionally now exports `wrapClientModuleMagic`, `flattenLayout`,
51
+ `isShortSlot`, `resolveSectionRef`, `resolveAnchorMap` and `parseTreatment` — the
52
+ node-side schema and island helpers the CLI reaches across the peer edge. They are
53
+ deliberately absent from the authoring barrel: a renderer never imports `./ssr`.
54
+
55
+ - b7bfc96: Fixtures state media asset ids instead of URLs, and the kit resolves them.
56
+
57
+ A fixture now states what a saved page actually holds — `{ photo_id, alt }` — and
58
+ the kit resolves that id to a URL, an `alt`, and a responsive ladder when it loads
59
+ the fixture. Previously fixtures stated the post-resolution URL plus a synthesized
60
+ ladder, which exercised a code path real content never takes and left every preview
61
+ image broken.
62
+
63
+ Breaking changes to `@homepages/template-kit/fixtures`:
64
+
65
+ - `mkResponsive` is removed. Nothing needs to call it: the ladder is attached during
66
+ fixture load.
67
+ - `PhotoFixture.url` is removed; `PhotoFixture.id` now holds a media asset id.
68
+ - `ContactFixture.fields` replaces `headshot_url`, `logo_light`, and `logo_dark`
69
+ with `headshot_id`, `logo_light_id`, and `logo_dark_id`.
70
+ - `MediaFixture.video_url` becomes `video_id`. `tour_3d_url` is unchanged — a 3D
71
+ tour is a third-party embed, not a media asset.
72
+ - `FloorplanAssignment.url` and `.alt` are now optional: a fixture states the plan
73
+ ref, and url/alt/responsive appear only after resolution.
74
+
75
+ To migrate a fixture, drop `url` and `responsive` and keep the id:
76
+
77
+ ```diff
78
+ hero_image: {
79
+ photo_id: photo.id,
80
+ - url: photo.url,
81
+ alt: photo.alt,
82
+ - responsive: mkResponsive(photo.url, 2048, 1259),
83
+ },
84
+ ```
85
+
27
86
  ### Patch Changes
28
87
 
29
- - 10f53a7: `template-kit dev`'s canvas hover/select ring no longer overflows the page's
30
- x-axis when hovering a full-bleed section (e.g. a hero or header flush with
31
- both page edges). The ring's geometry inflated the target's bounding rect by
32
- a fixed offset on every side with no bound, so a rect already touching the
33
- document edge pushed the ring 2px past it, growing the iframe's horizontal
34
- scroll extent. The ring box is now clamped to `[0, containerWidth]`.
35
- - f3f6d70: `template-kit check` no longer fails on a tsconfig `paths` alias (e.g.
36
- `"@/platform/*": ["../platform/*"]`) or on a section whose contract files
37
- share a module large enough for esbuild to code-split. Previously the
38
- loader's css-stub plugin decided "external vs. bundle" from the specifier's
39
- own text, so a `paths`-resolved alias got re-emitted as a literal bare
40
- import Node couldn't resolve; separately, the loader's build applied
41
- `publicPath` to code-split `chunk-*.js` cross-imports the same way it does
42
- to real asset files, producing an import path Node couldn't resolve either.
43
- Both are now decided from the actual resolved file, and `publicPath` is no
44
- longer set on the loader's build.
45
- - 1c2c2db: `template-kit dev`'s preview server no longer 500s on `/runtime/base.css`.
46
- The route resolved the file at a path the build never emits to
47
- (`dist/css/base.css`; the build copies it flat to `dist/base.css`), so every
48
- section rendered unstyled — Tailwind-derived colors and layout were silently
49
- missing everywhere, only arbitrary-value element geometry survived. The
50
- route now checks the built (flat) path first and falls back to the source
51
- (`src/css/`) layout, so it resolves correctly both from an installed kit and
52
- when running the CLI unbuilt via `tsx` during kit development.
53
- - 989d23e: `template-kit dev` gains an optional `--diagnostics-cmd` flag: point it at any
54
- command that prints `{"violations": [...]}` JSON for a `--template`/`--section`
55
- pair, and the standalone section preview
56
- (`/sections/<template>/<section>/<fixture>`) shows a dismissible banner when it
57
- reports violations. Unset by default — no behavior change for a workspace that
58
- doesn't configure it.
59
- - e1d5792: `template-kit dev`'s preview server now compiles a template's theme-color
60
- Tailwind utilities (`bg-primary`, `text-ink`, `bg-background`, etc.) instead
61
- of silently dropping them. `assembleTemplateCss` used to concatenate the
62
- compiled theme (`compileThemeToCss`, the `@theme inline` alias layer plus
63
- its `:root` custom props) onto Tailwind's CLI output _after_ the CLI had
64
- already run, so the theme's color tokens were never part of the build graph
65
- Tailwind scanned — `@theme` outside that graph is inert, and silently so, so
66
- any utility built on a template color never got generated. No error, no
67
- warning: every section rendered with correct layout but default
68
- black-on-white text and backgrounds. The theme partial is now written to the
69
- dev cache and `@import`ed into the Tailwind entry ahead of the section
70
- `@source` scans, mirroring how the registry's own publish pipeline
71
- (`ci/build-css.ts`) has always assembled it. `fontFaces` is still prepended
72
- to the final bundle separately, since a nested `@import url(...)` doesn't
73
- hoist out of an imported partial.
74
- - 2e36277: Escape `>` and `&` as well as `<` in the dev server's island map, matching the escaping used by published pages.
75
88
  - 1c4c7dd: Island hydration is now nesting-safe: `hydrateIslands` skips any island marker
76
89
  nested inside another marker, so an island can render another island without the
77
90
  child being hydrated twice (once by its parent root, once independently). Enables
78
91
  interactive islands (accordion, card-slider, carousel) to wrap existing leaf
79
92
  islands (expandable-text) on published pages.
80
- - ff1d6de: `template-kit dev`'s canvas mirror now shows the Islands inspector panel for a
81
- section-level selection, not just a slot selection. A `fill-parent`/`fill-self`
82
- island with no owning slot of its own only ever gets its shield dropped by a
83
- section-level click, so the panel previously could show it as `shielded` but
84
- never `interactive`. Existing slot-selection behavior (edit panel + Islands
85
- list) is unchanged.
86
- - 41d0e88: `template-kit dev` now honors a workspace's tsconfig `paths` when resolving
87
- imports, the same way `check` already does via esbuild's native tsconfig
88
- lookup. Previously the dev server's Vite instance had no `paths` awareness at
89
- all, so a workspace mapping (e.g. `"@/lib/*": ["../lib/*"]`) that made `check`
90
- pass left `dev` 500ing with `Cannot find module`. A workspace with no `paths`
91
- mapping is unaffected.
92
93
 
93
94
  ## 0.8.0
94
95
 
package/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # @homepages/template-kit
2
2
 
3
3
  The authoring surface for HomePages marketing-section templates: schema system,
4
- contract primitives, the theme system, and the `template-kit` CLI.
4
+ contract primitives, and the theme system. The `template-kit` command lives in its
5
+ own package, [`@homepages/template-cli`](#install) — the kit is what your templates
6
+ `import`.
5
7
 
6
8
  **Status: 0.0.x.** The **schema system** (the section authoring contract and the closed
7
9
  vocabulary it validates against), the **contract primitives**, and the **theme system**
@@ -31,9 +33,16 @@ Prefer to wire a workspace by hand? See **Install** below.
31
33
 
32
34
  ```bash
33
35
  npm install @homepages/template-kit react react-dom
36
+ npm install -D @homepages/template-cli # the `template-kit` command
34
37
  npm install -D typescript eslint @typescript-eslint/parser esbuild # the authoring toolchain
35
38
  ```
36
39
 
40
+ The **command is `template-kit`; the package that provides it is
41
+ `@homepages/template-cli`** — a package/bin mismatch in the same spirit as
42
+ `typescript`/`tsc`. Everything you write is imported from `@homepages/template-kit`;
43
+ the CLI declares the kit as a peer dependency, so a workspace resolves exactly one
44
+ copy of it.
45
+
37
46
  React 19 is a **peer dependency** — the kit never bundles its own copy.
38
47
 
39
48
  The kit has exactly **one runtime dependency** (`zod`). The authoring toolchain the
@@ -97,6 +106,7 @@ Full contract: **[guide/primitives.md](guide/primitives.md)**.
97
106
  | `@homepages/template-kit/browser` | Browser helpers for published pages (site config, signed ingest) |
98
107
  | `@homepages/template-kit/styles.css` | The global Tailwind preamble: the breakpoint ladder, the spacing base unit, the motion defaults, kit source registration |
99
108
  | `@homepages/template-kit/base.css` | Cascade-layer order, the three reset gaps Tailwind's preflight leaves, `.tr-section`, and the motion tokens |
109
+ | `@homepages/template-cli` | The `template-kit` command (its own package) |
100
110
  | `@homepages/eslint-plugin-template` | ESLint flat-config preset (its own package) |
101
111
  | `@homepages/template-kit/tsconfig` | tsconfig preset (`"extends"`) |
102
112
 
@@ -158,7 +168,7 @@ section also has a standalone page, and so does the whole template at
158
168
  payload), and `/api/screenshot` (a full-page PNG, section or whole template). Like
159
169
  `check`, it runs **your** toolchain — Vite and Tailwind resolved from your workspace,
160
170
  installed with `npm i -D vite @vitejs/plugin-react tailwindcss @tailwindcss/cli` — not
161
- shipped by the kit. Full contract: **[guide/dev.md](guide/dev.md)**.
171
+ shipped by either package. Full contract: **[guide/dev.md](guide/dev.md)**.
162
172
 
163
173
  ## Checking a workspace
164
174
 
@@ -229,5 +239,5 @@ that vocabulary — your template names its own.
229
239
 
230
240
  Don't — changesets owns `package.json`'s version. There is nothing else to keep
231
241
  in step with it: `KIT_VERSION` is read from `package.json` and inlined at build
232
- time, and the test suite asserts `template-kit --version` against `package.json`
233
- to prove the inlining happened.
242
+ time, and the test suite asserts it against `package.json` to prove the inlining
243
+ happened.
@@ -0,0 +1,23 @@
1
+ //#region src/contracts/fill-treatments.d.ts
2
+ declare const GENERATED_KINDS: readonly ["text", "list<text>"];
3
+ type GeneratedKind = (typeof GENERATED_KINDS)[number];
4
+ declare const COMPUTED_FNS: readonly ["haversine_miles", "mint_id"];
5
+ type ComputedFn = (typeof COMPUTED_FNS)[number];
6
+ declare const CATALOG_FIELDS: readonly ["key", "label", "icon", "slot"];
7
+ type CatalogField = (typeof CATALOG_FIELDS)[number];
8
+ type Treatment = {
9
+ kind: "source";
10
+ path: string | null;
11
+ } | {
12
+ kind: "computed";
13
+ fn: ComputedFn;
14
+ } | {
15
+ kind: "catalog";
16
+ field: CatalogField;
17
+ } | {
18
+ kind: "generated";
19
+ outKind: GeneratedKind;
20
+ };
21
+ declare function parseTreatment(raw: string): Treatment | null;
22
+ //#endregion
23
+ export { CATALOG_FIELDS, COMPUTED_FNS, CatalogField, ComputedFn, GENERATED_KINDS, GeneratedKind, Treatment, parseTreatment };
@@ -1,15 +1,12 @@
1
- import { ResponsiveImage } from "../contracts/image-descriptor.js";
2
1
  //#region src/fixtures/sample-property.d.ts
3
2
  type PhotoFixture = {
4
3
  id: string;
5
- url: string;
6
4
  alt: string;
7
5
  tags: {
8
6
  room?: string;
9
7
  category?: string;
10
8
  };
11
9
  };
12
- declare function mkResponsive(url: string, width: number, height: number): ResponsiveImage;
13
10
  type PoiFixture = {
14
11
  id: string;
15
12
  category: "restaurant" | "park" | "transit" | "shopping" | "coffee" | "fitness";
@@ -31,14 +28,14 @@ type ContactFixture = {
31
28
  display_name: string;
32
29
  title: string;
33
30
  bio: string;
34
- headshot_url: string;
31
+ headshot_id: string;
35
32
  website_url: string;
36
33
  email: string;
37
34
  phone: string;
38
35
  address: string;
39
36
  org_name: string;
40
- logo_light: string | null;
41
- logo_dark: string | null;
37
+ logo_light_id: string | null;
38
+ logo_dark_id: string | null;
42
39
  socials: Array<{
43
40
  platform: "instagram" | "linkedin" | "x" | "facebook";
44
41
  url: string;
@@ -54,4 +51,4 @@ type AmenityItem = {
54
51
  items: string[];
55
52
  };
56
53
  //#endregion
57
- export { AmenityItem, ContactFixture, PhotoFixture, PoiFixture, mkResponsive };
54
+ export { AmenityItem, ContactFixture, PhotoFixture, PoiFixture };
@@ -1,9 +1,7 @@
1
1
  //#region src/fixtures/sample-property.ts
2
- const PHOTO_BASE = "https://example.invalid/photos/queensway";
3
2
  const galleryPhotos = [
4
3
  {
5
- id: "ph_001",
6
- url: `${PHOTO_BASE}/01-lobby.jpg`,
4
+ id: "dev_upl_queensway_01_lobby",
7
5
  alt: "Lobby with terrazzo flooring and chandeliers.",
8
6
  tags: {
9
7
  room: "lobby",
@@ -11,8 +9,7 @@ const galleryPhotos = [
11
9
  }
12
10
  },
13
11
  {
14
- id: "ph_002",
15
- url: `${PHOTO_BASE}/02-gym-a.jpg`,
12
+ id: "dev_upl_queensway_02_gym_a",
16
13
  alt: "Gym with cardio equipment and floor-to-ceiling mirrors.",
17
14
  tags: {
18
15
  room: "gym",
@@ -20,8 +17,7 @@ const galleryPhotos = [
20
17
  }
21
18
  },
22
19
  {
23
- id: "ph_003",
24
- url: `${PHOTO_BASE}/03-gym-b.jpg`,
20
+ id: "dev_upl_queensway_03_gym_b",
25
21
  alt: "Gym free-weights area.",
26
22
  tags: {
27
23
  room: "gym",
@@ -29,8 +25,7 @@ const galleryPhotos = [
29
25
  }
30
26
  },
31
27
  {
32
- id: "ph_004",
33
- url: `${PHOTO_BASE}/04-living-a.jpg`,
28
+ id: "dev_upl_queensway_04_living_a",
34
29
  alt: "Living area with bay windows and original mouldings.",
35
30
  tags: {
36
31
  room: "living",
@@ -38,8 +33,7 @@ const galleryPhotos = [
38
33
  }
39
34
  },
40
35
  {
41
- id: "ph_005",
42
- url: `${PHOTO_BASE}/05-living-b.jpg`,
36
+ id: "dev_upl_queensway_05_living_b",
43
37
  alt: "Living area facing the fireplace.",
44
38
  tags: {
45
39
  room: "living",
@@ -47,8 +41,7 @@ const galleryPhotos = [
47
41
  }
48
42
  },
49
43
  {
50
- id: "ph_006",
51
- url: `${PHOTO_BASE}/06-kitchen-a.jpg`,
44
+ id: "dev_upl_queensway_06_kitchen_a",
52
45
  alt: "Kitchen with quartz waterfall island.",
53
46
  tags: {
54
47
  room: "kitchen",
@@ -56,8 +49,7 @@ const galleryPhotos = [
56
49
  }
57
50
  },
58
51
  {
59
- id: "ph_007",
60
- url: `${PHOTO_BASE}/07-kitchen-b.jpg`,
52
+ id: "dev_upl_queensway_07_kitchen_b",
61
53
  alt: "Kitchen breakfast nook by the window.",
62
54
  tags: {
63
55
  room: "kitchen",
@@ -65,8 +57,7 @@ const galleryPhotos = [
65
57
  }
66
58
  },
67
59
  {
68
- id: "ph_008",
69
- url: `${PHOTO_BASE}/08-dining.jpg`,
60
+ id: "dev_upl_queensway_08_dining",
70
61
  alt: "Dining area set for six.",
71
62
  tags: {
72
63
  room: "dining",
@@ -74,8 +65,7 @@ const galleryPhotos = [
74
65
  }
75
66
  },
76
67
  {
77
- id: "ph_009",
78
- url: `${PHOTO_BASE}/09-living-c.jpg`,
68
+ id: "dev_upl_queensway_09_living_c",
79
69
  alt: "Living area with statement art wall.",
80
70
  tags: {
81
71
  room: "living",
@@ -83,8 +73,7 @@ const galleryPhotos = [
83
73
  }
84
74
  },
85
75
  {
86
- id: "ph_010",
87
- url: `${PHOTO_BASE}/10-living-d.jpg`,
76
+ id: "dev_upl_queensway_10_living_d",
88
77
  alt: "Living area opening onto private terrace.",
89
78
  tags: {
90
79
  room: "living",
@@ -92,8 +81,7 @@ const galleryPhotos = [
92
81
  }
93
82
  },
94
83
  {
95
- id: "ph_011",
96
- url: `${PHOTO_BASE}/11-living-e.jpg`,
84
+ id: "dev_upl_queensway_11_living_e",
97
85
  alt: "Sunken living area with built-in shelving.",
98
86
  tags: {
99
87
  room: "living",
@@ -101,8 +89,7 @@ const galleryPhotos = [
101
89
  }
102
90
  },
103
91
  {
104
- id: "ph_012",
105
- url: `${PHOTO_BASE}/12-living-f.jpg`,
92
+ id: "dev_upl_queensway_12_living_f",
106
93
  alt: "Living area corner with reading chair.",
107
94
  tags: {
108
95
  room: "living",
@@ -110,8 +97,7 @@ const galleryPhotos = [
110
97
  }
111
98
  },
112
99
  {
113
- id: "ph_013",
114
- url: `${PHOTO_BASE}/13-master-bedroom.jpg`,
100
+ id: "dev_upl_queensway_13_master_bedroom",
115
101
  alt: "Primary bedroom with king bed and skylight.",
116
102
  tags: {
117
103
  room: "bedroom",
@@ -119,8 +105,7 @@ const galleryPhotos = [
119
105
  }
120
106
  },
121
107
  {
122
- id: "ph_014",
123
- url: `${PHOTO_BASE}/14-master-bath-a.jpg`,
108
+ id: "dev_upl_queensway_14_master_bath_a",
124
109
  alt: "Spa-style primary bath with freestanding tub.",
125
110
  tags: {
126
111
  room: "bath",
@@ -128,8 +113,7 @@ const galleryPhotos = [
128
113
  }
129
114
  },
130
115
  {
131
- id: "ph_015",
132
- url: `${PHOTO_BASE}/15-master-bath-b.jpg`,
116
+ id: "dev_upl_queensway_15_master_bath_b",
133
117
  alt: "Primary bath double vanity and shower.",
134
118
  tags: {
135
119
  room: "bath",
@@ -137,8 +121,7 @@ const galleryPhotos = [
137
121
  }
138
122
  },
139
123
  {
140
- id: "ph_016",
141
- url: `${PHOTO_BASE}/16-common-bath.jpg`,
124
+ id: "dev_upl_queensway_16_common_bath",
142
125
  alt: "Common bath with marble subway tile.",
143
126
  tags: {
144
127
  room: "bath",
@@ -146,8 +129,7 @@ const galleryPhotos = [
146
129
  }
147
130
  },
148
131
  {
149
- id: "ph_017",
150
- url: `${PHOTO_BASE}/17-entrance.jpg`,
132
+ id: "dev_upl_queensway_17_entrance",
151
133
  alt: "Entrance foyer with original mosaic floor.",
152
134
  tags: {
153
135
  room: "entry",
@@ -155,8 +137,7 @@ const galleryPhotos = [
155
137
  }
156
138
  },
157
139
  {
158
- id: "ph_018",
159
- url: `${PHOTO_BASE}/18-patio.jpg`,
140
+ id: "dev_upl_queensway_18_patio",
160
141
  alt: "Patio with built-in seating and planters.",
161
142
  tags: {
162
143
  room: "patio",
@@ -164,8 +145,7 @@ const galleryPhotos = [
164
145
  }
165
146
  },
166
147
  {
167
- id: "ph_019",
168
- url: `${PHOTO_BASE}/19-outdoor-deck.jpg`,
148
+ id: "dev_upl_queensway_19_outdoor_deck",
169
149
  alt: "Outdoor deck with sunset view.",
170
150
  tags: {
171
151
  room: "deck",
@@ -173,8 +153,7 @@ const galleryPhotos = [
173
153
  }
174
154
  },
175
155
  {
176
- id: "ph_020",
177
- url: `${PHOTO_BASE}/20-exterior-hero.jpg`,
156
+ id: "dev_upl_queensway_20_exterior_hero",
178
157
  alt: "Mansard victorian facade at golden hour.",
179
158
  tags: {
180
159
  room: "exterior-front",
@@ -183,56 +162,21 @@ const galleryPhotos = [
183
162
  }
184
163
  ];
185
164
  const allPhotos = galleryPhotos;
186
- const RUNG_WIDTHS = [
187
- 320,
188
- 640,
189
- 960,
190
- 1200,
191
- 1600,
192
- 2048
193
- ];
194
- function mkResponsive(url, width, height) {
195
- const widths = RUNG_WIDTHS.filter((w) => w <= width);
196
- if (widths.length === 0) widths.push(width);
197
- const ladder = (ext) => widths.map((w) => `${url}.${w}.${ext} ${w}w`).join(", ");
198
- return {
199
- sources: [
200
- {
201
- type: "image/avif",
202
- srcset: ladder("avif")
203
- },
204
- {
205
- type: "image/webp",
206
- srcset: ladder("webp")
207
- },
208
- {
209
- type: "image/jpeg",
210
- srcset: ladder("jpg")
211
- }
212
- ],
213
- width,
214
- height
215
- };
216
- }
217
- const FP_BASE = "https://example.invalid/floorplans/queensway";
218
- function fp(id, url, alt, floorLabel, position) {
165
+ function fp(id, floorLabel, position) {
219
166
  return {
220
167
  id,
221
- url,
222
- alt,
223
168
  floor_label: floorLabel,
224
- position,
225
- responsive: mkResponsive(url, 1024, 1024)
169
+ position
226
170
  };
227
171
  }
228
- const FLOORPLAN_3BR = (suffix) => [fp(`fp_3br_${suffix}_a`, `${FP_BASE}/3br-${suffix}-l1.png`, "3BR layout — main floor.", "Main Floor", 0), fp(`fp_3br_${suffix}_b`, `${FP_BASE}/3br-${suffix}-l2.png`, "3BR layout — upper floor.", "Upper Floor", 1)];
229
- const FLOORPLAN_2BR = (suffix) => [fp(`fp_2br_${suffix}_a`, `${FP_BASE}/2br-${suffix}-l1.png`, "2BR layout.", null, 0)];
172
+ const FLOORPLAN_3BR = (suffix) => [fp(`dev_efp_queensway_3br_${suffix}_l1`, "Main Floor", 0), fp(`dev_efp_queensway_3br_${suffix}_l2`, "Upper Floor", 1)];
173
+ const FLOORPLAN_2BR = (suffix) => [fp(`dev_efp_queensway_2br_${suffix}_l1`, null, 0)];
230
174
  const FLOORPLAN_4BR = (suffix) => [
231
- fp(`fp_4br_${suffix}_a`, `${FP_BASE}/4br-${suffix}-l1.png`, "4BR layout — main floor.", "Main Floor", 0),
232
- fp(`fp_4br_${suffix}_b`, `${FP_BASE}/4br-${suffix}-l2.png`, "4BR layout — upper floor.", "Upper Floor", 1),
233
- fp(`fp_4br_${suffix}_c`, `${FP_BASE}/4br-${suffix}-l3.png`, "4BR layout — penthouse.", "Penthouse", 2)
175
+ fp(`dev_efp_queensway_4br_${suffix}_l1`, "Main Floor", 0),
176
+ fp(`dev_efp_queensway_4br_${suffix}_l2`, "Upper Floor", 1),
177
+ fp(`dev_efp_queensway_4br_${suffix}_l3`, "Penthouse", 2)
234
178
  ];
235
- const FLOORPLAN_STUDIO = (suffix) => [fp(`fp_studio_${suffix}_a`, `${FP_BASE}/studio-${suffix}-l1.png`, "Studio layout.", null, 0)];
179
+ const FLOORPLAN_STUDIO = (suffix) => [fp(`dev_efp_queensway_studio_${suffix}_l1`, null, 0)];
236
180
  function unit(position, unitLabel, beds, baths, sqft, price, notes, description, features, floorPlans) {
237
181
  return {
238
182
  unit_id: `unit_${String(position + 1).padStart(2, "0")}`,
@@ -645,14 +589,14 @@ const sampleContacts = [
645
589
  display_name: "James Donovan",
646
590
  title: "Listing Agent",
647
591
  bio: "James Donovan is a 15-year veteran of the Boston real estate market, specializing in historic multi-family conversions in Jamaica Plain and the South End. He has closed over $400M in residential transactions and is known for white-glove attention to multi-unit listings.",
648
- headshot_url: "https://example.invalid/contacts/james-donovan.jpg",
592
+ headshot_id: "dev_lib_james_donovan",
649
593
  website_url: "https://okane-marketing.example.invalid",
650
594
  email: "info@homepages.io",
651
595
  phone: "(617) 555-1068",
652
596
  address: "677 Centre St, Suite 200, Jamaica Plain, MA 02130",
653
597
  org_name: "Okane Marketing",
654
- logo_light: "https://example.invalid/brand/okane-logo-light.svg",
655
- logo_dark: "https://example.invalid/brand/okane-logo-dark.svg",
598
+ logo_light_id: "dev_lib_okane_logo_light",
599
+ logo_dark_id: "dev_lib_okane_logo_dark",
656
600
  socials: [
657
601
  {
658
602
  platform: "instagram",
@@ -682,14 +626,14 @@ const sampleContacts = [
682
626
  display_name: "Elena Marsh",
683
627
  title: "Buyer's Agent",
684
628
  bio: "Elena Marsh has guided over 120 buyer clients through the Boston multi-family market since joining the team in 2018, with a focus on Jamaica Plain, Roslindale, and West Roxbury first-time and investor purchases. She is a certified condo-conversion specialist and negotiates every offer personally.",
685
- headshot_url: "https://example.invalid/contacts/elena-marsh.jpg",
629
+ headshot_id: "dev_lib_elena_marsh",
686
630
  website_url: "https://okane-marketing.example.invalid",
687
631
  email: "elena@homepages.io",
688
632
  phone: "(617) 555-2214",
689
633
  address: "677 Centre St, Suite 200, Jamaica Plain, MA 02130",
690
634
  org_name: "Okane Marketing",
691
- logo_light: "https://example.invalid/brand/okane-logo-light.svg",
692
- logo_dark: "https://example.invalid/brand/okane-logo-dark.svg",
635
+ logo_light_id: "dev_lib_okane_logo_light",
636
+ logo_dark_id: "dev_lib_okane_logo_dark",
693
637
  socials: [{
694
638
  platform: "instagram",
695
639
  url: "https://instagram.com/elenamarsh.realty/"
@@ -708,14 +652,14 @@ const sampleContacts = [
708
652
  display_name: "Marcus Webb",
709
653
  title: "Director of Sales",
710
654
  bio: "Marcus Webb leads the team's dispositions practice, working with investors and owner-occupants on multi-unit acquisitions and conversions across greater Boston. A former general contractor, he brings a builder's eye to every walkthrough and has overseen more than 60 multi-family closings.",
711
- headshot_url: "https://example.invalid/contacts/marcus-webb.jpg",
655
+ headshot_id: "dev_lib_marcus_webb",
712
656
  website_url: "https://okane-marketing.example.invalid",
713
657
  email: "marcus@homepages.io",
714
658
  phone: "(617) 555-3357",
715
659
  address: "677 Centre St, Suite 200, Jamaica Plain, MA 02130",
716
660
  org_name: "Okane Marketing",
717
- logo_light: "https://example.invalid/brand/okane-logo-light.svg",
718
- logo_dark: "https://example.invalid/brand/okane-logo-dark.svg",
661
+ logo_light_id: "dev_lib_okane_logo_light",
662
+ logo_dark_id: "dev_lib_okane_logo_dark",
719
663
  socials: [{
720
664
  platform: "linkedin",
721
665
  url: "https://linkedin.com/in/marcus-webb-realty/"
@@ -824,10 +768,10 @@ const sampleProperty = {
824
768
  phone: c.fields.phone,
825
769
  address: c.fields.address,
826
770
  website_url: c.fields.website_url,
827
- avatar_upload_id: `upload_headshot_${c.library_item_id}`,
771
+ avatar_upload_id: c.fields.headshot_id,
828
772
  org_name: c.fields.org_name,
829
- logo_light_upload_id: c.fields.logo_light ? `upload_logo_light_${c.library_item_id}` : null,
830
- logo_dark_upload_id: c.fields.logo_dark ? `upload_logo_dark_${c.library_item_id}` : null,
773
+ logo_light_upload_id: c.fields.logo_light_id,
774
+ logo_dark_upload_id: c.fields.logo_dark_id,
831
775
  instagram_url: c.fields.socials.find((s) => s.platform === "instagram")?.url ?? null,
832
776
  linkedin_url: c.fields.socials.find((s) => s.platform === "linkedin")?.url ?? null,
833
777
  x_url: c.fields.socials.find((s) => s.platform === "x")?.url ?? null,
@@ -836,4 +780,4 @@ const sampleProperty = {
836
780
  };
837
781
 
838
782
  //#endregion
839
- export { allPhotos, mkResponsive, sampleAmenities, sampleContacts, samplePois, sampleProperty };
783
+ export { allPhotos, sampleAmenities, sampleContacts, samplePois, sampleProperty };
@@ -3,7 +3,7 @@ import { AmenityItem, ContactFixture, PhotoFixture, PoiFixture } from "./sample-
3
3
  //#region src/fixtures/scenario.d.ts
4
4
  type ScenarioKey = "luxuryMultiUnit" | "sparseSingleFamily" | "forRentCondo";
5
5
  type MediaFixture = {
6
- video_url: string | null;
6
+ video_id: string | null;
7
7
  tour_3d_url: string | null;
8
8
  };
9
9
  type Scenario = {