bsmnt 0.13.1 → 0.15.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 (177) hide show
  1. package/dist/core/create/internal-ids.d.ts +1 -0
  2. package/dist/core/create/internal-ids.d.ts.map +1 -1
  3. package/dist/core/create/internal-ids.js +1 -0
  4. package/dist/core/create/internal-ids.js.map +1 -1
  5. package/dist/domain/skills.d.ts +5 -0
  6. package/dist/domain/skills.d.ts.map +1 -1
  7. package/dist/domain/skills.js +9 -1
  8. package/dist/domain/skills.js.map +1 -1
  9. package/dist/infrastructure/create/format-project.d.ts +9 -0
  10. package/dist/infrastructure/create/format-project.d.ts.map +1 -0
  11. package/dist/infrastructure/create/format-project.js +21 -0
  12. package/dist/infrastructure/create/format-project.js.map +1 -0
  13. package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
  14. package/dist/infrastructure/create/lifecycle-registry.js +5 -0
  15. package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
  16. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  17. package/dist/modules/features/cms/sanity-pagebuilder/config.js +1 -0
  18. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  19. package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
  20. package/dist/modules/features/cms/sanity-pagebuilder/index.js +1 -0
  21. package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
  22. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -1
  23. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +1 -0
  24. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -1
  25. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -1
  26. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +1 -0
  27. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/agent-skills/ashby-pagebuilder-block/SKILL.md +29 -11
  30. package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +31 -12
  31. package/src/agent-skills/launch-ready/SKILL.md +114 -0
  32. package/src/agent-skills/launch-ready/references/a11y.md +74 -0
  33. package/src/agent-skills/launch-ready/references/content.md +65 -0
  34. package/src/agent-skills/launch-ready/references/crawlers.md +89 -0
  35. package/src/agent-skills/launch-ready/references/launch.md +44 -0
  36. package/src/agent-skills/launch-ready/references/metadata.md +83 -0
  37. package/src/agent-skills/launch-ready/references/performance.md +36 -0
  38. package/src/agent-skills/launch-ready/references/routing.md +47 -0
  39. package/src/agent-skills/launch-ready/references/security.md +39 -0
  40. package/src/agent-skills/page-builder-block/SKILL.md +26 -13
  41. package/src/modules/features/cms/sanity/files/app/layout.tsx +2 -2
  42. package/src/modules/features/cms/sanity/files/lib/utils/metadata.ts +2 -2
  43. package/src/modules/features/cms/sanity/files/lib/utils/url.ts +6 -3
  44. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +2 -2
  45. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +4 -2
  46. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  47. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  48. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  49. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  50. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  51. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/redirect-import-dialog.tsx +538 -0
  52. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/csv.ts +88 -0
  53. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/breadcrumbs.ts +22 -0
  54. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
  55. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +36 -49
  56. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +1 -50
  57. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +76 -64
  58. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/redirect-import.ts +651 -0
  59. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +6 -8
  60. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  61. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +1 -4
  62. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +9 -6
  63. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/structured-data.ts +8 -0
  64. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  65. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +60 -6
  66. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  67. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +33 -38
  68. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/richText.ts +0 -1
  69. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +0 -16
  70. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +52 -35
  71. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +5 -2
  72. package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +8 -11
  73. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +109 -136
  74. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/studio-embed.ts +22 -0
  75. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +2 -2
  76. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +4 -2
  77. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  78. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  79. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  80. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  81. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  82. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/redirect-import-dialog.tsx +538 -0
  83. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/csv.ts +88 -0
  84. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/breadcrumbs.ts +25 -0
  85. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +2 -2
  86. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +36 -49
  87. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +1 -50
  88. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +76 -64
  89. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/redirect-import.ts +651 -0
  90. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +6 -8
  91. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  92. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +1 -4
  93. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +9 -6
  94. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +8 -0
  95. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  96. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +60 -6
  97. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  98. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +33 -38
  99. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +0 -1
  100. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +0 -16
  101. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +52 -35
  102. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +6 -2
  103. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +8 -11
  104. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +109 -136
  105. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/studio-embed.ts +22 -0
  106. package/src/modules/features/forms/hubspot/files/_sanity-adapter/form-block.ts +4 -10
  107. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/README.md +10 -5
  108. package/src/modules/features/jobs/ashby/files/_sanity-adapter/job-board-block.ts +0 -7
  109. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/README.md +11 -5
  110. package/src/templates/next-default/.env.example +3 -1
  111. package/src/templates/next-default/app/layout.tsx +2 -1
  112. package/src/templates/next-default/app/robots.ts +9 -3
  113. package/src/templates/next-default/lib/utils/metadata.ts +2 -1
  114. package/src/templates/next-default/lib/utils/url.ts +20 -3
  115. package/src/templates/next-default/next.config.ts +16 -0
  116. package/src/templates/next-experiments/.env.example +3 -1
  117. package/src/templates/next-experiments/app/layout.tsx +2 -1
  118. package/src/templates/next-experiments/app/robots.ts +9 -3
  119. package/src/templates/next-experiments/lib/utils/metadata.ts +2 -1
  120. package/src/templates/next-experiments/lib/utils/url.ts +20 -3
  121. package/src/templates/next-experiments/next.config.ts +16 -0
  122. package/src/templates/next-pagebuilder/.env.example +3 -1
  123. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +32 -8
  124. package/src/templates/next-pagebuilder/AGENTS.md +22 -7
  125. package/src/templates/next-pagebuilder/README.md +15 -3
  126. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +45 -7
  127. package/src/templates/next-pagebuilder/app/actions/unlock-gated-content.ts +5 -4
  128. package/src/templates/next-pagebuilder/app/components/page.tsx +81 -0
  129. package/src/templates/next-pagebuilder/app/layout.tsx +3 -41
  130. package/src/templates/next-pagebuilder/app/robots.ts +9 -3
  131. package/src/templates/next-pagebuilder/biome.json +2 -1
  132. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +1 -1
  133. package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +8 -3
  134. package/src/templates/next-pagebuilder/components/page-builder/library.ts +183 -0
  135. package/src/templates/next-pagebuilder/components/page-builder/registry.tsx +4 -1
  136. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +3 -1
  137. package/src/templates/next-pagebuilder/components/page-builder/stage.tsx +10 -0
  138. package/src/templates/next-pagebuilder/components/page-document/index.tsx +28 -35
  139. package/src/templates/next-pagebuilder/lefthook.yml +1 -1
  140. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +170 -299
  141. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +18 -3
  142. package/src/templates/next-pagebuilder/lib/utils/breadcrumbs.ts +70 -0
  143. package/src/templates/next-pagebuilder/next.config.ts +16 -0
  144. package/src/templates/next-pagebuilder/package.json +1 -1
  145. package/src/templates/next-pagebuilder-cache/.env.example +3 -1
  146. package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +32 -8
  147. package/src/templates/next-pagebuilder-cache/AGENTS.md +22 -7
  148. package/src/templates/next-pagebuilder-cache/README.md +15 -3
  149. package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +46 -7
  150. package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +5 -4
  151. package/src/templates/next-pagebuilder-cache/app/components/page.tsx +91 -0
  152. package/src/templates/next-pagebuilder-cache/app/layout.tsx +5 -46
  153. package/src/templates/next-pagebuilder-cache/app/robots.ts +9 -3
  154. package/src/templates/next-pagebuilder-cache/biome.json +2 -1
  155. package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +1 -1
  156. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +8 -3
  157. package/src/templates/next-pagebuilder-cache/components/page-builder/library.ts +183 -0
  158. package/src/templates/next-pagebuilder-cache/components/page-builder/registry.tsx +4 -1
  159. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +3 -1
  160. package/src/templates/next-pagebuilder-cache/components/page-builder/stage.tsx +10 -0
  161. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +28 -35
  162. package/src/templates/next-pagebuilder-cache/lefthook.yml +1 -1
  163. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +170 -299
  164. package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +18 -3
  165. package/src/templates/next-pagebuilder-cache/lib/utils/breadcrumbs.ts +70 -0
  166. package/src/templates/next-pagebuilder-cache/next.config.ts +16 -0
  167. package/src/templates/next-pagebuilder-cache/package.json +1 -1
  168. package/src/templates/next-webgl/.env.example +3 -1
  169. package/src/templates/next-webgl/app/layout.tsx +2 -1
  170. package/src/templates/next-webgl/app/robots.ts +9 -3
  171. package/src/templates/next-webgl/lib/utils/metadata.ts +2 -1
  172. package/src/templates/next-webgl/lib/utils/url.ts +20 -3
  173. package/src/templates/next-webgl/next.config.ts +16 -0
  174. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  175. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
  176. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  177. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
