camox 0.0.0 → 0.1.2-alpha.2

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 (255) hide show
  1. package/LICENSE.md +110 -0
  2. package/dist/components/AuthGate.d.ts +7 -0
  3. package/dist/components/AuthGate.d.ts.map +1 -0
  4. package/dist/core/components/AddBlockControlBar.d.ts +9 -0
  5. package/dist/core/components/AddBlockControlBar.d.ts.map +1 -0
  6. package/dist/core/components/AddBlockControlBar.js +65 -0
  7. package/dist/core/components/lexical/InlineContentEditable.d.ts +7 -0
  8. package/dist/core/components/lexical/InlineContentEditable.d.ts.map +1 -0
  9. package/dist/core/components/lexical/InlineContentEditable.js +40 -0
  10. package/dist/core/components/lexical/InlineLexicalEditor.d.ts +12 -0
  11. package/dist/core/components/lexical/InlineLexicalEditor.d.ts.map +1 -0
  12. package/dist/core/components/lexical/InlineLexicalEditor.js +133 -0
  13. package/dist/core/components/lexical/InlineParagraphNode.d.ts +10 -0
  14. package/dist/core/components/lexical/InlineParagraphNode.d.ts.map +1 -0
  15. package/dist/core/components/lexical/InlineParagraphNode.js +34 -0
  16. package/dist/core/components/lexical/SelectionBroadcaster.d.ts +6 -0
  17. package/dist/core/components/lexical/SelectionBroadcaster.d.ts.map +1 -0
  18. package/dist/core/components/lexical/SelectionBroadcaster.js +62 -0
  19. package/dist/core/components/lexical/SidebarLexicalEditor.d.ts +9 -0
  20. package/dist/core/components/lexical/SidebarLexicalEditor.d.ts.map +1 -0
  21. package/dist/core/components/lexical/editorConfig.d.ts +4 -0
  22. package/dist/core/components/lexical/editorConfig.d.ts.map +1 -0
  23. package/dist/core/components/lexical/editorConfig.js +24 -0
  24. package/dist/core/createApp.d.ts +373 -0
  25. package/dist/core/createApp.d.ts.map +1 -0
  26. package/dist/core/createApp.js +40 -0
  27. package/dist/core/createBlock.d.ts +947 -0
  28. package/dist/core/createBlock.d.ts.map +1 -0
  29. package/dist/core/createBlock.js +873 -0
  30. package/dist/core/createLayout.d.ts +78 -0
  31. package/dist/core/createLayout.d.ts.map +1 -0
  32. package/dist/core/createLayout.js +73 -0
  33. package/dist/core/hooks/useFieldSelection.d.ts +11 -0
  34. package/dist/core/hooks/useFieldSelection.d.ts.map +1 -0
  35. package/dist/core/hooks/useFieldSelection.js +25 -0
  36. package/dist/core/hooks/useIsEditable.d.ts +2 -0
  37. package/dist/core/hooks/useIsEditable.d.ts.map +1 -0
  38. package/dist/core/hooks/useIsEditable.js +12 -0
  39. package/dist/core/hooks/useOverlayMessage.d.ts +10 -0
  40. package/dist/core/hooks/useOverlayMessage.d.ts.map +1 -0
  41. package/dist/core/hooks/useOverlayMessage.js +37 -0
  42. package/dist/core/lib/contentType.d.ts +165 -0
  43. package/dist/core/lib/contentType.d.ts.map +1 -0
  44. package/dist/core/lib/contentType.js +148 -0
  45. package/dist/core/lib/fieldTypes.d.ts +85 -0
  46. package/dist/core/lib/fieldTypes.d.ts.map +1 -0
  47. package/dist/core/lib/lexicalReact.d.ts +3 -0
  48. package/dist/core/lib/lexicalReact.d.ts.map +1 -0
  49. package/dist/core/lib/lexicalReact.js +24 -0
  50. package/dist/core/lib/lexicalState.d.ts +10 -0
  51. package/dist/core/lib/lexicalState.d.ts.map +1 -0
  52. package/dist/core/lib/lexicalState.js +38 -0
  53. package/dist/core/lib/modifierFormats.d.ts +9 -0
  54. package/dist/core/lib/modifierFormats.d.ts.map +1 -0
  55. package/dist/core/lib/modifierFormats.js +8 -0
  56. package/dist/core/lib/modifiers.d.ts +12 -0
  57. package/dist/core/lib/modifiers.d.ts.map +1 -0
  58. package/dist/core/lib/modifiers.js +27 -0
  59. package/dist/features/content/CamoxContent.d.ts +2 -0
  60. package/dist/features/content/CamoxContent.d.ts.map +1 -0
  61. package/dist/features/content/CamoxContent.js +100 -0
  62. package/dist/features/content/components/AssetCard.d.ts +10 -0
  63. package/dist/features/content/components/AssetCard.d.ts.map +1 -0
  64. package/dist/features/content/components/AssetCard.js +41 -0
  65. package/dist/features/content/components/AssetCardSkeleton.d.ts +2 -0
  66. package/dist/features/content/components/AssetCardSkeleton.d.ts.map +1 -0
  67. package/dist/features/content/components/AssetCardSkeleton.js +11 -0
  68. package/dist/features/content/components/ContentSidebar.d.ts +2 -0
  69. package/dist/features/content/components/ContentSidebar.d.ts.map +1 -0
  70. package/dist/features/content/components/ContentSidebar.js +15 -0
  71. package/dist/features/content/components/UploadDropZone.d.ts +9 -0
  72. package/dist/features/content/components/UploadDropZone.d.ts.map +1 -0
  73. package/dist/features/content/components/UploadDropZone.js +51 -0
  74. package/dist/features/content/components/UploadProgressDrawer.d.ts +11 -0
  75. package/dist/features/content/components/UploadProgressDrawer.d.ts.map +1 -0
  76. package/dist/features/content/components/UploadProgressDrawer.js +72 -0
  77. package/dist/features/preview/CamoxPreview.d.ts +124 -0
  78. package/dist/features/preview/CamoxPreview.d.ts.map +1 -0
  79. package/dist/features/preview/CamoxPreview.js +253 -0
  80. package/dist/features/preview/components/AddBlockSheet.d.ts +3 -0
  81. package/dist/features/preview/components/AddBlockSheet.d.ts.map +1 -0
  82. package/dist/features/preview/components/AddBlockSheet.js +121 -0
  83. package/dist/features/preview/components/AgentChatSheet.d.ts +3 -0
  84. package/dist/features/preview/components/AgentChatSheet.d.ts.map +1 -0
  85. package/dist/features/preview/components/AgentChatSheet.js +24 -0
  86. package/dist/features/preview/components/AssetFieldEditor.d.ts +18 -0
  87. package/dist/features/preview/components/AssetFieldEditor.d.ts.map +1 -0
  88. package/dist/features/preview/components/AssetFieldEditor.js +139 -0
  89. package/dist/features/preview/components/AssetLightbox.d.ts +9 -0
  90. package/dist/features/preview/components/AssetLightbox.d.ts.map +1 -0
  91. package/dist/features/preview/components/AssetLightbox.js +421 -0
  92. package/dist/features/preview/components/AssetPickerGrid.d.ts +11 -0
  93. package/dist/features/preview/components/AssetPickerGrid.d.ts.map +1 -0
  94. package/dist/features/preview/components/AssetPickerGrid.js +92 -0
  95. package/dist/features/preview/components/BlockActionsPopover.d.ts +15 -0
  96. package/dist/features/preview/components/BlockActionsPopover.d.ts.map +1 -0
  97. package/dist/features/preview/components/BlockActionsPopover.js +506 -0
  98. package/dist/features/preview/components/CreatePageSheet.d.ts +3 -0
  99. package/dist/features/preview/components/CreatePageSheet.d.ts.map +1 -0
  100. package/dist/features/preview/components/CreatePageSheet.js +159 -0
  101. package/dist/features/preview/components/DebouncedFieldEditor.d.ts +10 -0
  102. package/dist/features/preview/components/DebouncedFieldEditor.d.ts.map +1 -0
  103. package/dist/features/preview/components/DebouncedFieldEditor.js +51 -0
  104. package/dist/features/preview/components/EditPageSheet.d.ts +3 -0
  105. package/dist/features/preview/components/EditPageSheet.d.ts.map +1 -0
  106. package/dist/features/preview/components/EditPageSheet.js +352 -0
  107. package/dist/features/preview/components/ItemFieldsEditor.d.ts +29 -0
  108. package/dist/features/preview/components/ItemFieldsEditor.d.ts.map +1 -0
  109. package/dist/features/preview/components/ItemFieldsEditor.js +308 -0
  110. package/dist/features/preview/components/LinkFieldEditor.d.ts +8 -0
  111. package/dist/features/preview/components/LinkFieldEditor.d.ts.map +1 -0
  112. package/dist/features/preview/components/LinkFieldEditor.js +190 -0
  113. package/dist/features/preview/components/MultipleAssetFieldEditor.d.ts +10 -0
  114. package/dist/features/preview/components/MultipleAssetFieldEditor.d.ts.map +1 -0
  115. package/dist/features/preview/components/MultipleAssetFieldEditor.js +232 -0
  116. package/dist/features/preview/components/OverlayTracker.d.ts +6 -0
  117. package/dist/features/preview/components/OverlayTracker.d.ts.map +1 -0
  118. package/dist/features/preview/components/OverlayTracker.js +41 -0
  119. package/dist/features/preview/components/Overlays.d.ts +6 -0
  120. package/dist/features/preview/components/Overlays.d.ts.map +1 -0
  121. package/dist/features/preview/components/Overlays.js +58 -0
  122. package/dist/features/preview/components/PageContentSheet.d.ts +3 -0
  123. package/dist/features/preview/components/PageContentSheet.d.ts.map +1 -0
  124. package/dist/features/preview/components/PageContentSheet.js +492 -0
  125. package/dist/features/preview/components/PageLocationFieldset.d.ts +14 -0
  126. package/dist/features/preview/components/PageLocationFieldset.d.ts.map +1 -0
  127. package/dist/features/preview/components/PageLocationFieldset.js +77 -0
  128. package/dist/features/preview/components/PagePicker.d.ts +3 -0
  129. package/dist/features/preview/components/PagePicker.d.ts.map +1 -0
  130. package/dist/features/preview/components/PagePicker.js +185 -0
  131. package/dist/features/preview/components/PageTree.d.ts +3 -0
  132. package/dist/features/preview/components/PageTree.d.ts.map +1 -0
  133. package/dist/features/preview/components/PageTree.js +410 -0
  134. package/dist/features/preview/components/PeekedBlock.d.ts +6 -0
  135. package/dist/features/preview/components/PeekedBlock.d.ts.map +1 -0
  136. package/dist/features/preview/components/PeekedBlock.js +95 -0
  137. package/dist/features/preview/components/PreviewPanel.d.ts +12 -0
  138. package/dist/features/preview/components/PreviewPanel.d.ts.map +1 -0
  139. package/dist/features/preview/components/PreviewPanel.js +192 -0
  140. package/dist/features/preview/components/PreviewSideSheet.d.ts +13 -0
  141. package/dist/features/preview/components/PreviewSideSheet.d.ts.map +1 -0
  142. package/dist/features/preview/components/PreviewSideSheet.js +28 -0
  143. package/dist/features/preview/components/PreviewToolbar.d.ts +2 -0
  144. package/dist/features/preview/components/PreviewToolbar.d.ts.map +1 -0
  145. package/dist/features/preview/components/PreviewToolbar.js +79 -0
  146. package/dist/features/preview/components/RepeatableItemsList.d.ts +14 -0
  147. package/dist/features/preview/components/RepeatableItemsList.d.ts.map +1 -0
  148. package/dist/features/preview/components/RepeatableItemsList.js +366 -0
  149. package/dist/features/preview/components/ShikiMarkdown.d.ts +4 -0
  150. package/dist/features/preview/components/ShikiMarkdown.d.ts.map +1 -0
  151. package/dist/features/preview/components/ShikiMarkdown.js +37 -0
  152. package/dist/features/preview/components/TextFormatToolbar.d.ts +2 -0
  153. package/dist/features/preview/components/TextFormatToolbar.d.ts.map +1 -0
  154. package/dist/features/preview/components/TextFormatToolbar.js +73 -0
  155. package/dist/features/preview/components/UnlinkAssetButton.d.ts +9 -0
  156. package/dist/features/preview/components/UnlinkAssetButton.d.ts.map +1 -0
  157. package/dist/features/preview/components/UnlinkAssetButton.js +55 -0
  158. package/dist/features/preview/overlayConstants.d.ts +19 -0
  159. package/dist/features/preview/overlayConstants.d.ts.map +1 -0
  160. package/dist/features/preview/overlayConstants.js +21 -0
  161. package/dist/features/preview/overlayMessages.d.ts +62 -0
  162. package/dist/features/preview/overlayMessages.d.ts.map +1 -0
  163. package/dist/features/preview/overlayMessages.js +9 -0
  164. package/dist/features/preview/previewConstants.d.ts +2 -0
  165. package/dist/features/preview/previewConstants.d.ts.map +1 -0
  166. package/dist/features/preview/previewStore.d.ts +116 -0
  167. package/dist/features/preview/previewStore.d.ts.map +1 -0
  168. package/dist/features/preview/previewStore.js +321 -0
  169. package/dist/features/provider/CamoxProvider.d.ts +11 -0
  170. package/dist/features/provider/CamoxProvider.d.ts.map +1 -0
  171. package/dist/features/provider/CamoxProvider.js +73 -0
  172. package/dist/features/provider/actionsStore.d.ts +39 -0
  173. package/dist/features/provider/actionsStore.d.ts.map +1 -0
  174. package/dist/features/provider/actionsStore.js +35 -0
  175. package/dist/features/provider/components/CamoxAppContext.d.ts +371 -0
  176. package/dist/features/provider/components/CamoxAppContext.d.ts.map +1 -0
  177. package/dist/features/provider/components/CamoxAppContext.js +17 -0
  178. package/dist/features/provider/components/CommandPalette.d.ts +3 -0
  179. package/dist/features/provider/components/CommandPalette.d.ts.map +1 -0
  180. package/dist/features/provider/components/CommandPalette.js +127 -0
  181. package/dist/features/provider/useAdminShortcuts.d.ts +5 -0
  182. package/dist/features/provider/useAdminShortcuts.d.ts.map +1 -0
  183. package/dist/features/provider/useAdminShortcuts.js +83 -0
  184. package/dist/features/routes/ogRoute.d.ts +7 -0
  185. package/dist/features/routes/ogRoute.d.ts.map +1 -0
  186. package/dist/features/routes/ogRoute.js +19 -0
  187. package/dist/features/routes/pageRoute.d.ts +135 -0
  188. package/dist/features/routes/pageRoute.d.ts.map +1 -0
  189. package/dist/features/routes/pageRoute.js +112 -0
  190. package/dist/features/studio/CamoxStudio.d.ts +7 -0
  191. package/dist/features/studio/CamoxStudio.d.ts.map +1 -0
  192. package/dist/features/studio/CamoxStudio.js +24 -0
  193. package/dist/features/studio/components/Navbar.d.ts +4 -0
  194. package/dist/features/studio/components/Navbar.d.ts.map +1 -0
  195. package/dist/features/studio/components/Navbar.js +95 -0
  196. package/dist/features/studio/components/ProjectMenu.d.ts +2 -0
  197. package/dist/features/studio/components/ProjectMenu.d.ts.map +1 -0
  198. package/dist/features/studio/components/ProjectMenu.js +132 -0
  199. package/dist/features/studio/components/UserButton.d.ts +2 -0
  200. package/dist/features/studio/components/UserButton.d.ts.map +1 -0
  201. package/dist/features/studio/components/UserButton.js +96 -0
  202. package/dist/features/studio/studioStore.d.ts +17 -0
  203. package/dist/features/studio/studioStore.d.ts.map +1 -0
  204. package/dist/features/studio/studioStore.js +44 -0
  205. package/dist/features/studio/useTheme.d.ts +9 -0
  206. package/dist/features/studio/useTheme.d.ts.map +1 -0
  207. package/dist/features/studio/useTheme.js +98 -0
  208. package/dist/features/vite/appGeneration.d.ts +4 -0
  209. package/dist/features/vite/appGeneration.d.ts.map +1 -0
  210. package/dist/features/vite/appGeneration.js +67 -0
  211. package/dist/features/vite/blockBoilerplate.d.ts +3 -0
  212. package/dist/features/vite/blockBoilerplate.d.ts.map +1 -0
  213. package/dist/features/vite/blockBoilerplate.js +59 -0
  214. package/dist/features/vite/convexSync.d.ts +6 -0
  215. package/dist/features/vite/convexSync.d.ts.map +1 -0
  216. package/dist/features/vite/convexSync.js +98 -0
  217. package/dist/features/vite/definitionsSync.d.ts +11 -0
  218. package/dist/features/vite/definitionsSync.d.ts.map +1 -0
  219. package/dist/features/vite/definitionsSync.js +157 -0
  220. package/dist/features/vite/routeGeneration.d.ts +4 -0
  221. package/dist/features/vite/routeGeneration.d.ts.map +1 -0
  222. package/dist/features/vite/routeGeneration.js +194 -0
  223. package/dist/features/vite/skillGeneration.d.ts +4 -0
  224. package/dist/features/vite/skillGeneration.d.ts.map +1 -0
  225. package/dist/features/vite/skillGeneration.js +69 -0
  226. package/dist/features/vite/utils.d.ts +2 -0
  227. package/dist/features/vite/utils.d.ts.map +1 -0
  228. package/dist/features/vite/utils.js +10 -0
  229. package/dist/features/vite/vite.d.ts +18 -0
  230. package/dist/features/vite/vite.d.ts.map +1 -0
  231. package/dist/features/vite/vite.js +77 -0
  232. package/dist/hooks/use-file-upload.d.ts +22 -0
  233. package/dist/hooks/use-file-upload.d.ts.map +1 -0
  234. package/dist/hooks/use-marquee-selection.d.ts +17 -0
  235. package/dist/hooks/use-marquee-selection.d.ts.map +1 -0
  236. package/dist/lib/analytics-client.d.ts +3 -0
  237. package/dist/lib/analytics-client.d.ts.map +1 -0
  238. package/dist/lib/analytics.d.ts +3 -0
  239. package/dist/lib/analytics.d.ts.map +1 -0
  240. package/dist/lib/analytics.js +24 -0
  241. package/dist/lib/auth.d.ts +3683 -0
  242. package/dist/lib/auth.d.ts.map +1 -0
  243. package/dist/lib/convex-site.d.ts +3 -0
  244. package/dist/lib/convex-site.d.ts.map +1 -0
  245. package/dist/lib/utils.d.ts +40 -0
  246. package/dist/lib/utils.d.ts.map +1 -0
  247. package/dist/studio.css +2 -0
  248. package/package.json +123 -10
  249. package/server/api.d.ts +1 -0
  250. package/server/api.js +1 -0
  251. package/server/dataModel.d.ts +1 -0
  252. package/server/dataModel.js +1 -0
  253. package/skills/camox-block/SKILL.md +357 -0
  254. package/skills/camox-layout/SKILL.md +181 -0
  255. package/index.js +0 -3
