bsmnt 0.13.1 → 0.14.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 (80) hide show
  1. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  2. package/dist/modules/features/cms/sanity-pagebuilder/config.js +1 -0
  3. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  4. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -1
  5. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +1 -0
  6. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/agent-skills/ashby-pagebuilder-block/SKILL.md +29 -11
  9. package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +31 -12
  10. package/src/agent-skills/page-builder-block/SKILL.md +26 -13
  11. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +2 -2
  12. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +1 -1
  13. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  14. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  15. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  16. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  17. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  18. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
  19. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +21 -49
  20. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +1 -50
  21. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +56 -64
  22. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +4 -8
  23. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  24. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +1 -4
  25. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  26. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  27. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +11 -37
  28. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +0 -16
  29. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +36 -34
  30. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/studio-embed.ts +22 -0
  31. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +2 -2
  32. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +1 -1
  33. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  34. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  35. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  36. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  37. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  38. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +2 -2
  39. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +21 -49
  40. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +1 -50
  41. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +56 -64
  42. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +4 -8
  43. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  44. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +1 -4
  45. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  46. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  47. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +11 -37
  48. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +0 -16
  49. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +36 -34
  50. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/studio-embed.ts +22 -0
  51. package/src/modules/features/forms/hubspot/files/_sanity-adapter/form-block.ts +4 -10
  52. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/README.md +10 -5
  53. package/src/modules/features/jobs/ashby/files/_sanity-adapter/job-board-block.ts +0 -7
  54. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/README.md +11 -5
  55. package/src/templates/next-pagebuilder/AGENTS.md +19 -7
  56. package/src/templates/next-pagebuilder/README.md +15 -3
  57. package/src/templates/next-pagebuilder/app/actions/unlock-gated-content.ts +5 -4
  58. package/src/templates/next-pagebuilder/app/components/page.tsx +81 -0
  59. package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +8 -3
  60. package/src/templates/next-pagebuilder/components/page-builder/library.ts +183 -0
  61. package/src/templates/next-pagebuilder/components/page-builder/registry.tsx +4 -1
  62. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +3 -1
  63. package/src/templates/next-pagebuilder/components/page-builder/stage.tsx +10 -0
  64. package/src/templates/next-pagebuilder/components/page-document/index.tsx +1 -0
  65. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +139 -271
  66. package/src/templates/next-pagebuilder-cache/AGENTS.md +19 -7
  67. package/src/templates/next-pagebuilder-cache/README.md +15 -3
  68. package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +5 -4
  69. package/src/templates/next-pagebuilder-cache/app/components/page.tsx +91 -0
  70. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +8 -3
  71. package/src/templates/next-pagebuilder-cache/components/page-builder/library.ts +183 -0
  72. package/src/templates/next-pagebuilder-cache/components/page-builder/registry.tsx +4 -1
  73. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +3 -1
  74. package/src/templates/next-pagebuilder-cache/components/page-builder/stage.tsx +10 -0
  75. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +1 -0
  76. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +139 -271
  77. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  78. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
  79. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  80. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../src/modules/features/cms/sanity-pagebuilder/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,kCAAkC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,aAAa,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,eAAO,MAAM,uBAAuB,EAAE,kCAgDrC,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../src/modules/features/cms/sanity-pagebuilder/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,kCAAkC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,aAAa,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,eAAO,MAAM,uBAAuB,EAAE,kCAiDrC,CAAC"}