@@ -0,0 +1,183 @@
1
+ import type { PageBuilderDocumentType } from "@/lib/integrations/sanity/page-builder-config"
2
+ import type { PageBuilderBlock } from "./types"
3
+
4
+ /** One block's stand-in value, rendered by `/components` through `Renderer`. */
5
+ export type LibraryEntry = {
6
+ documentType: PageBuilderDocumentType
7
+ title: string
8
+ notes: string
9
+ background: string
10
+ block: PageBuilderBlock
11
+ }
12
+
13
+ type BlockOf<T extends PageBuilderDocumentType> = Extract<
14
+ PageBuilderBlock,
15
+ { _type: T }
16
+ >
17
+
18
+ const span = (key: string, text: string) => ({
19
+ _type: "span" as const,
20
+ _key: key,
21
+ text,
22
+ marks: [],
23
+ })
24
+
25
+ const paragraph = (key: string, text: string) => ({
26
+ _type: "block" as const,
27
+ _key: key,
28
+ style: "normal" as const,
29
+ markDefs: [],
30
+ children: [span(`${key}-span`, text)],
31
+ })
32
+
33
+ const heading = (key: string, text: string) => ({
34
+ ...paragraph(key, text),
35
+ style: "h2" as const,
36
+ })
37
+
38
+ const PLACEHOLDER_KEY = "placeholder"
39
+
40
+ const hero: BlockOf<"hero"> = {
41
+ _key: PLACEHOLDER_KEY,
42
+ _type: "hero",
43
+ headline: "A heading that says what this page is for.",
44
+ }
45
+
46
+ const description: BlockOf<"description"> = {
47
+ _key: PLACEHOLDER_KEY,
48
+ _type: "description",
49
+ description:
50
+ "One paragraph of standfirst copy under a heading — long enough to set up the page, short enough that nobody skips it.",
51
+ }
52
+
53
+ const richTextBlock: BlockOf<"richTextBlock"> = {
54
+ _key: PLACEHOLDER_KEY,
55
+ _type: "richTextBlock",
56
+ content: [
57
+ heading("rt-head", "What long-form copy looks like here"),
58
+ paragraph(
59
+ "rt-one",
60
+ "Rich Text is the block for body copy: headings, paragraphs, lists, links and quotes, all authored in the Studio and rendered with the site's own typography."
61
+ ),
62
+ paragraph(
63
+ "rt-two",
64
+ "Because it is Portable Text rather than HTML, the same content renders in the page, in the markdown route and in the feed without a second template."
65
+ ),
66
+ ],
67
+ }
68
+
69
+ const gatedContent: BlockOf<"gatedContent"> = {
70
+ _key: PLACEHOLDER_KEY,
71
+ _type: "gatedContent",
72
+ internalTitle: "Gated guide",
73
+ teaserHeading: "A guide worth an email address",
74
+ teaserBody: [
75
+ paragraph(
76
+ "gc-teaser",
77
+ "The teaser is public. The payload — rich text, a file or a video — is withheld from the page query until a visitor submits the form."
78
+ ),
79
+ ],
80
+ gateType: "form",
81
+ gateHeadline: "Read the full guide",
82
+ gateDescription: "We'll email you a copy as well.",
83
+ submitLabel: "Get the guide",
84
+ successMessage: "Sent — it's on its way.",
85
+ kind: "richText",
86
+ anchorId: null,
87
+ presentation: null,
88
+ }
89
+
90
+ const blogCollection: BlockOf<"blogCollection"> = {
91
+ _key: PLACEHOLDER_KEY,
92
+ _type: "blogCollection",
93
+ subtitle: "Everything else we've written",
94
+ }
95
+
96
+ const blogContent: BlockOf<"blogContent"> = {
97
+ _key: PLACEHOLDER_KEY,
98
+ _type: "blogContent",
99
+ title: "How this starter renders a blog post",
100
+ categories: [],
101
+ date: "2026-02-11",
102
+ author: {
103
+ _id: "placeholder-author",
104
+ name: "Jane Doe",
105
+ avatar: null,
106
+ role: "Engineer",
107
+ },
108
+ body: [
109
+ paragraph(
110
+ "bc-one",
111
+ "A blog post is an ordinary page whose page builder holds a Blog Posts block: byline, date, categories and body all live on the block."
112
+ ),
113
+ heading("bc-head", "One block, one article"),
114
+ paragraph(
115
+ "bc-two",
116
+ "The collection block reads the same fields, so an article added in the Studio shows up in the index without touching code."
117
+ ),
118
+ ],
119
+ relatedPosts: [],
120
+ }
121
+
122
+ export const COMPONENT_LIBRARY: LibraryEntry[] = [
123
+ {
124
+ documentType: "hero",
125
+ title: "Heading",
126
+ notes: "The page's opening statement. Headline only, no media.",
127
+ background: "bg-white",
128
+ block: hero,
129
+ },
130
+ {
131
+ documentType: "description",
132
+ title: "Description",
133
+ notes: "A single paragraph of standfirst copy under a heading.",
134
+ background: "bg-white",
135
+ block: description,
136
+ },
137
+ {
138
+ documentType: "richTextBlock",
139
+ title: "Rich Text",
140
+ notes: "Long-form body copy — headings, lists, links and quotes.",
141
+ background: "bg-white",
142
+ block: richTextBlock,
143
+ },
144
+ {
145
+ documentType: "gatedContent",
146
+ title: "Gated Content",
147
+ notes:
148
+ "A teaser with an email gate. The payload is withheld until a visitor submits, so the preview shows the locked state.",
149
+ background: "bg-white",
150
+ block: gatedContent,
151
+ },
152
+ {
153
+ documentType: "blogCollection",
154
+ title: "Blog Collection",
155
+ notes:
156
+ "The full article index with search and category filters. Reads real posts.",
157
+ background: "bg-white",
158
+ block: blogCollection,
159
+ },
160
+ {
161
+ documentType: "blogContent",
162
+ title: "Blog Posts",
163
+ notes:
164
+ "The body of an article — byline, date, categories and rich text. Only valid on a blog entry page.",
165
+ background: "bg-white",
166
+ block: blogContent,
167
+ },
168
+ ]
169
+
170
+ export const getLibraryEntry = (documentType: string | undefined) =>
171
+ documentType
172
+ ? (COMPONENT_LIBRARY.find((entry) => entry.documentType === documentType) ??
173
+ null)
174
+ : null
175
+
176
+ export const COMPONENT_PREVIEW_PATH = "/components"
177
+
178
+ export const COMPONENT_PREVIEW_PARAM = "component"
179
+
180
+ export const getComponentPreviewPath = (documentType?: string) =>
181
+ documentType
182
+ ? `${COMPONENT_PREVIEW_PATH}?${COMPONENT_PREVIEW_PARAM}=${encodeURIComponent(documentType)}`
183
+ : COMPONENT_PREVIEW_PATH
@@ -13,6 +13,7 @@ import { RichTextBlock } from "./components/rich-text-block"
13
13
  import type { PageBuilderBlock } from "./types"