@@ -0,0 +1,357 @@
1
+ ---
2
+ name: camox-block
3
+ description: "How to create Camox block definition files. Use this skill whenever the user wants to create a new block for their Camox website, add a page section/component, build a reusable content block, or asks about the block definition API. Trigger on mentions of blocks, sections, page components, content types, or any request to add new visual sections to a Camox site — even if they don't say 'block' explicitly."
4
+ ---
5
+
6
+ # Creating Camox Block Definitions
7
+
8
+ A block is a reusable page section (hero, testimonial, gallery, footer...). Users compose pages by assembling blocks. This skill covers creating block **definitions** — the template that describes a block's schema and rendering. Not the content (an instance of a block).
9
+
10
+ ## Quick Start
11
+
12
+ A block file lives in the app's `src/camox/blocks/` folder, is a `.tsx` file, and exports `block`:
13
+
14
+ ```tsx
15
+ import { Type, createBlock } from "camox/createBlock";
16
+
17
+ const myBlock = createBlock({
18
+ id: "my-block", // Must match filename (kebab-case)
19
+ title: "My Block", // Human-readable name
20
+ description: "...", // Tells the AI when/how to use this block
21
+ toMarkdown: ["# {{title}}", "{{description}}"], // Markdown template
22
+ content: {
23
+ /* ... */
24
+ }, // Editable content schema
25
+ settings: {
26
+ /* ... */
27
+ }, // Optional config toggles
28
+ component: MyBlockComponent,
29
+ });
30
+
31
+ function MyBlockComponent() {
32
+ return (
33
+ <section>
34
+ <myBlock.Field name="title">{(content) => <h1>{content}</h1>}</myBlock.Field>
35
+ </section>
36
+ );
37
+ }
38
+
39
+ export { myBlock as block };
40
+ ```
41
+
42
+ ## The `createBlock` options
43
+
44
+ | Option | Required | Description |
45
+ | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | `id` | yes | Unique kebab-case identifier. Must match the filename without extension. |
47
+ | `title` | yes | Display name shown in the CMS UI. |
48
+ | `description` | yes | Tells the AI assistant when to use this block and what content it expects. Write it like guidance for an LLM — be specific about placement, tone, and content guidelines. |
49
+ | `toMarkdown` | yes | A `string[]` template for rendering block content as markdown. Each line is joined with `\n\n`. Use `{{fieldName}}` placeholders for field values. Lines where all placeholders resolve to empty are omitted. |
50
+ | `content` | yes | An object where each key is a field name and each value is a `Type.*` call. These fields are inline-editable in the CMS. |
51
+ | `settings` | no | Same shape as `content`, but for configuration that lives in a settings panel (not inline). Only `Type.Enum` and `Type.Boolean` should be used here. |
52
+ | `layoutOnly` | no | If `true`, the block won't appear in the "add block" sheet — it can only be placed inside layouts (e.g. navbar, footer). |
53
+ | `component` | yes | A named React function component that renders the block. |
54
+
55
+ ## Markdown Template (`toMarkdown`)
56
+
57
+ The `toMarkdown` array controls how block content is rendered as markdown for AI features (summaries, SEO). Each string in the array becomes a paragraph (joined with `\n\n`). Use `{{fieldName}}` placeholders that reference keys in `content`.
58
+
59
+ **Field resolution rules:**
60
+
61
+ - **String**: raw text value
62
+ - **Link**: `[text](href)`
63
+ - **Image**: `![alt](filename)`
64
+ - **File**: `[filename](url)`
65
+ - **Embed**: raw URL string
66
+ - **RepeatableObject**: each item rendered via its own `toMarkdown` (if set on the RepeatableObject options), items joined with `\n\n`
67
+ - **Boolean/Enum**: raw string value
68
+
69
+ Lines where ALL placeholders resolve to empty are omitted from output.
70
+
71
+ **Examples:**
72
+
73
+ ```tsx
74
+ // Hero block
75
+ createBlock({
76
+ toMarkdown: ["# {{title}}", "{{description}}", "{{illustration}}", "{{cta}}"],
77
+ content: { ... },
78
+ })
79
+
80
+ // Testimonial — combine fields on one line
81
+ createBlock({
82
+ toMarkdown: ["> {{quote}}", "— {{author}}, {{title}}, {{company}}"],
83
+ content: { ... },
84
+ })
85
+
86
+ // Statistics — RepeatableObject with its own toMarkdown
87
+ createBlock({
88
+ toMarkdown: ["## {{subtitle}}", "{{description}}", "{{statistics}}"],
89
+ content: {
90
+ subtitle: Type.String({ default: "..." }),
91
+ description: Type.String({ default: "..." }),
92
+ statistics: Type.RepeatableObject(
93
+ {
94
+ number: Type.String({ default: "100M+" }),
95
+ label: Type.String({ default: "pages served" }),
96
+ },
97
+ { minItems: 4, maxItems: 8, toMarkdown: ["**{{number}}** — {{label}}"] }
98
+ ),
99
+ },
100
+ })
101
+ ```
102
+
103
+ The template is type-safe — `{{fieldName}}` placeholders are validated against the content keys at compile time. Typos like `{{titl}}` produce a TypeScript error.
104
+
105
+ ## Content Field Types
106
+
107
+ Import `Type` from `"camox/createBlock"`. Every field requires a default value.
108
+
109
+ ### Type.String
110
+
111
+ Inline-editable text. The workhorse field type.
112
+
113
+ ```tsx
114
+ Type.String({
115
+ default: "Hello world", // Required
116
+ title: "Heading", // Optional label
117
+ maxLength: 280, // Optional
118
+ minLength: 1, // Optional
119
+ pattern: "^[A-Z]", // Optional regex
120
+ });
121
+ ```
122
+
123
+ ### Type.Boolean
124
+
125
+ A toggle. Use in `settings` for config, or in `content` for user-controlled flags.
126
+
127
+ ```tsx
128
+ Type.Boolean({ default: false, title: "Show background" });
129
+ ```
130
+
131
+ ### Type.Enum
132
+
133
+ A dropdown with predefined options. Most commonly used in `settings`.
134
+
135
+ ```tsx
136
+ Type.Enum({
137
+ default: "left",
138
+ options: { left: "Left", center: "Center", right: "Right" },
139
+ title: "Alignment",
140
+ });
141
+ ```
142
+
143
+ The `default` must be one of the keys in `options`.
144
+
145
+ ### Type.Link
146
+
147
+ A link with text, URL (or internal page reference), and new-tab toggle.
148
+
149
+ ```tsx
150
+ Type.Link({
151
+ default: { text: "Learn more", href: "/", newTab: false },
152
+ title: "CTA",
153
+ });
154
+ ```
155
+
156
+ ### Type.Image
157
+
158
+ A single image, or a repeatable array of images.
159
+
160
+ ```tsx
161
+ // Single image
162
+ Type.Image({ title: "Cover photo" });
163
+
164
+ // Multiple images (creates a repeatable list)
165
+ Type.Image({ multiple: true, defaultItems: 6, title: "Gallery images" });
166
+ ```
167
+
168
+ ### Type.File
169
+
170
+ A file upload, with MIME type filtering.
171
+
172
+ ```tsx
173
+ Type.File({
174
+ accept: ["application/pdf"],
175
+ title: "PDF Document",
176
+ });
177
+
178
+ // Multiple files
179
+ Type.File({
180
+ accept: ["application/pdf"],
181
+ multiple: true,
182
+ defaultItems: 0,
183
+ title: "Documents",
184
+ });
185
+ ```
186
+
187
+ ### Type.Embed
188
+
189
+ A URL validated against a regex pattern. Used for embedding external content.
190
+
191
+ ```tsx
192
+ Type.Embed({
193
+ pattern: "https:\\/\\/(www\\.)?(youtube\\.com|youtu\\.be)\\/.+",
194
+ default: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
195
+ title: "YouTube URL",
196
+ });
197
+ ```
198
+
199
+ The `default` must match the `pattern` — an error is thrown at definition time otherwise.
200
+
201
+ ### Type.RepeatableObject
202
+
203
+ An array of structured items. Each item is an object with its own fields. This is how you create lists of things (testimonials, features, stats, links...).
204
+
205
+ ```tsx
206
+ Type.RepeatableObject(
207
+ {
208
+ name: Type.String({ default: "Feature" }),
209
+ description: Type.String({ default: "Description" }),
210
+ },
211
+ {
212
+ minItems: 1, // Must be >= 1
213
+ maxItems: 10,
214
+ title: "Features",
215
+ toMarkdown: ["### {{name}}", "{{description}}"], // Optional markdown template for each item
216
+ },
217
+ );
218
+ ```
219
+
220
+ The `toMarkdown` option on RepeatableObject defines how each item is rendered as markdown when the parent block's `toMarkdown` references this field. If omitted, fields are joined with " — " as a fallback.
221
+
222
+ RepeatableObjects can be nested — an item can contain another RepeatableObject:
223
+
224
+ ```tsx
225
+ columns: Type.RepeatableObject(
226
+ {
227
+ title: Type.String({ default: "Column" }),
228
+ links: Type.RepeatableObject(
229
+ {
230
+ link: Type.Link({ default: { text: "Link", href: "#", newTab: false } }),
231
+ },
232
+ { minItems: 1, maxItems: 999, toMarkdown: ["{{link}}"] },
233
+ ),
234
+ },
235
+ { minItems: 2, maxItems: 4, title: "Columns", toMarkdown: ["### {{title}}", "{{links}}"] },
236
+ );
237
+ ```
238
+
239
+ ## Rendering in the Component
240
+
241
+ The component is a regular React function. It uses methods on the block constant to render each field. The pattern is always: use the appropriate renderer for each field type, with a render-prop child that receives the field value.
242
+
243
+ ### Rendering String fields — `block.Field`
244
+
245
+ ```tsx
246
+ <myBlock.Field name="title">{(content) => <h1>{content}</h1>}</myBlock.Field>
247
+ ```
248
+
249
+ The `name` must match a key in `content` that is a `Type.String`. The `content` argument is a string. This is what makes the field inline-editable in the CMS.
250
+
251
+ ### Rendering Link fields — `block.Link`
252
+
253
+ Inside the render prop, use the `Link` component from `@tanstack/react-router` instead of a plain `<a>` tag. This enables client-side navigation for internal links.
254
+
255
+ ```tsx
256
+ import { Link } from "@tanstack/react-router";
257
+
258
+ <myBlock.Link name="cta">
259
+ {({ text, href, newTab }) => (
260
+ <Link to={href} target={newTab ? "_blank" : undefined} rel={newTab ? "noreferrer" : undefined}>
261
+ {text}
262
+ </Link>
263
+ )}
264
+ </myBlock.Link>;
265
+ ```
266
+
267
+ ### Rendering Image fields — `block.Image`
268
+
269
+ ```tsx
270
+ <myBlock.Image name="cover">{(img) => <img src={img.url} alt={img.alt} />}</myBlock.Image>
271
+ ```
272
+
273
+ ### Rendering File fields — `block.File`
274
+
275
+ ```tsx
276
+ <myBlock.File name="document">
277
+ {(file) => (
278
+ <a href={file.url} download={file.filename}>
279
+ Download
280
+ </a>
281
+ )}
282
+ </myBlock.File>
283
+ ```
284
+
285
+ ### Rendering Embed fields — `block.Embed`
286
+
287
+ ```tsx
288
+ <myBlock.Embed name="videoUrl">{(url) => <iframe src={url} />}</myBlock.Embed>
289
+ ```
290
+
291
+ ### Rendering RepeatableObject fields — `block.Repeater`
292
+
293
+ ```tsx
294
+ <myBlock.Repeater name="features">
295
+ {(item) => (
296
+ <div>
297
+ <item.Field name="name">{(content) => <h3>{content}</h3>}</item.Field>
298
+ <item.Field name="description">{(content) => <p>{content}</p>}</item.Field>
299
+ </div>
300
+ )}
301
+ </myBlock.Repeater>
302
+ ```
303
+
304
+ Inside a Repeater, the `item` callback argument exposes the same `.Field`, `.Link`, `.Image`, `.File`, `.Embed`, and `.Repeater` methods — scoped to that item. This is how nested repeaters work too:
305
+
306
+ ```tsx
307
+ <footer.Repeater name="columns">
308
+ {(column) => (
309
+ <column.Repeater name="links">
310
+ {(linkItem) => (
311
+ <linkItem.Link name="link">{({ text, href }) => <a href={href}>{text}</a>}</linkItem.Link>
312
+ )}
313
+ </column.Repeater>
314
+ )}
315
+ </footer.Repeater>
316
+ ```
317
+
318
+ For `Type.Image({ multiple: true })`, the repeater item has a single `image` key:
319
+
320
+ ```tsx
321
+ <gallery.Repeater name="images">
322
+ {(item) => <item.Image name="image">{(img) => <img src={img.url} alt={img.alt} />}</item.Image>}
323
+ </gallery.Repeater>
324
+ ```
325
+
326
+ ### Reading settings — `block.useSetting`
327
+
328
+ ```tsx
329
+ function MyComponent() {
330
+ const theme = myBlock.useSetting("theme");
331
+ const compact = myBlock.useSetting("compact");
332
+ // Use these values in your JSX for conditional rendering/styling
333
+ }
334
+ ```
335
+
336
+ ### Detached rendering — `block.Detached`
337
+
338
+ Renders content outside the block's DOM container. Useful for fixed/floating elements like sticky navbars or modals.
339
+
340
+ ```tsx
341
+ <myBlock.Detached>
342
+ <div className="fixed top-0 left-0 right-0 z-50">{/* floating content */}</div>
343
+ </myBlock.Detached>
344
+ ```
345
+
346
+ ## Rules and Conventions
347
+
348
+ 1. **File = one block.** One `.tsx` file per block in `src/camox/blocks/`. The `id` must match the filename (without `.tsx`).
349
+ 2. **Named export as `block`.** Always: `export { myVar as block }`. Not a default export.
350
+ 3. **Named function component.** Use `function MyComponent()`, not an arrow function. Reference it in `createBlock` before its declaration is fine (hoisting).
351
+ 4. **All fields need defaults.** Every `Type.*` call requires a default value (images and files get automatic placeholders).
352
+ 5. **Description is for the AI.** Write the `description` as guidance for an LLM — explain when to use this block, what kind of content it's for, and where it fits on a page.
353
+ 6. **`toMarkdown` is required.** Every block must define how its content renders as markdown. Use `{{fieldName}}` placeholders matching content keys.
354
+ 7. **Settings = Enum and Boolean only.** Keep settings simple. Use `content` for everything the user edits inline.
355
+ 8. **RepeatableObject minItems >= 1.** You can't have an empty repeatable — there's always at least one item.
356
+ 9. **Import path is `"camox/createBlock"`.** Both `Type` and `createBlock` come from this import.
357
+ 10. **Use Tailwind CSS for styling.** All example blocks use Tailwind utility classes. Follow the same patterns: `container mx-auto px-4` for centered content, responsive breakpoints, etc.
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: camox-layout
3
+ description: "How to create and edit Camox layout definition files. Use this skill whenever the user wants to create a new layout for their Camox website, modify an existing layout, wrap pages in shared structure (navbar + footer), customize meta titles or OG images, or asks about the layout definition API. Trigger on mentions of layouts, page wrappers, page templates, shared page structure, navbar/footer placement, or any request to define how pages are structured — even if they don't say 'layout' explicitly."
4
+ ---
5
+
6
+ # Creating Camox Layout Definitions
7
+
8
+ A layout wraps pages in shared structure — a navbar at the top, a footer at the bottom, consistent styling. Each page in the CMS is assigned a layout. This skill covers creating layout **definitions** — the template that describes which blocks surround page content and how to render them.
9
+
10
+ ## Quick Start
11
+
12
+ A layout file lives in the app's `src/camox/layouts/` folder, is a `.tsx` file, and exports `layout`:
13
+
14
+ ```tsx
15
+ import { createLayout } from "camox/createLayout";
16
+ import { block as navbarBlock } from "../blocks/navbar";
17
+ import { block as footerBlock } from "../blocks/footer";
18
+
19
+ const myLayout = createLayout({
20
+ id: "my-layout", // Must match filename (kebab-case)
21
+ title: "My Layout", // Human-readable name
22
+ description: "When to use this layout",
23
+ blocks: {
24
+ before: [navbarBlock], // Blocks rendered before page content
25
+ after: [footerBlock], // Blocks rendered after page content
26
+ },
27
+ component: MyLayoutComponent,
28
+ buildMetaTitle: ({ pageMetaTitle, projectName }) => `${pageMetaTitle} | ${projectName}`,
29
+ });
30
+
31
+ function MyLayoutComponent({ children }: { children: React.ReactNode }) {
32
+ return (
33
+ <main className="flex min-h-screen flex-col">
34
+ <myLayout.blocks.Navbar />
35
+ <div className="flex-1">{children}</div>
36
+ <myLayout.blocks.Footer />
37
+ </main>
38
+ );
39
+ }
40
+
41
+ export { myLayout as layout };
42
+ ```
43
+
44
+ ## The `createLayout` options
45
+
46
+ | Option | Required | Description |
47
+ | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `id` | yes | Unique kebab-case identifier. Must match the filename without extension. |
49
+ | `title` | yes | Display name shown in the CMS UI. |
50
+ | `description` | yes | Tells the CMS user (or AI agent) when to pick this layout. Write it as guidance — explain what kind of pages this layout suits. |
51
+ | `blocks` | yes | An object with `before` and `after` arrays. Each array contains block instances (imported from block files). `before` blocks render above the page content, `after` blocks render below it. |
52
+ | `component` | yes | A named React function component that renders the layout shell. Receives `{ children }` — the page content. |
53
+ | `buildMetaTitle` | yes | A function that builds the `<title>` tag. Receives `{ pageMetaTitle, projectName, pageFullPath }` and returns a string. |
54
+ | `buildOgImage` | no | A function that returns a JSX element for generating Open Graph images. Receives `{ title, description, projectName }`. |
55
+
56
+ ## Block Placement — `before` and `after`
57
+
58
+ Layout blocks are split into two groups:
59
+
60
+ - **`before`**: rendered above the page content (navbars, banners, announcements)
61
+ - **`after`**: rendered below the page content (footers, cookie bars)
62
+
63
+ The blocks you reference here are regular block definitions (created with `createBlock`). They're typically marked with `layoutOnly: true` in their block definition so they don't appear in the "add block" picker for page content.
64
+
65
+ ```tsx
66
+ blocks: {
67
+ before: [navbarBlock, announcementBlock],
68
+ after: [footerBlock],
69
+ }
70
+ ```
71
+
72
+ You can have multiple blocks in either group, or leave one empty:
73
+
74
+ ```tsx
75
+ blocks: {
76
+ before: [navbarBlock],
77
+ after: [], // No blocks after page content
78
+ }
79
+ ```
80
+
81
+ ## The Layout Component
82
+
83
+ The component is a named React function that receives `{ children }` and renders the overall page structure. Inside it, use the slot components from the layout constant to place each block.
84
+
85
+ Slot components are accessed via `layoutVar.blocks.PascalCaseName`, where the name is the PascalCase version of the block's `id`. For example, a block with `id: "navbar"` becomes `layoutVar.blocks.Navbar`; `id: "cookie-bar"` becomes `layoutVar.blocks.CookieBar`.
86
+
87
+ ```tsx
88
+ function MyLayoutComponent({ children }: { children: React.ReactNode }) {
89
+ return (
90
+ <main className="flex min-h-screen flex-col">
91
+ <myLayout.blocks.Navbar />
92
+ <div className="flex-1">{children}</div>
93
+ <myLayout.blocks.Footer />
94
+ </main>
95
+ );
96
+ }
97
+ ```
98
+
99
+ The component controls the HTML structure — you decide how to wrap and position the blocks and page content. Use Tailwind CSS for styling.
100
+
101
+ ## Meta Title — `buildMetaTitle`
102
+
103
+ Controls how the browser tab title is built. Receives three parameters:
104
+
105
+ - `pageMetaTitle` — the title set on the individual page
106
+ - `projectName` — the site/project name
107
+ - `pageFullPath` — the full URL path of the page
108
+
109
+ Common patterns:
110
+
111
+ ```tsx
112
+ // "Page Title | Site Name" (most common for content pages)
113
+ buildMetaTitle: ({ pageMetaTitle, projectName }) =>
114
+ `${pageMetaTitle} | ${projectName}`,
115
+
116
+ // "Site Name | Page Title" (common for landing/home pages)
117
+ buildMetaTitle: ({ pageMetaTitle, projectName }) =>
118
+ `${projectName} | ${pageMetaTitle}`,
119
+
120
+ // Just the page title
121
+ buildMetaTitle: ({ pageMetaTitle }) => pageMetaTitle,
122
+ ```
123
+
124
+ ## OG Image — `buildOgImage` (optional)
125
+
126
+ Generates an Open Graph image (the preview shown when sharing links on social media). The function returns a JSX element that gets rendered as a 1200x630 image.
127
+
128
+ The JSX here uses **inline styles only** (no Tailwind) because it's rendered by an image generation engine, not a browser. Use `display: "flex"` for layout.
129
+
130
+ ```tsx
131
+ buildOgImage: ({ title, description, projectName }) => (
132
+ <div
133
+ style={{
134
+ display: "flex",
135
+ flexDirection: "column",
136
+ justifyContent: "center",
137
+ alignItems: "flex-start",
138
+ width: "100%",
139
+ height: "100%",
140
+ backgroundColor: "#09090b",
141
+ padding: "60px 80px",
142
+ fontFamily: "sans-serif",
143
+ }}
144
+ >
145
+ {projectName && (
146
+ <div style={{ fontSize: 24, color: "#a1a1aa", marginBottom: 24 }}>
147
+ {projectName}
148
+ </div>
149
+ )}
150
+ <div
151
+ style={{
152
+ fontSize: 64,
153
+ fontWeight: 700,
154
+ color: "#fafafa",
155
+ lineHeight: 1.2,
156
+ marginBottom: 24,
157
+ }}
158
+ >
159
+ {title}
160
+ </div>
161
+ {description && (
162
+ <div style={{ fontSize: 28, color: "#a1a1aa", lineHeight: 1.5 }}>
163
+ {description}
164
+ </div>
165
+ )}
166
+ </div>
167
+ ),
168
+ ```
169
+
170
+ ## Rules and Conventions
171
+
172
+ 1. **File = one layout.** One `.tsx` file per layout in `src/camox/layouts/`. The `id` must match the filename (without `.tsx`).
173
+ 2. **Named export as `layout`.** Always: `export { myVar as layout }`. Not a default export.
174
+ 3. **Named function component.** Use `function MyComponent()`, not an arrow function. Reference it in `createLayout` before its declaration is fine (hoisting).
175
+ 4. **Import path is `"camox/createLayout"`.** The `createLayout` function comes from this import.
176
+ 5. **Import blocks from `"../blocks/filename"`.** Layout blocks are imported from the blocks directory. Import the named `block` export.
177
+ 6. **Use Tailwind CSS for the component.** Style the layout shell with Tailwind utility classes. The OG image function uses inline styles instead.
178
+ 7. **Slot names are PascalCase.** A block with `id: "my-block"` becomes `layout.blocks.MyBlock`.
179
+ 8. **`buildMetaTitle` is required.** Every layout must define how page titles are constructed.
180
+ 9. **Description guides layout selection.** Write the `description` to help CMS users choose the right layout for their page — explain what types of pages it's suited for.
181
+ 10. **Layout blocks should use `layoutOnly: true`.** Blocks intended only for layouts (navbars, footers) should set `layoutOnly: true` in their block definition so they don't clutter the page block picker.
package/index.js DELETED
@@ -1,3 +0,0 @@
1
- module.exports = {
2
- hello: () => "Hello, world!",
3
- };