@@ -37,6 +37,7 @@ export const sanityPagebuilderConfig = {
37
37
  "lib/utils/metadata.ts",
38
38
  "lib/utils/rss.ts",
39
39
  "lib/utils/sitemap.ts",
40
+ "lib/utils/studio-embed.ts",
40
41
  "lib/scripts/sanity-typegen.ts",
41
42
  "app/studio",
42
43
  "app/api",
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../src/modules/features/cms/sanity-pagebuilder/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,CAAC,MAAM,uBAAuB,GAAuC;IAC1E,IAAI,EAAE,oBAAoB;IAE1B,YAAY,EAAE;QACb,uBAAuB,EAAE,SAAS;QAClC,qBAAqB,EAAE,QAAQ;QAC/B,qBAAqB,EAAE,QAAQ;QAC/B,eAAe,EAAE,QAAQ;QACzB,mBAAmB,EAAE,QAAQ;QAC7B,eAAe,EAAE,QAAQ;QACzB,YAAY,EAAE,SAAS;QACvB,gBAAgB,EAAE,SAAS;QAC3B,wBAAwB,EAAE,QAAQ;QAClC,aAAa,EAAE,SAAS;QACxB,MAAM,EAAE,SAAS;QACjB,qBAAqB,EAAE,QAAQ;KAC/B;IAED,eAAe,EAAE;QAChB,6BAA6B,EAAE,QAAQ;QACvC,YAAY,EAAE,QAAQ;KACtB;IAED,OAAO,EAAE;QACR,gBAAgB,EACf,wFAAwF;QACzF,gBAAgB,EAAE,qCAAqC;QACvD,MAAM,EAAE,wBAAwB;QAChC,QAAQ,EAAE,wBAAwB;KAClC;IAED,aAAa,EAAE;QACd,yBAAyB;QACzB,uCAAuC;QACvC,6BAA6B;QAC7B,uBAAuB;QACvB,uBAAuB;QACvB,kBAAkB;QAClB,sBAAsB;QACtB,+BAA+B;QAC/B,YAAY;QACZ,SAAS;QACT,cAAc;QACd,gBAAgB;QAChB,UAAU;QACV,mBAAmB;QACnB,4BAA4B;KAC5B;CACD,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../src/modules/features/cms/sanity-pagebuilder/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,CAAC,MAAM,uBAAuB,GAAuC;IAC1E,IAAI,EAAE,oBAAoB;IAE1B,YAAY,EAAE;QACb,uBAAuB,EAAE,SAAS;QAClC,qBAAqB,EAAE,QAAQ;QAC/B,qBAAqB,EAAE,QAAQ;QAC/B,eAAe,EAAE,QAAQ;QACzB,mBAAmB,EAAE,QAAQ;QAC7B,eAAe,EAAE,QAAQ;QACzB,YAAY,EAAE,SAAS;QACvB,gBAAgB,EAAE,SAAS;QAC3B,wBAAwB,EAAE,QAAQ;QAClC,aAAa,EAAE,SAAS;QACxB,MAAM,EAAE,SAAS;QACjB,qBAAqB,EAAE,QAAQ;KAC/B;IAED,eAAe,EAAE;QAChB,6BAA6B,EAAE,QAAQ;QACvC,YAAY,EAAE,QAAQ;KACtB;IAED,OAAO,EAAE;QACR,gBAAgB,EACf,wFAAwF;QACzF,gBAAgB,EAAE,qCAAqC;QACvD,MAAM,EAAE,wBAAwB;QAChC,QAAQ,EAAE,wBAAwB;KAClC;IAED,aAAa,EAAE;QACd,yBAAyB;QACzB,uCAAuC;QACvC,6BAA6B;QAC7B,uBAAuB;QACvB,uBAAuB;QACvB,kBAAkB;QAClB,sBAAsB;QACtB,2BAA2B;QAC3B,+BAA+B;QAC/B,YAAY;QACZ,SAAS;QACT,cAAc;QACd,gBAAgB;QAChB,UAAU;QACV,mBAAmB;QACnB,4BAA4B;KAC5B;CACD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../src/modules/features/cms/sanity-pagebuilder-cache/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,uCAAuC;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,aAAa,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,eAAO,MAAM,4BAA4B,EAAE,uCAiDzC,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../src/modules/features/cms/sanity-pagebuilder-cache/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,uCAAuC;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,aAAa,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,eAAO,MAAM,4BAA4B,EAAE,uCAkDzC,CAAC"}
@@ -37,6 +37,7 @@ export const sanityPagebuilderCacheConfig = {
37
37
  "lib/utils/metadata.ts",
38
38
  "lib/utils/rss.ts",
39
39
  "lib/utils/sitemap.ts",
40
+ "lib/utils/studio-embed.ts",
40
41
  "lib/scripts/sanity-typegen.ts",
41
42
  "app/studio",
42
43
  "app/api",
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../src/modules/features/cms/sanity-pagebuilder-cache/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,CAAC,MAAM,4BAA4B,GACxC;IACC,IAAI,EAAE,0BAA0B;IAEhC,YAAY,EAAE;QACb,uBAAuB,EAAE,SAAS;QAClC,qBAAqB,EAAE,QAAQ;QAC/B,qBAAqB,EAAE,QAAQ;QAC/B,eAAe,EAAE,QAAQ;QACzB,mBAAmB,EAAE,QAAQ;QAC7B,eAAe,EAAE,QAAQ;QACzB,YAAY,EAAE,SAAS;QACvB,gBAAgB,EAAE,SAAS;QAC3B,wBAAwB,EAAE,QAAQ;QAClC,aAAa,EAAE,SAAS;QACxB,MAAM,EAAE,SAAS;QACjB,qBAAqB,EAAE,QAAQ;KAC/B;IAED,eAAe,EAAE;QAChB,6BAA6B,EAAE,QAAQ;QACvC,YAAY,EAAE,QAAQ;KACtB;IAED,OAAO,EAAE;QACR,gBAAgB,EACf,wFAAwF;QACzF,gBAAgB,EAAE,qCAAqC;QACvD,MAAM,EAAE,wBAAwB;QAChC,QAAQ,EAAE,wBAAwB;KAClC;IAED,aAAa,EAAE;QACd,yBAAyB;QACzB,uCAAuC;QACvC,6BAA6B;QAC7B,uBAAuB;QACvB,uBAAuB;QACvB,kBAAkB;QAClB,sBAAsB;QACtB,+BAA+B;QAC/B,YAAY;QACZ,SAAS;QACT,cAAc;QACd,gBAAgB;QAChB,UAAU;QACV,mBAAmB;QACnB,4BAA4B;KAC5B;CACD,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../src/modules/features/cms/sanity-pagebuilder-cache/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,CAAC,MAAM,4BAA4B,GACxC;IACC,IAAI,EAAE,0BAA0B;IAEhC,YAAY,EAAE;QACb,uBAAuB,EAAE,SAAS;QAClC,qBAAqB,EAAE,QAAQ;QAC/B,qBAAqB,EAAE,QAAQ;QAC/B,eAAe,EAAE,QAAQ;QACzB,mBAAmB,EAAE,QAAQ;QAC7B,eAAe,EAAE,QAAQ;QACzB,YAAY,EAAE,SAAS;QACvB,gBAAgB,EAAE,SAAS;QAC3B,wBAAwB,EAAE,QAAQ;QAClC,aAAa,EAAE,SAAS;QACxB,MAAM,EAAE,SAAS;QACjB,qBAAqB,EAAE,QAAQ;KAC/B;IAED,eAAe,EAAE;QAChB,6BAA6B,EAAE,QAAQ;QACvC,YAAY,EAAE,QAAQ;KACtB;IAED,OAAO,EAAE;QACR,gBAAgB,EACf,wFAAwF;QACzF,gBAAgB,EAAE,qCAAqC;QACvD,MAAM,EAAE,wBAAwB;QAChC,QAAQ,EAAE,wBAAwB;KAClC;IAED,aAAa,EAAE;QACd,yBAAyB;QACzB,uCAAuC;QACvC,6BAA6B;QAC7B,uBAAuB;QACvB,uBAAuB;QACvB,kBAAkB;QAClB,sBAAsB;QACtB,2BAA2B;QAC3B,+BAA+B;QAC/B,YAAY;QACZ,SAAS;QACT,cAAc;QACd,gBAAgB;QAChB,UAAU;QACV,mBAAmB;QACnB,4BAA4B;KAC5B;CACD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bsmnt",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "packageManager": "bun@1.3.14",
5
5
  "description": "CLI to scaffold basement projects and add integrations",
6
6
  "type": "module",
@@ -42,7 +42,7 @@ The shipped adapter uses plain `defineType` so it also works in the standard
42
42
  Sanity integration, which has no page builder. In this template every block must
43
43
  go through `defineBlock`
44
44
  (`lib/integrations/sanity/schemas/shared/block.ts`) — it implies
45
- `type: "document"` and appends **Anchor ID** and **Presentation Mode**, the two
45
+ `type: "object"` and appends **Anchor ID** and **Presentation Mode**, the two
46
46
  fields the renderer reads for the anchor and the spacing. A block without them
47
47
  fails `page-builder-config`'s conventions test.
48
48
 
@@ -70,7 +70,7 @@ generated anchor from the editor's heading with no extra work.
70
70
 
71
71
  Export it from `lib/integrations/sanity/schemas/components/index.ts` and add it
72
72
  to the `schema.types` array in `lib/integrations/sanity/schemas/index.ts`,
73
- alongside the other component documents (near `hero` / `description`):
73
+ alongside the other block types (near `hero` / `description`):
74
74
 
75
75
  ```ts
76
76
  import { ashbyJobBoardBlock } from "./components"
@@ -85,7 +85,9 @@ export const schema: { types: SchemaTypeDefinition[] } = {
85
85
 
86
86
  ## 2. Add it to the page-builder config — `lib/integrations/sanity/page-builder-config.ts`
87
87
 
88
- Extend the `PageBuilderDocumentType` union and add a reusable reference member.
88
+ Extend the `PageBuilderDocumentType` union and add a block member. Blocks are
89
+ inlined, so the member is identified by its schema type alone — there is no
90
+ separate member name and no reference type.
89
91
  Match the member shape the file actually uses — in the current basement page
90
92
  builder that's `pageTypes` (which page documents may host it) plus `category`
91
93
  (which insert-menu group it lands in):
@@ -97,25 +99,41 @@ export type PageBuilderDocumentType =
97
99
  | "ashbyJobBoardBlock"
98
100
  // ...rest...
99
101
 
100
- // inside reusablePageBuilderReferenceMembers:
102
+ // inside pageBuilderBlockMembers:
101
103
  {
102
104
  documentType: "ashbyJobBoardBlock",
103
- name: "ashbyJobBoardBlockReference",
104
105
  title: "Job Board",
105
106
  pageTypes: ["generic"],
106
107
  category: "list",
107
108
  },
108
109
  ```
109
110
 
110
- ## 3. List it in the Studio — `lib/integrations/sanity/structure.ts`
111
+ ## 3. Give it a fixture — `components/page-builder/library.ts`
111
112
 
112
- Under the "Reusable Components" list, add (the schema's own case icon is used
113
- automatically, so `.icon()` is optional):
113
+ A block's preview is a fixture rendered by the production renderer, not an
114
+ image: the same entry drives the live pane in the insert menu, the block's row
115
+ in Studio → **Components** and the `/components` route. Nothing goes in
116
+ `structure.ts` — that list is generated from `COMPONENT_LIBRARY`.
114
117
 
115
118
  ```ts
116
- S.documentTypeListItem("ashbyJobBoardBlock").title("Job Boards"),
119
+ // inside COMPONENT_LIBRARY:
120
+ {
121
+ documentType: "ashbyJobBoardBlock",
122
+ title: "Job Board",
123
+ notes: "Live roles pulled from Ashby, grouped by team.",
124
+ background: "bg-white",
125
+ block: {
126
+ _key: "placeholder",
127
+ _type: "ashbyJobBoardBlock",
128
+ internalTitle: "Open roles",
129
+ heading: "Open roles",
130
+ },
131
+ },
117
132
  ```
118
133
 
134
+ The `block` value is typed as the block's *query result*, so a field the
135
+ projection doesn't return is a typecheck error here rather than a blank preview.
136
+
119
137
  ## 4. Icon — `lib/integrations/sanity/icons.ts`
120
138
 
121
139
  The adapter defaults to `@sanity/icons`' `CaseIcon`. This project uses phosphor
@@ -241,8 +259,8 @@ bun typecheck
241
259
  bun lint
242
260
  ```
243
261
 
244
- `bun typecheck` must pass. The page query already spreads dereferenced block
245
- fields (`...@->{ ... }`), so `heading`, `boardName`, `anchorId` and
262
+ `bun typecheck` must pass. The page query spreads inlined block fields (`...`),
263
+ so `heading`, `boardName`, `anchorId` and
246
264
  `presentation` flow through with no query change.
247
265
 
248
266
  Then check it in the Studio: the block's form ends with **Anchor ID** and
@@ -37,7 +37,7 @@ The shipped adapter uses plain `defineType` so it also works in the standard
37
37
  Sanity integration, which has no page builder. In this template every block must
38
38
  go through `defineBlock`
39
39
  (`lib/integrations/sanity/schemas/shared/block.ts`) — it implies
40
- `type: "document"` and appends **Anchor ID** and **Presentation Mode**, the two
40
+ `type: "object"` and appends **Anchor ID** and **Presentation Mode**, the two
41
41
  fields the renderer reads for the anchor and the spacing. A block without them
42
42
  fails `page-builder-config`'s conventions test.
43
43
 
@@ -62,7 +62,7 @@ export const hubspotFormBlock = defineBlock({
62
62
 
63
63
  Export it from `lib/integrations/sanity/schemas/components/index.ts` and add it
64
64
  to the `schema.types` array in `lib/integrations/sanity/schemas/index.ts`,
65
- alongside the other component documents (near `hero` / `description`):
65
+ alongside the other block types (near `hero` / `description`):
66
66
 
67
67
  ```ts
68
68
  import { hubspotFormBlock } from "./components"
@@ -77,7 +77,9 @@ export const schema: { types: SchemaTypeDefinition[] } = {
77
77
 
78
78
  ## 2. Add it to the page-builder config — `lib/integrations/sanity/page-builder-config.ts`
79
79
 
80
- Extend the `PageBuilderDocumentType` union and add a reusable reference member:
80
+ Extend the `PageBuilderDocumentType` union and add a block member. Blocks are
81
+ inlined, so the member is identified by its schema type alone — there is no
82
+ separate member name and no reference type:
81
83
 
82
84
  ```ts
83
85
  export type PageBuilderDocumentType =
@@ -86,24 +88,41 @@ export type PageBuilderDocumentType =
86
88
  | "hubspotFormBlock"
87
89
  // ...rest...
88
90
 
89
- // inside reusablePageBuilderReferenceMembers:
91
+ // inside pageBuilderBlockMembers:
90
92
  {
91
93
  documentType: "hubspotFormBlock",
92
- name: "hubspotFormBlockReference",
93
94
  title: "HubSpot Form",
94
- groups: ["generic"],
95
+ pageTypes: ["generic"],
96
+ category: "form",
95
97
  },
96
98
  ```
97
99
 
98
- ## 3. List it in the Studio — `lib/integrations/sanity/structure.ts`
100
+ ## 3. Give it a fixture — `components/page-builder/library.ts`
99
101
 
100
- Under the "Reusable Components" list, add (the schema's own envelope icon is used
101
- automatically, so `.icon()` is optional):
102
+ A block's preview is a fixture rendered by the production renderer, not an
103
+ image: the same entry drives the live pane in the insert menu, the block's row
104
+ in Studio → **Components** and the `/components` route. Nothing goes in
105
+ `structure.ts` — that list is generated from `COMPONENT_LIBRARY`.
102
106
 
103
107
  ```ts
104
- S.documentTypeListItem("hubspotFormBlock").title("HubSpot Forms"),
108
+ // inside COMPONENT_LIBRARY:
109
+ {
110
+ documentType: "hubspotFormBlock",
111
+ title: "HubSpot Form",
112
+ notes:
113
+ "An embedded HubSpot form. The preview renders the frame without a form selected.",
114
+ background: "bg-white",
115
+ block: {
116
+ _key: "placeholder",
117
+ _type: "hubspotFormBlock",
118
+ name: "Book a demo",
119
+ },
120
+ },
105
121
  ```
106
122
 
123
+ The `block` value is typed as the block's *query result*, so a field the
124
+ projection doesn't return is a typecheck error here rather than a blank preview.
125
+
107
126
  ## 4. Envelope icon — `lib/integrations/sanity/icons.ts`
108
127
 
109
128
  The adapter defaults to `@sanity/icons`' `EnvelopeIcon`. This project uses
@@ -175,8 +194,8 @@ bun typecheck
175
194
  bun lint
176
195
  ```
177
196
 
178
- `bun typecheck` must pass. The page query already spreads dereferenced block
179
- fields (`...@->{ ... }`), so `form` flows through with no query change.
197
+ `bun typecheck` must pass. The page query spreads inlined block fields (`...`),
198
+ so `form` flows through with no query change.
180
199
 
181
200
  ## Direct rendering (no page builder)
182
201
 
@@ -13,21 +13,23 @@ description: >-
13
13
 
14
14
  # Page-builder block conventions
15
15
 
16
- Blocks are **documents** referenced from the `pageBuilder` array, drawn by
16
+ Blocks are **inline objects** in the `pageBuilder` array — never documents, and
17
+ never referenced. A placement belongs to exactly one page, so the block lives on
18
+ that page and nothing is dereferenced at read time. They are drawn by
17
19
  `components/page-builder/renderer.tsx` through the registry in
18
20
  `components/page-builder/registry.tsx`. Read one existing block end to end
19
21
  before writing a new one — `hero` is the smallest complete example:
20
22
 
21
23
  - schema: `lib/integrations/sanity/schemas/components/reusable/hero.ts`
22
24
  - component: `components/page-builder/components/hero.tsx`
25
+ - fixture: `components/page-builder/library.ts`
23
26
  - registration: `lib/integrations/sanity/schemas/components/index.ts`,
24
27
  `lib/integrations/sanity/schemas/index.ts`,
25
28
  `lib/integrations/sanity/page-builder-config.ts`,
26
- `lib/integrations/sanity/structure.ts`,
27
29
  `components/page-builder/registry.tsx`
28
30
 
29
31
  A block exists in two halves — a schema and a component — and each half has one
30
- place that makes it a block rather than an ordinary document or a loose React
32
+ place that makes it a block rather than an ordinary object type or a loose React
31
33
  component. Use them and the conventions below come for free; skip them and a
32
34
  typecheck fails.
33
35
 
@@ -35,8 +37,10 @@ typecheck fails.
35
37
 
36
38
  1. **Define the schema with `defineBlock`, never `defineType`.**
37
39
  `lib/integrations/sanity/schemas/shared/block.ts` implies
38
- `type: "document"` and appends Anchor ID + Presentation Mode as the last two
39
- fields:
40
+ `type: "object"` and appends Anchor ID + Presentation Mode as the last two
41
+ fields. The array member's type name is the block's `_type`, so the schema
42
+ name is also what the registry, the insert menu and the fixture library key
43
+ off:
40
44
 
41
45
  ```ts
42
46
  import { defineField } from "sanity"
@@ -123,7 +127,15 @@ typecheck fails.
123
127
  `SANITY_LAYOUT_TAGS` (`fetchers/layout.ts`, or `tags.ts` on the Cache
124
128
  Components variant), same as navbar and footer.
125
129
 
126
- 5. **Regenerate types after a schema change:** `bun run sanity:typegen`. The
130
+ 5. **Ship a fixture in `components/page-builder/library.ts`.** One entry per
131
+ block: a stand-in value typed as the block's *query result* (so a schema or
132
+ projection change breaks the fixture at typecheck), a one-line note and a
133
+ stage background. That entry is the block's only preview — the live pane in
134
+ the insert menu, its row in Studio → Components and the `/components` route
135
+ all render it through the production renderer. Nothing needs adding to
136
+ `structure.ts`.
137
+
138
+ 6. **Regenerate types after a schema change:** `bun run sanity:typegen`. The
127
139
  page query spreads `...` for non-gated blocks, so new fields flow through
128
140
  automatically — except `gatedContent`, whose GROQ branch lists fields
129
141
  explicitly (`lib/integrations/sanity/queries.ts`). Add new fields there by
@@ -131,20 +143,21 @@ typecheck fails.
131
143
 
132
144
  ## Checklist for a new block
133
145
 
134
- - [ ] Schema in `schemas/components/reusable/<block>.ts` (or `singleton/` for a
135
- one-instance block), built with `defineBlock` and carrying a `preview`.
146
+ - [ ] Schema in `schemas/components/reusable/<block>.ts`, built with
147
+ `defineBlock` and carrying a `preview`.
136
148
  - [ ] Icon in `lib/integrations/sanity/icons.ts` (phosphor duotone).
137
149
  - [ ] Exported from `schemas/components/index.ts` and registered in
138
150
  `schemas/index.ts` (`schema.types`).
139
151
  - [ ] Member added to `page-builder-config.ts`: the `PageBuilderDocumentType`
140
- union plus an entry in `reusablePageBuilderReferenceMembers` carrying
141
- **both** axes — `pageTypes` (which page documents may host it) and
142
- `category` (what the block is, one of `componentCategories`; `misc` only
143
- when nothing fits).
144
- - [ ] Listed in `structure.ts` under the right folder.
152
+ union plus an entry in `pageBuilderBlockMembers` carrying **both** axes —
153
+ `pageTypes` (which page documents may host it) and `category` (what the
154
+ block is, one of `componentCategories`; `misc` only when nothing fits).
145
155
  - [ ] Component in `components/page-builder/components/<block>.tsx`, typed with
146
156
  `Extract<PageBuilderBlock, { _type: "<block>" }>`, no outer section, no
147
157
  vertical padding. Variants via `cva`/`cn` when the block has variants.
148
158
  - [ ] Entry in `components/page-builder/registry.tsx`, with `isEmpty` when the
149
159
  block can be left unfilled.
160
+ - [ ] Fixture in `components/page-builder/library.ts`, typed as the block's
161
+ query result. It is the block's preview everywhere, so nothing needs
162
+ adding to `structure.ts`.
150
163
  - [ ] `bun run sanity:typegen && bun run typecheck && bun run lint && bun run build && bun run knip`.
@@ -8,7 +8,7 @@ import { parseBody } from "next-sanity/webhook";
8
8
  import { directClient } from "@/lib/integrations/sanity/client";
9
9
  import { SANITY_LAYOUT_TAGS } from "@/lib/integrations/sanity/fetchers/layout";
10
10
  import { SANITY_REDIRECTS_TAG } from "@/lib/integrations/sanity/fetchers/redirects";
11
- import { pageBuilderReferenceMembers as pbrm } from "@/lib/integrations/sanity/page-builder-config";
11
+ import { pageBuilderBlockMembers } from "@/lib/integrations/sanity/page-builder-config";
12
12
  import {
13
13
  FOLDER_PATH_QUERY,
14
14
  P_REF_DOC_PAGES_QUERY,
@@ -63,7 +63,7 @@ type RevalidateWebhookBody = WebhookDocument & {
63
63
  };
64
64
 
65
65
  const referenceableTypes = new Set<string>([
66
- ...pbrm.map((m) => m.documentType),
66
+ ...pageBuilderBlockMembers.map((member) => member.documentType),
67
67
  ...REFERENCEABLE_TYPES,
68
68
  ]);
69
69
 
@@ -4,7 +4,7 @@ import { SanityImage } from "@/components/ui/sanity-image";
4
4
  import {
5
5
  getLinkAttributes,
6
6
  urlForReference,
7
- } from "../../lib/integrations/sanity/utils/link";
7
+ } from "@/lib/integrations/sanity/utils/link";
8
8
 
9
9
  interface RichTextProps {
10
10
  content: PortableTextProps["value"];
@@ -6,7 +6,7 @@ import { apiVersion } from "@/lib/integrations/sanity/env";
6
6
 
7
7
  const HIDDEN_KEYS_FIELD = "hiddenPageBuilderKeys";
8
8
 
9
- type ReferenceItem = { _key: string; _ref: string; _type: string };
9
+ type BlockItem = { _key: string; _type?: string };
10
10
 
11
11
  /**
12
12
  * Eye-icon toggle for a page builder placement's per-page visibility. Adds the
@@ -14,7 +14,7 @@ type ReferenceItem = { _key: string; _ref: string; _type: string };
14
14
  * filters those keys out at query time. Standalone control because Sanity's
15
15
  * built-in item menu isn't extensible via public props.
16
16
  */
17
- export function BlockActionsItem(props: ObjectItemProps<ReferenceItem>) {
17
+ export function BlockActionsItem(props: ObjectItemProps<BlockItem>) {
18
18
  const { value, renderDefault } = props;
19
19
  const client = useClient({ apiVersion });
20
20
  const toast = useToast();
@@ -0,0 +1,124 @@
1
+ import { Box, Button, Card, Code, Flex, Text } from "@sanity/ui";
2
+ import { useEffect, useRef, useState } from "react";
3
+
4
+ const VIEWPORTS = [
5
+ { id: "desktop", label: "Desktop", width: 1440 },
6
+ { id: "tablet", label: "Tablet", width: 834 },
7
+ { id: "mobile", label: "Mobile", width: 390 },
8
+ ] as const;
9
+
10
+ type ViewportId = (typeof VIEWPORTS)[number]["id"];
11
+
12
+ interface ComponentPreviewFrameProps {
13
+ notes?: string | undefined;
14
+ path: string;
15
+ }
16
+
17
+ export const ComponentPreviewFrame = ({
18
+ notes,
19
+ path,
20
+ }: ComponentPreviewFrameProps) => {
21
+ const stage = useRef<HTMLDivElement>(null);
22
+ const [box, setBox] = useState({ height: 0, width: 0 });
23
+ const [viewport, setViewport] = useState<ViewportId>("desktop");
24
+
25
+ useEffect(() => {
26
+ const element = stage.current;
27
+ if (!element) return;
28
+
29
+ const observer = new ResizeObserver(([entry]) => {
30
+ if (!entry) return;
31
+ const { height, width } = entry.contentRect;
32
+ setBox({ height, width });
33
+ });
34
+
35
+ observer.observe(element);
36
+ return () => observer.disconnect();
37
+ }, []);
38
+
39
+ const width = VIEWPORTS.find((item) => item.id === viewport)?.width ?? 1440;
40
+
41
+ const scale = box.width > 0 ? Math.min(1, box.width / width) : 1;
42
+ const height = box.height > 0 ? box.height / scale : 0;
43
+
44
+ return (
45
+ <Flex direction="column" height="fill">
46
+ <Card borderBottom padding={2} tone="transparent">
47
+ <Flex align="center" gap={2}>
48
+ <Flex gap={1}>
49
+ {VIEWPORTS.map((item) => (
50
+ <Button
51
+ fontSize={0}
52
+ key={item.id}
53
+ mode={item.id === viewport ? "default" : "bleed"}
54
+ onClick={() => setViewport(item.id)}
55
+ padding={2}
56
+ text={item.label}
57
+ tone={item.id === viewport ? "primary" : "default"}
58
+ />
59
+ ))}
60
+ </Flex>
61
+ <Box flex={1} />
62
+ <Code size={0}>
63
+ {width} × {Math.round(height)} · {Math.round(scale * 100)}%
64
+ </Code>
65
+ </Flex>
66
+ </Card>
67
+
68
+ {notes ? (
69
+ <Card borderBottom padding={3} tone="transparent">
70
+ <Text muted size={0}>
71
+ {notes}
72
+ </Text>
73
+ </Card>
74
+ ) : null}
75
+
76
+ <Box
77
+ flex={1}
78
+ style={{
79
+ background: "var(--card-muted-bg-color)",
80
+ overflow: "hidden",
81
+ position: "relative",
82
+ }}
83
+ >
84
+ <div
85
+ ref={stage}
86
+ style={{
87
+ display: "flex",
88
+ height: "100%",
89
+ justifyContent: "center",
90
+ width: "100%",
91
+ }}
92
+ >
93
+ {height > 0 ? (
94
+ <div
95
+ style={{
96
+ background: "var(--card-bg-color)",
97
+ border: "1px solid var(--card-border-color)",
98
+ borderRadius: 4,
99
+ boxShadow: "0 1px 12px rgb(0 0 0 / 0.12)",
100
+ flex: "none",
101
+ height: box.height,
102
+ overflow: "hidden",
103
+ width: width * scale,
104
+ }}
105
+ >
106
+ <iframe
107
+ src={path}
108
+ style={{
109
+ border: 0,
110
+ display: "block",
111
+ height,
112
+ transform: `scale(${scale})`,
113
+ transformOrigin: "top left",
114
+ width,
115
+ }}
116
+ title="Component preview"
117
+ />
118
+ </div>
119
+ ) : null}
120
+ </div>
121
+ </Box>
122
+ </Flex>
123
+ );
124
+ };
@@ -0,0 +1,25 @@
1
+ import { Card, Text } from "@sanity/ui";
2
+ import { ComponentPreviewFrame } from "./component-preview-frame";
3
+
4
+ interface ComponentPreviewPaneProps {
5
+ options?: Record<string, unknown>;
6
+ }
7
+
8
+ export const ComponentPreviewPane = ({
9
+ options,
10
+ }: ComponentPreviewPaneProps) => {
11
+ const path = options?.path as string | undefined;
12
+ const notes = options?.notes as string | undefined;
13
+
14
+ if (!path) {
15
+ return (
16
+ <Card padding={4}>
17
+ <Text muted size={1}>
18
+ This component has no preview route.
19
+ </Text>
20
+ </Card>
21
+ );
22
+ }
23
+
24
+ return <ComponentPreviewFrame notes={notes} path={path} />;
25
+ };
@@ -1,38 +1,18 @@
1
1
  import { type ArrayOfObjectsInputProps, useFormValue } from "sanity";
2
2
  import { PageBuilderInsertMenu } from "@/lib/integrations/sanity/components/page-builder-insert-menu";
3
- import { useStudioPreviewUrls } from "@/lib/integrations/sanity/components/use-studio-preview-urls";
4
3
  import {
5
- blockPreviewKey,
6
4
  getVisiblePageBuilderInsertMenuGroups,
7
5
  getVisiblePageBuilderMemberNames,
6
+ type PageBuilderDocumentType,
8
7
  type PageBuilderGroupName,
9
- pageBuilderReferenceMembers,
10
8
  } from "@/lib/integrations/sanity/page-builder-config";
11
9
 
12
- const BLOCK_PREVIEW_KEYS = pageBuilderReferenceMembers.map((member) =>
13
- blockPreviewKey(member.name),
14
- );
15
-
16
- function useInsertMenuViews() {
17
- const previewUrls = useStudioPreviewUrls(BLOCK_PREVIEW_KEYS);
18
-
19
- return [
20
- {
21
- name: "grid" as const,
22
- previewImageUrl: (schemaTypeName: string) =>
23
- previewUrls?.[blockPreviewKey(schemaTypeName)],
24
- },
25
- { name: "list" as const },
26
- ];
27
- }
28
-
29
10
  export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
30
11
  const pageTypeValue = useFormValue(["type"]);
31
12
  const pageType =
32
13
  typeof pageTypeValue === "string"
33
14
  ? (pageTypeValue as PageBuilderGroupName)
34
15
  : undefined;
35
- const views = useInsertMenuViews();
36
16
  const visibleMemberNames = getVisiblePageBuilderMemberNames(pageType);
37
17
 
38
18
  return props.renderDefault({
@@ -40,17 +20,14 @@ export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
40
20
  arrayFunctions: PageBuilderInsertMenu,
41
21
  schemaType: {
42
22
  ...props.schemaType,
43
- of: props.schemaType.of.filter(
44
- (member) =>
45
- typeof member.name === "string" &&
46
- visibleMemberNames.has(member.name),
23
+ of: props.schemaType.of.filter((member) =>
24
+ visibleMemberNames.has(member.name as PageBuilderDocumentType),
47
25
  ),
48
26
  options: {
49
27
  ...props.schemaType.options,
50
28
  insertMenu: {
51
29
  ...props.schemaType.options?.insertMenu,
52
30
  groups: getVisiblePageBuilderInsertMenuGroups(pageType),
53
- views,
54
31
  },
55
32
  },
56
33
  },