14
14
 
15
15
  export type BlockContext = {
16
+ pageId: string
16
17
  searchParams: ContentCollectionSearchParams
17
18
  pinnedCategory: PinnedCollectionCategory
18
19
  }
@@ -48,7 +49,9 @@ const blockRegistry: {
48
49
  isEmpty: (block) => !block.description,
49
50
  },
50
51
  gatedContent: {
51
- render: (block) => <GatedContent block={block} />,
52
+ render: (block, { pageId }) => (
53
+ <GatedContent block={block} pageId={pageId} />
54
+ ),
52
55
  },
53
56
  hero: {
54
57
  render: (block) => <Hero block={block} />,
@@ -12,18 +12,20 @@ import type { PageBuilderBlock, PageBuilderBlocks } from "./types"
12
12
 
13
13
  type RendererProps = {
14
14
  blocks: PageBuilderBlocks
15
+ pageId: string
15
16
  searchParams: ContentCollectionSearchParams
16
17
  pinnedCategory: PinnedCollectionCategory
17
18
  }
18
19
 
19
20
  export function Renderer({
20
21
  blocks,
22
+ pageId,
21
23
  searchParams,
22
24
  pinnedCategory,
23
25
  }: RendererProps) {
24
26
  if (!blocks?.length) return null
25
27
 
26
- const context: BlockContext = { searchParams, pinnedCategory }
28
+ const context: BlockContext = { pageId, searchParams, pinnedCategory }
27
29
 
28
30
  const reservedAnchorIds = reserveAnchorIds(blocks)
29
31
  const takenAnchorIds = new Set<string>()
@@ -0,0 +1,10 @@
1
+ import type { PropsWithChildren } from "react"
2
+ import { cn } from "@/lib/styles/cn"
3
+
4
+ type StageProps = PropsWithChildren<{ background?: string }>
5
+
6
+ export const Stage = ({ background, children }: StageProps) => (
7
+ <div className={cn("grid min-h-svh content-center", background)}>
8
+ {children}
9
+ </div>
10
+ )
@@ -1,9 +1,13 @@
1
1
  import { stegaClean } from "@sanity/client/stega"
2
2
  import type { ContentCollectionSearchParams } from "@/components/page-builder/components/content-collection/types"
3
3
  import { Renderer } from "@/components/page-builder/renderer"
4
- import type { PAGE_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
4
+ import type {
5
+ BREADCRUMB_PAGES_QUERY_RESULT,
6
+ PAGE_QUERY_RESULT,
7
+ } from "@/lib/integrations/sanity/sanity.types"
5
8
  import { urlForImage } from "@/lib/integrations/sanity/utils/image"
6
9
  import { buildPageJsonLd } from "@/lib/integrations/sanity/utils/page-json-ld"
10
+ import { buildBreadcrumbs } from "@/lib/utils/breadcrumbs"
7
11
  import {
8
12
  generateBreadcrumbJsonLd,
9
13
  generateSanityArticleJsonLd,
@@ -15,6 +19,7 @@ type PageDocumentProps = {
15
19
  page: NonNullable<PAGE_QUERY_RESULT>
16
20
  path: string | null
17
21
  searchParams: ContentCollectionSearchParams
22
+ breadcrumbPages: BREADCRUMB_PAGES_QUERY_RESULT
18
23
  }
19
24
 
20
25
  type PageBuilderBlock = NonNullable<
@@ -22,6 +27,13 @@ type PageBuilderBlock = NonNullable<
22
27
  >[number]
23
28
  type BlogContentBlock = Extract<PageBuilderBlock, { _type: "blogContent" }>
24
29
 
30
+ // Blog posts are pages with a Blog Posts block; it carries the post's date,
31
+ // author and thumbnail.
32
+ export const findBlogBlock = (page: NonNullable<PAGE_QUERY_RESULT>) =>
33
+ page.pageBuilder?.find(
34
+ (block): block is BlogContentBlock => block?._type === "blogContent"
35
+ )
36
+
25
37
  export const getPageSections = (
26
38
  page: NonNullable<PAGE_QUERY_RESULT>,
27
39
  searchParams: ContentCollectionSearchParams
@@ -31,48 +43,27 @@ export const getPageSections = (
31
43
  <Renderer
32
44
  key="page-builder"
33
45
  blocks={page.pageBuilder}
46
+ pageId={page._id}
34
47
  searchParams={searchParams}
35
48
  pinnedCategory={stegaClean(page.collectionCategory)}
36
49
  />,
37
50
  ]
38
51
  : []
39
52
 
40
- const buildBreadcrumbs = (
41
- slugPath: string | null,
42
- pageTitle: string | null
43
- ): Array<{ name: string; url: string }> => {
44
- const items: Array<{ name: string; url: string }> = [
45
- { name: "Home", url: "/" },
46
- ]
47
-
48
- if (!slugPath) return items
49
-
50
- const segments = slugPath.split("/").filter(Boolean)
51
- for (let i = 0; i < segments.length; i++) {
52
- const isLast = i === segments.length - 1
53
- const url = `/${segments.slice(0, i + 1).join("/")}`
54
-
55
- //TODO: we might change this in the future if slug title and folder diverge too much :)
56
-
57
- const name =
58
- isLast && pageTitle
59
- ? pageTitle
60
- : (segments[i]
61
- ?.replace(/-/g, " ")
62
- .replace(/\b\w/g, (c) => c.toUpperCase()) ?? "")
63
- items.push({ name, url })
64
- }
65
-
66
- return items
67
- }
68
-
69
53
  export const PageDocument = ({
70
54
  page,
71
55
  path,
72
56
  searchParams,
57
+ breadcrumbPages,
73
58
  }: PageDocumentProps) => {
74
59
  const title = stegaClean(page.title) || page.title
75
60
  const sections = getPageSections(page, searchParams)
61
+ // A Heading block renders the page's H1, so the title only stands in without
62
+ // one. Hidden blocks are already filtered out of the query, and an empty one
63
+ // renders nothing (see the registry's `isEmpty`).
64
+ const hasHeadingBlock = page.pageBuilder?.some(
65
+ (block) => block?._type === "hero" && Boolean(block.headline)
66
+ )
76
67
 
77
68
  const cleanTitle = stegaClean(page.title) || "Basement"
78
69
  const cleanDescription = stegaClean(
@@ -84,7 +75,11 @@ export const PageDocument = ({
84
75
  ? urlForImage(page.metadata.image).width(1200).height(630).url()
85
76
  : undefined
86
77
 
87
- const breadcrumbs = buildBreadcrumbs(path, cleanTitle)
78
+ const breadcrumbs = buildBreadcrumbs({
79
+ path,
80
+ name: cleanTitle,
81
+ pages: breadcrumbPages,
82
+ })
88
83
  const hasBreadcrumbs = breadcrumbs.length > 1
89
84
 
90
85
  // Editor-selected page-level structured data (FAQ, Event, custom, etc.).
@@ -93,9 +88,7 @@ export const PageDocument = ({
93
88
  // Blog posts (pages with a Blog Posts block) emit Article JSON-LD with
94
89
  // fields derived from the post — no Custom block needed. Skipped when the
95
90
  // editor already supplied an Article (via a Custom block) to avoid duplicates.
96
- const blogBlock = page.pageBuilder?.find(
97
- (block): block is BlogContentBlock => block?._type === "blogContent"
98
- )
91
+ const blogBlock = findBlogBlock(page)
99
92
  const hasManualArticle = pageJsonLd.some(
100
93
  ({ data }) => (data as { "@type"?: unknown })["@type"] === "Article"
101
94
  )
@@ -133,7 +126,7 @@ export const PageDocument = ({
133
126
  <JsonLd key={key} data={data} />
134
127
  ))}
135
128
  <article className="mx-auto flex w-full max-w-6xl flex-1 flex-col px-4 py-14 md:px-6 md:py-20">
136
- <h1>{title}</h1>
129
+ {hasHeadingBlock ? null : <h1>{title}</h1>}
137
130
  {sections.length ? (
138
131
  sections
139
132
  ) : (
@@ -7,7 +7,7 @@ pre-commit:
7
7
  commands:
8
8
  biome:
9
9
  glob: '*.{js,mjs,ts,jsx,tsx,css,scss}'
10
- run: bun biome check --max-diagnostics=200 --write --unsafe {staged_files}
10
+ run: bun biome check --max-diagnostics=200 --write {staged_files}
11
11
  stage_fixed: true
12
12
  typecheck:
13
13
  glob: '*.{ts,tsx}'