camox 0.43.0 → 0.45.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 (412) hide show
  1. package/bin/camox.mjs +9 -1
  2. package/dist/components/AuthGate.js +0 -6
  3. package/dist/components/DeviceSelector.js +96 -0
  4. package/dist/components/PlatformLabel.js +20 -24
  5. package/dist/core/components/lexical/HighlightNode.js +1 -1
  6. package/dist/core/components/lexical/InlineContentEditable.js +15 -19
  7. package/dist/core/components/lexical/InlineLexicalEditor.js +54 -74
  8. package/dist/core/components/lexical/InlineStylesPlugin.js +57 -41
  9. package/dist/core/components/lexical/MaxLengthPlugin.js +59 -0
  10. package/dist/core/components/lexical/SelectionBroadcaster.js +55 -56
  11. package/dist/core/components/lexical/SidebarLexicalEditor.js +327 -234
  12. package/dist/core/components/lexical/SummaryToggleGuard.js +42 -0
  13. package/dist/core/components/lexical/TextLinkPopover.js +169 -180
  14. package/dist/core/createApp.d.ts +254 -299
  15. package/dist/core/createApp.js +123 -29
  16. package/dist/core/createBlock.d.ts +5 -5
  17. package/dist/core/createBlock.js +399 -152
  18. package/dist/core/createCollection.d.ts +55 -0
  19. package/dist/core/createCollection.js +93 -0
  20. package/dist/core/createLayout.d.ts +197 -61
  21. package/dist/core/createLayout.js +114 -56
  22. package/dist/core/defineDocument.d.ts +3 -5
  23. package/dist/core/derivedRoutes.js +49 -1
  24. package/dist/core/dom.d.ts +13 -4
  25. package/dist/core/dom.js +44 -1
  26. package/dist/core/editing/BlockEditingRuntime.d.ts +16 -0
  27. package/dist/core/editing/BlockEditingRuntime.js +6 -15
  28. package/dist/core/editing/CompleteBlockEditingRuntime.js +28 -23
  29. package/dist/core/editing/createEditableBlock.d.ts +820 -565
  30. package/dist/core/editing/createEditableBlock.js +2153 -971
  31. package/dist/core/editing/referenceWrites.js +51 -0
  32. package/dist/core/editing/useLatestRecord.js +90 -0
  33. package/dist/core/hooks/useArtifactHost.js +139 -0
  34. package/dist/core/hooks/useFieldSelection.js +43 -18
  35. package/dist/core/hooks/useIsEditable.js +12 -10
  36. package/dist/core/hooks/useOverlayMessage.js +15 -19
  37. package/dist/core/lib/artifact.d.ts +21 -0
  38. package/dist/core/lib/artifact.js +154 -0
  39. package/dist/core/lib/artifactColors.js +123 -0
  40. package/dist/core/lib/artifactData.d.ts +1 -0
  41. package/dist/core/lib/artifactData.js +131 -0
  42. package/dist/core/lib/artifactHost.js +205 -0
  43. package/dist/core/lib/artifactRuntime.js +36 -0
  44. package/dist/core/lib/artifactTheme.js +57 -0
  45. package/dist/core/lib/contentType.d.ts +304 -103
  46. package/dist/core/lib/contentType.js +207 -168
  47. package/dist/core/lib/fieldTypes.js +4 -0
  48. package/dist/core/lib/iconTypes.d.ts +13 -0
  49. package/dist/core/lib/icons.d.ts +6 -0
  50. package/dist/core/lib/icons.js +126 -0
  51. package/dist/core/lib/imagePlaceholder.js +177 -0
  52. package/dist/core/lib/imageTransform.js +1 -1
  53. package/dist/core/lib/lexicalReact.d.ts +3 -1
  54. package/dist/core/lib/lexicalState.js +7 -7
  55. package/dist/core/lib/modifierFormats.js +1 -1
  56. package/dist/core/lib/previewPaused.js +9 -0
  57. package/dist/core/lib/recordScope.js +316 -0
  58. package/dist/core/lib/reference.d.ts +81 -0
  59. package/dist/core/lib/reference.js +29 -0
  60. package/dist/core/lib/textLinks.js +7 -5
  61. package/dist/core/lib/textStyles.d.ts +6 -8
  62. package/dist/core/lib/textStyles.js +5 -5
  63. package/dist/core/notFound.d.ts +7 -0
  64. package/dist/core/notFound.js +11 -0
  65. package/dist/core/pageDestinations.js +6 -3
  66. package/dist/core/recordItem.js +34 -0
  67. package/dist/core/requestContext.d.ts +26 -0
  68. package/dist/core/requestContext.js +20 -0
  69. package/dist/features/canvas/CamoxCanvas.js +1734 -0
  70. package/dist/features/canvas/CanvasBlockFrame.js +353 -0
  71. package/dist/features/canvas/CanvasBlockHeader.js +55 -0
  72. package/dist/features/canvas/CanvasLoadingScreen.js +35 -0
  73. package/dist/features/canvas/CanvasOverlays.js +254 -0
  74. package/dist/features/canvas/CanvasPageEdge.js +87 -0
  75. package/dist/features/canvas/CanvasPageFrame.js +335 -0
  76. package/dist/features/canvas/CanvasPageHeader.js +125 -0
  77. package/dist/features/canvas/CanvasPreviewPageSlot.js +103 -0
  78. package/dist/features/canvas/CanvasShelfMarker.js +255 -0
  79. package/dist/features/canvas/DerivedPagePathInput.js +238 -0
  80. package/dist/features/canvas/canvasCamera.js +229 -0
  81. package/dist/features/canvas/canvasFlight.js +104 -0
  82. package/dist/features/canvas/canvasFrameCapture.js +364 -0
  83. package/dist/features/canvas/canvasFrameDocument.js +317 -0
  84. package/dist/features/canvas/canvasFrameMode.js +53 -0
  85. package/dist/features/canvas/canvasInsertionSeams.js +44 -0
  86. package/dist/features/canvas/canvasItems.js +120 -0
  87. package/dist/features/canvas/canvasOverlayGeometry.js +240 -0
  88. package/dist/features/canvas/canvasPages.js +79 -0
  89. package/dist/features/canvas/canvasScreenSpace.js +26 -0
  90. package/dist/features/canvas/canvasSelection.js +18 -0
  91. package/dist/features/canvas/canvasStore.js +149 -0
  92. package/dist/features/canvas/canvasThumbnails.js +304 -0
  93. package/dist/features/canvas/canvasZoom.js +10 -0
  94. package/dist/features/canvas/sidebar/CanvasInspector.js +202 -0
  95. package/dist/features/canvas/sidebar/CanvasNavigator.js +226 -0
  96. package/dist/features/canvas/sidebar/CanvasSidebar.js +60 -0
  97. package/dist/features/canvas/sidebar/CanvasSidebarFrame.js +48 -0
  98. package/dist/features/canvas/sidebar/CanvasSidebarSkeleton.js +39 -0
  99. package/dist/features/canvas/sidebar/CatalogBlockInspector.js +189 -0
  100. package/dist/features/canvas/sidebar/CuratedPageInspector.js +69 -0
  101. package/dist/features/canvas/sidebar/DerivedPageInspector.js +37 -0
  102. package/dist/features/canvas/sidebar/LayoutPageBlocks.js +78 -0
  103. package/dist/features/canvas/sidebar/LayoutPageOverview.js +71 -0
  104. package/dist/features/canvas/sidebar/PageInspector.js +157 -0
  105. package/dist/features/canvas/sidebar/PageInspectorSkeleton.js +90 -0
  106. package/dist/features/canvas/sidebar/PageOverview.js +27 -0
  107. package/dist/features/canvas/sidebar/SingletonPageInspector.js +31 -0
  108. package/dist/features/canvas/useCanvasCamera.js +803 -0
  109. package/dist/features/canvas/useCanvasFrame.js +149 -0
  110. package/dist/features/canvas/useCanvasFrameInput.js +97 -0
  111. package/dist/features/canvas/useCanvasNearness.js +57 -0
  112. package/dist/features/canvas/useDiscoveredPreviewPaths.js +62 -0
  113. package/dist/features/content/CamoxContent.d.ts +2 -5
  114. package/dist/features/content/CamoxContent.js +174 -271
  115. package/dist/features/content/CollectionItemSheetContext.js +106 -0
  116. package/dist/features/content/ContentAssets.js +315 -0
  117. package/dist/features/content/ContentCollection.js +663 -0
  118. package/dist/features/content/collection-form.js +58 -0
  119. package/dist/features/content/components/AssetCard.js +101 -69
  120. package/dist/features/content/components/AssetCardSkeleton.js +4 -8
  121. package/dist/features/content/components/ContentSidebar.js +129 -18
  122. package/dist/features/content/components/ContentSidebarGroup.js +37 -0
  123. package/dist/features/content/components/DeleteCollectionItemButton.js +202 -0
  124. package/dist/features/content/components/MissingFromLocale.js +282 -0
  125. package/dist/features/content/components/UploadDropZone.js +27 -31
  126. package/dist/features/content/components/UploadProgressDrawer.js +90 -98
  127. package/dist/features/navigation/navigation.d.ts +66 -20
  128. package/dist/features/navigation/navigation.js +257 -90
  129. package/dist/features/og/defaultFonts.js +8 -8
  130. package/dist/features/og/geist-variable-ttf-base64.js +1 -1
  131. package/dist/features/og/imageResponse.d.ts +8 -0
  132. package/dist/features/og/imageResponse.js +26 -0
  133. package/dist/features/page/CuratedPageContent.js +167 -0
  134. package/dist/features/page/DerivedPageContent.js +26 -30
  135. package/dist/features/page/SharedLayoutContent.js +25 -27
  136. package/dist/features/preview/CamoxPreview.d.ts +23 -61
  137. package/dist/features/preview/CamoxPreview.js +811 -507
  138. package/dist/features/preview/EditablePageContent.js +1 -1
  139. package/dist/features/preview/EditablePageExperience.js +16 -22
  140. package/dist/features/preview/components/AddBlockDialog.js +270 -0
  141. package/dist/features/preview/components/AssetFieldEditor.js +173 -154
  142. package/dist/features/preview/components/AssetLightbox.js +194 -136
  143. package/dist/features/preview/components/AssetPickerModal.js +153 -170
  144. package/dist/features/preview/components/BlockActionsPopover.js +150 -149
  145. package/dist/features/preview/components/BlockErrorBoundary.js +8 -12
  146. package/dist/features/preview/components/BlockThumbnail.js +176 -100
  147. package/dist/features/preview/components/CanvasPreview.js +243 -0
  148. package/dist/features/preview/components/CreatePageModal.js +128 -137
  149. package/dist/features/preview/components/DebouncedFieldEditor.js +29 -33
  150. package/dist/features/preview/components/DerivedLayoutDialog.js +46 -29
  151. package/dist/features/preview/components/DrillRow.js +150 -0
  152. package/dist/features/preview/components/FieldOverlayStyles.js +33 -10
  153. package/dist/features/preview/components/FieldToolbar.js +166 -116
  154. package/dist/features/preview/components/Frame.js +108 -20
  155. package/dist/features/preview/components/IconFieldEditor.js +227 -0
  156. package/dist/features/preview/components/ItemFieldsEditor.js +238 -205
  157. package/dist/features/preview/components/LinkFieldEditor.js +135 -139
  158. package/dist/features/preview/components/Metadata.js +22 -30
  159. package/dist/features/preview/components/MobilePreviewDrawer.js +89 -108
  160. package/dist/features/preview/components/MultipleAssetFieldEditor.js +334 -246
  161. package/dist/features/preview/components/OverlayTracker.js +10 -42
  162. package/dist/features/preview/components/Overlays.js +81 -69
  163. package/dist/features/preview/components/PageInfo.js +1748 -0
  164. package/dist/features/preview/components/PageInfoSkeleton.js +51 -0
  165. package/dist/features/preview/components/PageLocationFieldset.js +112 -116
  166. package/dist/features/preview/components/PageNavigatorHeader.js +24 -0
  167. package/dist/features/preview/components/PageNicknameField.js +22 -26
  168. package/dist/features/preview/components/PagePicker.js +145 -147
  169. package/dist/features/preview/components/PageTree.js +525 -532
  170. package/dist/features/preview/components/PreviewActivation.js +28 -32
  171. package/dist/features/preview/components/PreviewPanel.js +620 -319
  172. package/dist/features/preview/components/PreviewToolbar.js +111 -340
  173. package/dist/features/preview/components/RecordCard.js +80 -0
  174. package/dist/features/preview/components/RecordCombobox.js +178 -0
  175. package/dist/features/preview/components/ReferenceFieldEditor.js +149 -0
  176. package/dist/features/preview/components/ReferenceListFieldEditor.js +432 -0
  177. package/dist/features/preview/components/RemovedBlockPlaceholder.js +95 -0
  178. package/dist/features/preview/components/RepeatableItemsList.js +125 -138
  179. package/dist/features/preview/components/SelectionEditor.js +993 -0
  180. package/dist/features/preview/components/ShikiMarkdown.js +14 -18
  181. package/dist/features/preview/components/SidebarSection.js +52 -64
  182. package/dist/features/preview/components/StudioEditingFallback.js +56 -0
  183. package/dist/features/preview/components/UnlinkAssetButton.js +104 -107
  184. package/dist/features/preview/components/blockGridNavigation.js +15 -0
  185. package/dist/features/preview/components/contentFieldSchema.js +8 -0
  186. package/dist/features/preview/components/useFieldWriter.js +200 -0
  187. package/dist/features/preview/components/useRecordView.js +383 -0
  188. package/dist/features/preview/components/useRepeatableItemActions.js +35 -37
  189. package/dist/features/preview/components/useUpdateBlockPosition.js +37 -37
  190. package/dist/features/preview/editingFrame.js +197 -0
  191. package/dist/features/preview/overlayHighlights.js +27 -0
  192. package/dist/features/preview/overlayMessages.js +72 -1
  193. package/dist/features/preview/pageRecord.js +189 -0
  194. package/dist/features/preview/previewConstants.js +6 -2
  195. package/dist/features/preview/previewPageFrame.js +95 -0
  196. package/dist/features/preview/previewPreparation.js +7 -0
  197. package/dist/features/preview/previewSelection.js +33 -59
  198. package/dist/features/preview/previewStore.js +225 -123
  199. package/dist/features/preview/referencePickerFocus.js +29 -0
  200. package/dist/features/preview/repeatableItemTargets.js +56 -0
  201. package/dist/features/preview/selectionReveal.js +21 -0
  202. package/dist/features/preview/usePreviewViewportActions.js +114 -0
  203. package/dist/features/preview/viewportContinuity.js +109 -0
  204. package/dist/features/provider/AuthenticatedCamoxProvider.js +69 -44
  205. package/dist/features/provider/CamoxProvider.d.ts +3 -6
  206. package/dist/features/provider/CamoxProvider.js +38 -50
  207. package/dist/features/provider/CoreCamoxProvider.js +71 -23
  208. package/dist/features/provider/LocalhostPreviewProvider.js +73 -73
  209. package/dist/features/provider/PreviewSignInPage.js +54 -0
  210. package/dist/features/provider/components/CamoxAppContext.js +11 -11
  211. package/dist/features/provider/components/CommandPalette.js +87 -143
  212. package/dist/features/provider/useAdminShortcuts.js +7 -11
  213. package/dist/features/routes/pageRuntime.d.ts +44 -4
  214. package/dist/features/routes/pageRuntime.js +244 -137
  215. package/dist/features/runtime/RuntimeChrome.js +117 -115
  216. package/dist/features/runtime/alternates.js +64 -0
  217. package/dist/features/runtime/analytics.d.ts +7 -0
  218. package/dist/features/runtime/analytics.js +66 -0
  219. package/dist/features/runtime/artifactSources.js +87 -0
  220. package/dist/features/runtime/editingFallback.js +20 -0
  221. package/dist/features/runtime/linkPrefetch.js +97 -0
  222. package/dist/features/runtime/livePages.js +62 -0
  223. package/dist/features/runtime/llmsTxt.js +40 -0
  224. package/dist/features/runtime/loopbackFetch.js +18 -0
  225. package/dist/features/runtime/navigationAccessibility.js +51 -0
  226. package/dist/features/runtime/navigationTarget.js +23 -2
  227. package/dist/features/runtime/pageApp.js +216 -141
  228. package/dist/features/runtime/pageBlockTypes.js +20 -0
  229. package/dist/features/runtime/pageClient.d.ts +2 -4
  230. package/dist/features/runtime/pageClient.js +18 -3
  231. package/dist/features/runtime/pageNavigation.js +254 -32
  232. package/dist/features/runtime/pageServer.d.ts +2 -4
  233. package/dist/features/runtime/pageServer.js +8 -1
  234. package/dist/features/runtime/pageViewport.js +7 -0
  235. package/dist/features/runtime/runtime.d.ts +60 -23
  236. package/dist/features/runtime/runtime.js +779 -166
  237. package/dist/features/runtime/runtimeChromeLoader.js +58 -0
  238. package/dist/features/runtime/runtimeRoutes.d.ts +19 -0
  239. package/dist/features/runtime/runtimeRoutes.js +25 -0
  240. package/dist/features/runtime/scrollRestoration.js +133 -0
  241. package/dist/features/runtime/studioApp.d.ts +4 -3
  242. package/dist/features/runtime/studioApp.js +5 -9
  243. package/dist/features/runtime/studioClient.d.ts +2 -4
  244. package/dist/features/runtime/studioClient.js +12 -3
  245. package/dist/features/runtime/studioServer.d.ts +2 -4
  246. package/dist/features/runtime/studioServer.js +6 -1
  247. package/dist/features/runtime/worker.d.ts +6 -0
  248. package/dist/features/runtime/worker.js +23 -0
  249. package/dist/features/runtime/workerRequest.js +44 -0
  250. package/dist/features/studio/CamoxStudio.d.ts +2 -6
  251. package/dist/features/studio/CamoxStudio.js +34 -49
  252. package/dist/features/studio/FollowAgentProvider.js +338 -0
  253. package/dist/features/studio/components/ChatSidebar.js +356 -0
  254. package/dist/features/studio/components/DraftsControl.js +676 -0
  255. package/dist/features/studio/components/FaviconDialog.js +318 -0
  256. package/dist/features/studio/components/LocalePicker.js +785 -0
  257. package/dist/features/studio/components/Navbar.js +123 -103
  258. package/dist/features/studio/components/ProjectMenu.js +239 -260
  259. package/dist/features/studio/components/RenameDraftDialog.js +206 -0
  260. package/dist/features/studio/components/ReviewDraftSheet.js +1472 -0
  261. package/dist/features/studio/components/ThreadComposer.js +157 -0
  262. package/dist/features/studio/components/ThreadMarkdown.js +113 -0
  263. package/dist/features/studio/components/ThreadMessages.js +270 -0
  264. package/dist/features/studio/components/UserButton.js +34 -23
  265. package/dist/features/studio/draftLabel.js +33 -0
  266. package/dist/features/studio/draftNotice.js +13 -0
  267. package/dist/features/studio/followAgent.js +250 -0
  268. package/dist/features/studio/followAgentContext.js +7 -0
  269. package/dist/features/studio/localeOptions.js +118 -0
  270. package/dist/features/studio/localeSwitch.js +19 -0
  271. package/dist/features/studio/routes.js +48 -1
  272. package/dist/features/studio/studioStore.js +64 -1
  273. package/dist/features/studio/useTheme.js +24 -27
  274. package/dist/features/vite/artifactRuntimeMinification.js +36 -0
  275. package/dist/features/vite/auth.js +1 -50
  276. package/dist/features/vite/blockBoilerplate.js +6 -5
  277. package/dist/features/vite/blockChunks.js +31 -0
  278. package/dist/features/vite/bundlerLogs.js +17 -0
  279. package/dist/features/vite/definitionsSync.js +158 -226
  280. package/dist/features/vite/devAuthentication.js +4 -16
  281. package/dist/features/vite/devSignIn.js +87 -0
  282. package/dist/features/vite/iconGeneration.js +16 -0
  283. package/dist/features/vite/layoutGeneration.js +11 -1
  284. package/dist/features/vite/publishSeededContent.js +30 -0
  285. package/dist/features/vite/routeGeneration.js +6 -7
  286. package/dist/features/vite/runtimeDev.js +80 -99
  287. package/dist/features/vite/vite.d.ts +53 -31
  288. package/dist/features/vite/vite.js +230 -116
  289. package/dist/features/vite/workerBuild.js +39 -0
  290. package/dist/hooks/use-debounced-field.js +116 -47
  291. package/dist/hooks/use-file-upload.js +114 -67
  292. package/dist/hooks/use-marquee-selection.js +32 -36
  293. package/dist/hooks/use-page-destinations.js +69 -37
  294. package/dist/lib/api-client-server.d.ts +5 -0
  295. package/dist/lib/api-client-server.js +10 -4
  296. package/dist/lib/api-client.d.ts +6 -0
  297. package/dist/lib/api-client.js +51 -16
  298. package/dist/lib/api-socket.js +17 -0
  299. package/dist/lib/auth-page-styles.js +39 -0
  300. package/dist/lib/auth.js +139 -60
  301. package/dist/lib/collection-cache.js +23 -0
  302. package/dist/lib/draft-scope.d.ts +4 -0
  303. package/dist/lib/draft-scope.js +222 -0
  304. package/dist/lib/html.js +12 -0
  305. package/dist/lib/link-pages.js +154 -0
  306. package/dist/lib/locale-path.d.ts +22 -0
  307. package/dist/lib/locale-path.js +57 -0
  308. package/dist/lib/normalized-data.js +252 -152
  309. package/dist/lib/preview-handoff.js +13 -0
  310. package/dist/lib/queries.d.ts +11 -0
  311. package/dist/lib/queries.js +71 -131
  312. package/dist/lib/studio-reads.d.ts +14 -0
  313. package/dist/lib/studio-reads.js +574 -0
  314. package/dist/lib/use-project-room.js +50 -47
  315. package/dist/lib/use-thread.js +310 -0
  316. package/dist/lib/utils.js +25 -29
  317. package/dist/lib/view-locale-context.js +22 -0
  318. package/dist/lib/view-locale.js +46 -0
  319. package/dist/package.js +5 -0
  320. package/dist/studio-overlays.css +41 -147
  321. package/dist/studio.css +2 -2
  322. package/package.json +70 -72
  323. package/skills/camox/SKILL.md +16 -5
  324. package/skills/camox/references/artifacts.md +179 -0
  325. package/skills/camox/references/block-definitions.md +98 -55
  326. package/skills/camox/references/cli-common.md +18 -6
  327. package/skills/camox/references/cli-content.md +92 -58
  328. package/skills/camox/references/cli-publishing.md +26 -0
  329. package/skills/camox/references/dom-integrations.md +27 -0
  330. package/skills/camox/references/field-styling.md +7 -7
  331. package/skills/camox/references/layout-definitions.md +16 -14
  332. package/skills/camox/references/localization.md +56 -0
  333. package/skills/camox/references/routed-layouts.md +133 -15
  334. package/skills/camox-expand/SKILL.md +69 -0
  335. package/skills/camox-expand/references/jobs.md +39 -0
  336. package/skills/camoxify/SKILL.md +38 -2
  337. package/dist/core/components/AddBlockControlBar.js +0 -78
  338. package/dist/core/hooks/useRequireDraftSource.js +0 -23
  339. package/dist/features/metadata/sitemap.d.ts +0 -8
  340. package/dist/features/metadata/sitemap.js +0 -17
  341. package/dist/features/og/imageResponse.node.d.ts +0 -12
  342. package/dist/features/og/imageResponse.node.js +0 -49
  343. package/dist/features/og/imageResponse.workerd.d.ts +0 -12
  344. package/dist/features/og/imageResponse.workerd.js +0 -40
  345. package/dist/features/page/PublishedPageContent.js +0 -205
  346. package/dist/features/page/PublishedPageExperience.js +0 -23
  347. package/dist/features/preview/DerivedPreview.js +0 -134
  348. package/dist/features/preview/commentsEnabled.js +0 -8
  349. package/dist/features/preview/components/AddBlockSidebar.js +0 -381
  350. package/dist/features/preview/components/AttachedComments.js +0 -194
  351. package/dist/features/preview/components/CommentHeader.js +0 -122
  352. package/dist/features/preview/components/CommentSidebar.js +0 -29
  353. package/dist/features/preview/components/CommentTargetQuote.js +0 -166
  354. package/dist/features/preview/components/DerivedLayoutSidebar.d.ts +0 -5
  355. package/dist/features/preview/components/DerivedLayoutSidebar.js +0 -179
  356. package/dist/features/preview/components/DerivedPageInfoSidebar.js +0 -69
  357. package/dist/features/preview/components/LeftSidebar.js +0 -68
  358. package/dist/features/preview/components/PageEditorSidebar.js +0 -563
  359. package/dist/features/preview/components/PageInfoSidebar.js +0 -1551
  360. package/dist/features/preview/components/PageNavigatorSidebar.js +0 -78
  361. package/dist/features/preview/components/PageStatusBadge.js +0 -97
  362. package/dist/features/preview/components/PeekedBlock.js +0 -217
  363. package/dist/features/preview/components/PreviewComments.js +0 -56
  364. package/dist/features/preview/components/PreviewToolbarSpacer.js +0 -43
  365. package/dist/features/preview/components/PublicationRow.js +0 -422
  366. package/dist/features/preview/components/PublishDialog.js +0 -194
  367. package/dist/features/preview/components/RightSidebar.js +0 -59
  368. package/dist/features/preview/previewCommentsStore.js +0 -115
  369. package/dist/features/preview/publication.js +0 -81
  370. package/dist/features/runtime/SharedChromeContext.js +0 -7
  371. package/dist/features/studio/components/EnvironmentMenu.js +0 -413
  372. package/dist/features/vite/appGeneration.js +0 -89
  373. package/dist/lib/view-queries.js +0 -28
  374. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/Matches.d.ts +0 -114
  375. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/RouterProvider.d.ts +0 -25
  376. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/config.d.ts +0 -1
  377. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/defer.d.ts +0 -1
  378. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/fileRoute.d.ts +0 -20
  379. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/global.d.ts +0 -8
  380. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/hash-scroll.d.ts +0 -1
  381. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/index.d.ts +0 -18
  382. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/link.d.ts +0 -161
  383. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/load-matches.d.ts +0 -6
  384. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/location.d.ts +0 -54
  385. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/lru-cache.d.ts +0 -8
  386. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/manifest.d.ts +0 -32
  387. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.d.ts +0 -115
  388. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/not-found.d.ts +0 -1
  389. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/redirect.d.ts +0 -49
  390. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/rewrite.d.ts +0 -1
  391. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/root.d.ts +0 -6
  392. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/route.d.ts +0 -343
  393. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/routeInfo.d.ts +0 -48
  394. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/router.d.ts +0 -695
  395. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/scroll-restoration.d.ts +0 -1
  396. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/searchMiddleware.d.ts +0 -1
  397. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/searchParams.d.ts +0 -5
  398. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/ssr/serializer/RawStream.d.ts +0 -38
  399. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/ssr/serializer/seroval-plugins.d.ts +0 -1
  400. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/ssr/serializer/transformer.d.ts +0 -65
  401. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/stores.d.ts +0 -69
  402. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/structuralSharing.d.ts +0 -1
  403. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/typePrimitives.d.ts +0 -1
  404. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useLoaderData.d.ts +0 -1
  405. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useLoaderDeps.d.ts +0 -1
  406. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useNavigate.d.ts +0 -10
  407. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useParams.d.ts +0 -1
  408. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useRouteContext.d.ts +0 -1
  409. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/useSearch.d.ts +0 -1
  410. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/utils.d.ts +0 -28
  411. package/dist/node_modules/.pnpm/@tanstack_router-core@1.168.14/node_modules/@tanstack/router-core/dist/esm/validators.d.ts +0 -49
  412. package/skills/camox/references/cli-environments.md +0 -37
@@ -4,7 +4,7 @@ Use this reference to customize inline text, links, and highlights on an existin
4
4
 
5
5
  ## String formatting
6
6
 
7
- String fields support inline formatting in both editing and published output: `**bold**`, `*italic*` (or `_italic_`), `***bold italic***`, `<u>underlined</u>`, `<highlight>highlight text</highlight>`, and text links like `[label](https://example.com)` or internal page links inserted by the editor. Highlight can contain other formatting, links, and line breaks. Content remains a string. The editor uses explicit `<strong>` and `<em>` tags when Markdown delimiters cannot represent a selection losslessly (for example, formatting that includes leading/trailing whitespace). These are built-in formatting tags, not arbitrary HTML.
7
+ String fields support inline formatting in both editing and published output: `**bold**`, `*italic*` (or `_italic_`), `***bold italic***`, `<s>strikethrough</s>`, `<highlight>highlight text</highlight>`, and text links like `[label](https://example.com)` or internal page links inserted by the editor. Highlight can contain other formatting, links, and line breaks. Content remains a string. The editor uses explicit `<strong>` and `<em>` tags when Markdown delimiters cannot represent a selection losslessly (for example, formatting that includes leading/trailing whitespace). These are built-in formatting tags, not arbitrary HTML.
8
8
 
9
9
  ## Rendering and appearance
10
10
 
@@ -14,7 +14,7 @@ Spread the field's render-prop `props` onto the element to preserve inline editi
14
14
  <myBlock.Field name="title">{(props) => <h1 {...props} />}</myBlock.Field>
15
15
  ```
16
16
 
17
- Camox owns inline markup so editing and published output have the same appearance. Customize appearance with `textStyle` and `linkStyle` (these replace the old `components` prop):
17
+ Camox owns inline markup so editing and published output have the same appearance. Customize appearance with `textStyle` and `linkStyle` (these replace the old `components` prop). Utilities below, such as `text-brand`, are examples; use the ones the project's Theme defines:
18
18
 
19
19
  ```tsx
20
20
  <myBlock.Field
@@ -31,20 +31,20 @@ Camox owns inline markup so editing and published output have the same appearanc
31
31
  };
32
32
  }
33
33
  return {
34
- className: bold ? "text-primary" : undefined,
34
+ className: bold ? "text-brand" : undefined,
35
35
  style: bold && italic ? { letterSpacing: "0.02em" } : undefined,
36
36
  };
37
37
  }}
38
38
  linkStyle={({ external }) => ({
39
- className: external ? "text-blue-600" : "text-primary font-medium",
39
+ className: external ? "text-ink-muted" : "text-brand font-medium",
40
40
  })}
41
41
  >
42
42
  {(props) => <p {...props} />}
43
43
  </myBlock.Field>
44
44
  ```
45
45
 
46
- - `textStyle` receives `{ bold, italic, underline, highlight }` and returns `{ className?, style? }` or `undefined`. For text runs, `highlight` is false and the other flags describe explicit content formatting, not inherited heading styles. Unmarked text keeps its parent's typography.
46
+ - `textStyle` receives `{ bold, italic, strikethrough, highlight }` and returns `{ className?, style? }` or `undefined`. For text runs, `highlight` is false and the other flags describe explicit content formatting, not inherited heading styles. Unmarked text keeps its parent's typography.
47
47
  - `linkStyle` receives `{ target, href, external, pageId }` and returns the same appearance-only object. Links are underlined by default; override `style.textDecorationLine` to change that.
48
- - Camox also calls `textStyle` once for each shared highlight wrapper with `{ highlight: true, bold: false, italic: false, underline: false }`. That result styles the whole range; its children receive their own text-run calls. `highlight` identifies the wrapper being styled, not whether a run is inside a highlight. This keeps one continuous background when bold/italic formatting changes within the range. Highlights default to the site's `--primary` text color, falling back to `currentColor`. Return a custom `style.color` to change it, or use a clipped background with transparent text fill to render a gradient, as shown above.
49
- - Styles apply to inline children; the outer element's `className` stays on that element and is inherited normally. Explicit formatting defaults (combined italic, underline, highlight color) use inline styles; use `style` to override those defaults rather than competing utility classes.
48
+ - Camox also calls `textStyle` once for each shared highlight wrapper with `{ highlight: true, bold: false, italic: false, strikethrough: false }`. That result styles the whole range; its children receive their own text-run calls. `highlight` identifies the wrapper being styled, not whether a run is inside a highlight. This keeps one continuous background when bold/italic formatting changes within the range. Highlights default to the website's `--camox-highlight` color, falling back to `currentColor`. Return a custom `style.color` to change it, or use a clipped background with transparent text fill to render a gradient, as shown above.
49
+ - Styles apply to inline children; the outer element's `className` stays on that element and is inherited normally. Explicit formatting defaults (combined italic, strikethrough, highlight color) use inline styles; use `style` to override those defaults rather than competing utility classes.
50
50
  - These props accept appearance only, never replacement elements, children, or event handlers. They also work on `item.Field` inside repeaters. The sidebar editor shows default formatting; field-specific styles apply on the page.
@@ -12,9 +12,9 @@ Use one `.tsx` file per layout in `src/layouts/`, a named `layout` or `Layout` e
12
12
 
13
13
  ```tsx
14
14
  import { createLayout } from "camox/createLayout";
15
- import { block as navbarBlock } from "../blocks/navbar";
15
+
16
16
  import { block as footerBlock } from "../blocks/footer";
17
- import { block as heroBlock } from "../blocks/hero";
17
+ import { block as navbarBlock } from "../blocks/navbar";
18
18
 
19
19
  const myLayout = createLayout({
20
20
  id: "my-layout",
@@ -23,7 +23,7 @@ const myLayout = createLayout({
23
23
  blocks: {
24
24
  before: [navbarBlock],
25
25
  after: [footerBlock],
26
- initial: [heroBlock],
26
+ initial: [() => import("../blocks/hero")],
27
27
  },
28
28
  component: MyLayoutComponent,
29
29
  buildMetaTitle: ({ pageMetaTitle, projectName }) => `${pageMetaTitle} | ${projectName}`,
@@ -44,26 +44,28 @@ export { myLayout as layout };
44
44
 
45
45
  ## The `createLayout` options
46
46
 
47
- | Option | Required | Description |
48
- | ---------------- | -------- | --------------------------------------------------------------------------------- |
49
- | `id` | yes | Unique kebab-case identifier matching the filename without extension. |
50
- | `title` | yes | CMS display name. |
51
- | `description` | yes | Guidance for users and agents on which pages suit this layout. |
52
- | `blocks` | yes | `before`, `after`, and optional `initial` arrays; see placement rules below. |
53
- | `component` | yes | Named function component receiving `{ children }`. |
54
- | `buildMetaTitle` | yes | `({ pageMetaTitle, projectName, pageFullPath }) => string` for the browser title. |
55
- | `buildOgImage` | no | `({ title, description, projectName }) => JSX` for a sharing image. |
47
+ | Option | Required | Description |
48
+ | ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
49
+ | `id` | yes | Unique kebab-case identifier matching the filename without extension. |
50
+ | `title` | yes | CMS display name. |
51
+ | `description` | yes | Guidance for users and agents on which pages suit this layout. |
52
+ | `blocks` | yes | `before`, `after`, and optional `initial` arrays; see layout slot rules below. |
53
+ | `component` | yes | Named function component receiving `{ children }`. |
54
+ | `buildMetaTitle` | yes | `({ pageMetaTitle, projectName, pageFullPath }) => string` for the browser title. |
55
+ | `buildMetaDescription` | no | `({ pageMetaDescription, projectName, pageFullPath }) => string \| undefined`. Without it, the editor's description is used. |
56
+ | `buildOgImage` | no | `({ title, description, projectName }) => JSX` for a sharing image. |
56
57
 
57
- ## Block Placement — `before`, `after`, and `initial`
58
+ ## Layout Slots — `before`, `after`, and `initial`
58
59
 
59
60
  - `before` and `after` require blocks with `layoutOnly: true`. Either array may be empty.
60
61
  - Render groups with `<myLayout.BeforeBlocks />` and `<myLayout.AfterBlocks />`, not individual blocks. Array order controls rendering order; edit the config to add or reorder blocks.
61
- - For identical navbar/footer content across layouts, also set `synced: true`. `layoutOnly` restricts placement; it does not enable syncing. See [Synced blocks](block-definitions.md#synced-blocks-synced-optional) for publishing and existing-instance behavior.
62
+ - For identical navbar/footer content across layouts, also set `synced: true`. `layoutOnly` restricts a block to layout slots; it does not enable syncing. See [Synced blocks](block-definitions.md#synced-blocks-synced-optional) for sharing and existing-placement behavior.
62
63
 
63
64
  ## Initial Blocks — `blocks.initial` (optional)
64
65
 
65
66
  `initial` seeds the homepage at project creation, using block defaults in array order. It does not populate existing Pages.
66
67
 
68
+ - List each block as an import of its module, `() => import("../blocks/hero")`, never a statically imported block. Layouts load on every page, so a static import would ship the block to every visitor.
67
69
  - Accepts only blocks with `layoutOnly` omitted or `false`.
68
70
  - At most one layout per app may define `initial`; multiple definitions cause a startup error.
69
71
  - Without `initial`, Camox seeds an empty homepage using the first curated layout. Apps with only singleton/derived layouts do not seed a curated homepage.
@@ -0,0 +1,56 @@
1
+ # Localization in code
2
+
3
+ A project publishes in one or more locales, added and removed as content, never in code. The default locale's pages sit at unprefixed paths (`/pricing`); every other locale's sit under its lowercase tag (`/de/preise`). Camox translates everything in blocks and records, writes `<html lang>`, `hreflang` alternates and the combined sitemap itself. Code only has to keep text in blocks and links in the locale.
4
+
5
+ ## Visible text lives in blocks
6
+
7
+ Every text a visitor can read is block content, layout blocks included: navbar and footer labels, a singleton's heading, a "Back to articles" link, a record page's "Written by". Camox translates block content; it never translates JSX. Put the text in a `layoutOnly` block placed in the layout's `before` or `after` slots, or in a field of the block that shows it. JSX keeps only structure, styling and data a loader returns.
8
+
9
+ ## Internal links stay in the locale
10
+
11
+ Write root-relative paths without a locale prefix, and pass them through `useLocalizedHref` from `camox/navigation`. On a `de` page, `/articles` becomes `/de/articles`; on a default-locale page it stays `/articles`. External URLs, `#anchors` and `mailto:` links come back as is.
12
+
13
+ ```tsx
14
+ import { Link, useLocalizedHref } from "camox/navigation";
15
+
16
+ function BackToArticles({ children }: { children: React.ReactNode }) {
17
+ const localized = useLocalizedHref();
18
+ return <Link to={localized("/articles")}>{children}</Link>;
19
+ }
20
+ ```
21
+
22
+ Link fields and `camox:page:` text links already resolve per locale: render them as they come. The locale's prefix comes from the helper alone, so a `/de` written into code or content links German visitors to `/de/de/...` and English visitors to German.
23
+
24
+ ## Locale switcher
25
+
26
+ `useAlternates()` returns the current page in every locale it exists in, the current one included, as `{ locale, href, label }`. `label` is the locale's name in its own language (`Deutsch`, `français`), from `Intl.DisplayNames`; capitalize it with CSS if the design needs it. A locale the page was removed from isn't listed, so the switcher never links to a 404. `useLocale()` returns the current locale's tag, such as `de`.
27
+
28
+ ```tsx
29
+ import { Link, useAlternates, useLocale } from "camox/navigation";
30
+
31
+ function LocaleSwitcher() {
32
+ const current = useLocale();
33
+ const alternates = useAlternates();
34
+ if (alternates.length < 2) return null;
35
+ return (
36
+ <nav aria-label="Language">
37
+ {alternates.map(({ locale, href, label }) => (
38
+ <Link key={locale} to={href} hrefLang={locale} aria-current={locale === current}>
39
+ {label}
40
+ </Link>
41
+ ))}
42
+ </nav>
43
+ );
44
+ }
45
+ ```
46
+
47
+ ## Loaders
48
+
49
+ A singleton or derived layout's `loader` receives `locale`, the page's locale tag. Use it to fetch localized data when the data source has it. Camox renders what a loader returns as it is and never translates it. Route patterns aren't localized: `routes` lists the same paths for every locale, and `/de/pokemon/pikachu` renders the `pokemon.$name` layout in German.
50
+
51
+ ```tsx
52
+ loader: async ({ params, locale }) => ({
53
+ kind: "data",
54
+ data: await loadPokemon(params.name, { language: locale }),
55
+ }),
56
+ ```
@@ -5,7 +5,7 @@ Use this reference for code-owned pages in `src/layouts/`. For editor-created pa
5
5
  - **`singleton`**: one code-owned page at a fixed file-defined URL. Editors change its blocks, not its existence, URL, or structure.
6
6
  - **`derived`**: code-owned routes resolved through a required server-side loader, typically using file parameters such as `$name`.
7
7
 
8
- All editable content lives in blocks. Layouts do not expose editable `content` schemas or `.Field` helpers. Read [Block Definitions](block-definitions.md) when creating or changing those blocks.
8
+ All editable content lives in blocks. Layouts do not expose editable `content` schemas or `.Field` helpers; [record pages](#record-pages) render their collection record through `layout.Item`. Read [Block Definitions](block-definitions.md) when creating or changing those blocks.
9
9
 
10
10
  ## Singleton pages
11
11
 
@@ -13,13 +13,14 @@ Declare a singleton in `src/layouts/pokedex.tsx`. The file identity defines `/po
13
13
 
14
14
  ```tsx
15
15
  import { createLayout } from "camox/createLayout";
16
+
17
+ import { block as footerBlock } from "../blocks/footer";
16
18
  import { block as navbarBlock } from "../blocks/navbar";
17
19
  import { block as pokedexIntroBlock } from "../blocks/pokedex-intro";
18
- import { block as footerBlock } from "../blocks/footer";
19
- import { loadPokemon } from "../lib/pokemon";
20
20
  import { PokemonBrowser } from "../components/pokemon-browser";
21
+ import { loadPokemon } from "../lib/pokemon";
21
22
 
22
- export const Layout = createLayout("pokedex")({
23
+ export const layout = createLayout("pokedex")({
23
24
  kind: "singleton",
24
25
  title: "Pokédex",
25
26
  description: "The code-owned Pokémon directory.",
@@ -27,41 +28,158 @@ export const Layout = createLayout("pokedex")({
27
28
  before: [navbarBlock, pokedexIntroBlock],
28
29
  after: [footerBlock],
29
30
  },
30
- loader: () => loadPokemon(),
31
- component: PokedexPage,
31
+ loader: async () => ({ kind: "data", data: await loadPokemon() }),
32
+ // After the loader: these callbacks' `data` is inferred from it.
32
33
  buildMetaTitle: ({ pageMetaTitle }) => pageMetaTitle,
34
+ toMarkdown: ({ data }) => [`${data.length} Pokémon`, data.map((p) => `- ${p.name}`).join("\n")],
35
+ component: PokedexPage,
33
36
  });
34
37
 
35
38
  function PokedexPage() {
36
- const pokemon = Layout.useData();
39
+ const pokemon = layout.useData();
37
40
  return (
38
41
  <>
39
- <Layout.BeforeBlocks />
42
+ <layout.BeforeBlocks />
40
43
  <main>
41
44
  <PokemonBrowser pokemon={pokemon} />
42
45
  </main>
43
- <Layout.AfterBlocks />
46
+ <layout.AfterBlocks />
44
47
  </>
45
48
  );
46
49
  }
47
50
  ```
48
51
 
49
- - `loader` is optional. Its awaited result is inferred by `Layout.useData()`. It runs server-side for document requests and client navigation; return JSON-serializable data. Without a loader, `useData()` returns `undefined`.
52
+ - `loader` is optional. Return `{ kind: "data", data }`, synchronously or asynchronously. `layout.useData()` infers and returns the unwrapped `data`, not the envelope. It runs server-side for document requests and client navigation; the entire envelope travels through JSON to hydration. Keep data JSON-serializable (no functions, symbols, BigInt, or cyclic objects). Without a loader, `useData()` returns `undefined`. The loader also receives `locale`, the page's locale tag; Camox renders its data untranslated (see [Localization](localization.md#loaders)).
50
53
  - Before/after blocks must be `layoutOnly: true`. Put editable headings, introductions, and other bespoke content in these blocks; keep structural application UI in the component. See [Block Definitions](block-definitions.md).
51
- - For a navbar or footer whose content must stay identical across different layouts, also set `synced: true` in its `createBlock` options. `layoutOnly: true` alone only restricts placement. See [Synced blocks](block-definitions.md#synced-blocks-synced-optional) for sharing and publishing behavior.
54
+ - For a navbar or footer whose content must stay identical across different layouts, also set `synced: true` in its `createBlock` options. `layoutOnly: true` alone only restricts placement. See [Synced blocks](block-definitions.md#synced-blocks-synced-optional) for sharing behavior.
52
55
  - No `children` slot or `blocks.initial`: editors do not compose or create these pages.
53
56
  - No curated page record is created. Do not use the pages CLI to create, duplicate, move, or assign a singleton.
54
- - Studio lists singletons as directly navigable pages with block editing and layout publishing, without deletion or URL/layout controls. They also appear in link destination pickers (including inline text links), “Go to page” actions, and the runtime sitemap. Derived route families are not enumerated.
57
+ - Studio lists singletons as directly navigable pages with block editing, without deletion or URL/layout controls. They also appear in link destination pickers (including inline text links), “Go to page” actions, and the runtime sitemap. Derived pages appear in the sitemap only when their layout declares `routes` (see [Route discovery](#route-discovery)).
55
58
  - Singleton links are saved as fixed internal URLs, not synthetic database page IDs: link fields use `{ type: "external", href: "/pokedex", text: "Pokédex", newTab: false }` (the existing URL representation), while inline text uses `[Pokédex](/pokedex)`. Renaming a code-owned URL requires updating links or adding a redirect. Curated-page links retain their page-ID references.
56
59
  - Curated-only mutation surfaces, such as page-parent selectors and the pages record API/CLI, remain separate from the navigable destination list.
57
- - Publishing exposes the current editable blocks. Unpublishing removes those published blocks, **not** the route or code-rendered UI. Synced blocks retain their existing shared publishing behavior.
60
+ - The route renders the current editable blocks.
58
61
  - Code deployment controls route existence. Removing the definition and syncing releases its reserved URL. Existing curated URL collisions or layouts still assigned to curated pages must be resolved explicitly before sync can succeed.
59
62
  - Curated pages cannot claim singleton URLs or use singleton/derived layouts. Literal singleton routes take precedence over parameterized derived routes.
60
- - Current routed-page HTML titles use the definition's `title`; the required `buildMetaTitle` option is retained for API compatibility, but routed-page metadata callbacks are not yet applied.
63
+ - See [Metadata and Markdown](#metadata-and-markdown) for page titles, descriptions, and the page's Markdown Representation.
61
64
 
62
65
  ## Derived routes
63
66
 
64
- Use the same file-based form with `kind: "derived"`, for example `createLayout("pokemon.$name")`. Its required `loader: ({ params }) => ...` receives typed `params.name`. Read its result through `Layout.useData()`. Use `throw notFound()` (imported from `camox/createLayout`) for a missing record; other loader errors remain server errors. Like singletons, derived routes have before/after blocks but no page-owned `children` or `blocks.initial`. Unlike singletons, curated URLs retain precedence over derived routes.
67
+ Use the same file-based form with `kind: "derived"`, for example `createLayout("pokemon.$name")`. Its required `loader: ({ params }) => ...` receives typed `params.name` and returns `{ kind: "data", data }`. Read its unwrapped data through `layout.useData()`. Use `throw notFound()` (imported from `camox/createLayout`) for a missing record; other loader errors remain server errors. Like singletons, derived routes have before/after blocks but no page-owned `children` or `blocks.initial`. Unlike singletons, curated URLs retain precedence over derived routes.
68
+
69
+ ### Route discovery
70
+
71
+ A derived layout can list the pages it renders with an optional `routes` function, a sibling of `loader`:
72
+
73
+ ```tsx
74
+ export const Layout = createLayout("pokemon.$name")({
75
+ kind: "derived",
76
+ // Other options omitted.
77
+ loader: async ({ params }) => ({ kind: "data", data: await loadPokemon(params.name) }),
78
+ routes: async () => ({
79
+ data: (await listPokemon()).map((pokemon) => ({ params: { name: pokemon.name } })),
80
+ }),
81
+ });
82
+ ```
83
+
84
+ - Return `{ data: [{ params }] }`, synchronously or asynchronously. `params` is typed from the file identity: `pokemon.$name` needs exactly `{ name: string }`. Return raw values; Camox builds and encodes the URL (`mr mime` becomes `/pokemon/mr%20mime`).
85
+ - Only derived layouts accept `routes`; singletons and curated layouts are a type error.
86
+ - The runtime sitemap lists every discovered page, without `<lastmod>`. The Canvas previews the layout's first discovered page until a path is chosen.
87
+ - Entries may carry an optional `target` naming the collection record they render (see [Record pages](#record-pages)).
88
+ - An invalid entry (missing, extra or empty params, or a value containing `/`) or a throwing `routes` makes `/sitemap.xml` respond 500 with a message naming the layout, rather than silently omitting pages.
89
+ - `routes` lists pages; it does not authorize them. The loader still validates every request, and URLs left out of `routes` keep rendering. A curated page at a discovered URL still takes precedence and may be listed twice.
90
+ - `routes` returns every page at once.
91
+ - `loader` receives `{ params, context }` and `routes` receives `{ context }`. `context` is an opaque request context: ignore it, or pass it through unchanged to Camox helpers. The sitemap and Markdown always get live content; HTML and Studio navigation follow the editor's session.
92
+
93
+ ### Record pages
94
+
95
+ A **Record Page** renders one collection record, found by one of its fields. Spread `collection.pages({ by, param })` into a derived layout instead of writing `loader` and `routes`:
96
+
97
+ ```tsx
98
+ import { createLayout } from "camox/createLayout";
99
+
100
+ import { collection as articles } from "../collections/articles";
101
+
102
+ export const Layout = createLayout("articles.$slug")({
103
+ kind: "derived",
104
+ title: "Article",
105
+ description: "One article, found by its slug.",
106
+ blocks: { before: [navbarBlock], after: [footerBlock] },
107
+ ...articles.pages({ by: "slug", param: "slug" }),
108
+ buildMetaTitle: ({ item, projectName }) => `${item.title} | ${projectName}`,
109
+ buildMetaDescription: ({ item }) => item.excerpt || undefined,
110
+ toMarkdown: ({ item }) => [`# ${item.title}`, `By ${item.author.name}`, item.body],
111
+ component: ArticlePage,
112
+ });
113
+
114
+ function ArticlePage() {
115
+ return (
116
+ <Layout.Item>
117
+ {(article) => (
118
+ <article>
119
+ <article.Field name="title">{(props) => <h1 {...props} />}</article.Field>
120
+ <article.Reference name="author">
121
+ {(author) => <author.Field name="name">{(props) => <p {...props} />}</author.Field>}
122
+ </article.Reference>
123
+ <article.Field name="body">{(props) => <p {...props} />}</article.Field>
124
+ </article>
125
+ )}
126
+ </Layout.Item>
127
+ );
128
+ }
129
+ ```
130
+
131
+ - `by` names a String field of the collection; any other field is a type error. `param` names the route parameter holding its value; a layout whose route lacks it fails to type-check.
132
+ - The loader finds the record whose `by` field's plain text equals the param: the draft value for a signed-in editor previewing, the published value for everyone else. A draft-only record is reachable in preview at its draft value; a never-published or unknown value answers 404. Records whose value is empty or contains `/` never get a page.
133
+ - Several records sharing a value in the same source is a server error naming the collection, field, value and records. Camox does not enforce uniqueness: keep the field unique.
134
+ - The loader returns a `collection-item` envelope, `{ kind: "collection-item", collectionId, data: { id, content } }`, typed as `LayoutCollectionItemResult` from `camox/createLayout`. `layout.useData()` returns the unwrapped record, `{ id, content }`. Camox rejects an envelope naming an unregistered collection or a malformed record id.
135
+ - `routes` lists one entry per record that can have a page, in the request's source: the sitemap lists published records only, even when an editor asks, while the Canvas previews a signed-in editor's first record, draft-only records included. Each entry is `{ params, target: { kind: "collection-item", collectionId, id } }`; `target` names the record independently of its current value. Records whose value is empty or contains `/` are left out. Several records sharing a value fail discovery, so `/sitemap.xml` responds 500 naming the layout and the records. An empty collection returns `{ data: [] }`, and the Canvas asks for a path.
136
+ - `layout.Item` renders the page's record with the same typed scope as a block's `Reference`: `Field`, `Image`, `File`, `Embed`, `ImageList`, `FileList`, `Reference` and `ReferenceList`, plus `id` and `label`. Field names are checked against the collection. The record is the first hop: `Reference` and `ReferenceList` render the records it links, which link nothing further. Prefer it to `useData()`, whose `content` holds stored values (String fields may be rich-text state).
137
+ - `layout.Item` exists only when every return of the loader is a record of one collection. On a loader that may return external data or records of several collections, using it is a type error. At runtime it renders nothing when the result isn't a record.
138
+ - In Studio, editors edit record page fields in place, like a referenced record's: purple overlays, selection, and the record's fields in the sidebar. Edits change the shared record everywhere it appears.
139
+ - `toMarkdown` also receives `item`, the same Markdown builder as a reference list's `toMarkdown`: interpolate fields (`item.title`, `item.author.name`, `item.cover`) and Camox fills them with the live record, rich text included. `data` stays the raw record. Markdown always shows live content, whoever asks.
140
+ - `buildMetaTitle` and `buildMetaDescription` also receive `item`: the record's String fields as plain text.
141
+ - To wrap the helper, pass the whole loader input through, `context` included, and return the envelope unchanged. Type it as the helper's loader (`ReturnType<typeof articlePages.loader>`) to keep `layout.Item` and `item` typed:
142
+
143
+ ```tsx
144
+ const articlePages = articles.pages({ by: "slug", param: "slug" });
145
+ // …
146
+ loader: async (input) => {
147
+ const result = await articlePages.loader(input);
148
+ return result;
149
+ },
150
+ routes: articlePages.routes,
151
+ ```
152
+
153
+ ## Migrating existing loaders
154
+
155
+ This is a breaking loader contract: replace `return pokemon` with `return { kind: "data", data: pokemon }`. For asynchronous data, await the payload inside the envelope: `return { kind: "data", data: await loadPokemon() }`, not a promise in `data`. Components using `layout.useData()` do not change. Existing layouts without loaders do not change.
156
+
157
+ For an explicit return annotation, import `type LayoutLoaderResult` from `camox/createLayout` and use `Promise<LayoutLoaderResult<Pokemon>>` instead of `Promise<Pokemon>`. Inline loaders infer their payload automatically. Standalone loader functions can use `LayoutLoaderResult<Pokemon>` (or `satisfies LayoutLoaderResult<Pokemon>`) to keep `kind` a literal rather than a widened string. Wrappers are ordinary functions returning the same envelope; no attached properties or discovery phase are involved.
158
+
159
+ The playground's `/pokedex`, `/pokemon/pikachu`, and `/pokemon-types/electric` are working external-data examples, and both derived layouts declare `routes`, so `/sitemap.xml` lists every Pokémon and type. Load a URL directly, follow a Pokémon/type link, and refresh: `useData()` receives the same payload in server rendering, navigation, and hydration. Search and “Show more” still run on client state. A nonexistent Pokémon still returns 404; a PokéAPI failure is still a server error.
160
+
161
+ `kind: "data"` does not make external data editable. For collection records, use [Record pages](#record-pages).
162
+
163
+ ## Metadata and Markdown
164
+
165
+ Singleton and derived pages get the same head as curated pages: title, description, `og:*` tags, the `/og` image, and the favicon. They have no editor metadata, so the callbacks receive the loader's unwrapped `data` and the route `params` instead:
166
+
167
+ ```tsx
168
+ export const Layout = createLayout("pokemon.$name")({
169
+ kind: "derived",
170
+ // Other options omitted.
171
+ loader: async ({ params }) => ({ kind: "data", data: await loadPokemon(params.name) }),
172
+ buildMetaTitle: ({ data, pageMetaTitle }) => `${data.name} – ${pageMetaTitle}`,
173
+ buildMetaDescription: ({ data }) => `${data.name} is a ${data.types.join("/")} type Pokémon.`,
174
+ toMarkdown: ({ data, params }) => [`# ${data.name}`, `Types: ${data.types.join(", ")}`],
175
+ });
176
+ ```
177
+
178
+ - **Put `loader` before `buildMetaTitle`, `buildMetaDescription`, and `toMarkdown`.** TypeScript infers their `data` from the loader only in that order. Otherwise the loader is inferred as `never` and fails to type-check.
179
+ - `buildMetaTitle` receives `pageMetaTitle` (the layout's `title` on these pages), `projectName`, `pageFullPath`, `data`, and `params`. The `/og` image shows its result.
180
+ - `buildMetaDescription` is optional; without it these pages have no description.
181
+ - `toMarkdown` returns the page's Markdown Representation as a list of strings. Empty strings are dropped and the rest joined with blank lines. A request with `Accept: text/markdown` gets YAML front matter (the `title` and `description` the head shows, and the page's `url`), then the live before-block Markdown, then this output, then the live after-block Markdown. Curated pages' Markdown opens with the same front matter. Without `toMarkdown`, the page answers HTML only. `throw notFound()` in the loader answers 404. Curated layouts cannot declare `toMarkdown`: their Markdown comes from their blocks.
182
+ - The playground's Pokémon, Pokémon type, and Pokédex layouts declare these. Try `curl -H "Accept: text/markdown" http://localhost:3000/pokemon/pikachu`.
65
183
 
66
184
  ## File and component conventions
67
185
 
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: camox-expand
3
+ description: Expand a Camox project's Block Definitions ahead of need by finding jobs they can't do yet, proposing new blocks and Settings, and building the approved ones. Use when asked to add more blocks or prepare blocks before a launch.
4
+ ---
5
+
6
+ # Camox Expand
7
+
8
+ Work in an existing Camox project. Grow its Block Definitions so editors can do more with content changes alone. Every addition fills a real **gap**: a job the current Block Definitions can't do, or can only do in a shape or visual treatment the site already repeats. Each one must keep the site's own design.
9
+
10
+ ## 1. Ask about what's next
11
+
12
+ Ask the operator one question: what is being launched or published next? Use the answer to rank gaps. If they have nothing planned, the other evidence decides.
13
+
14
+ ## 2. Inventory the Block Definitions
15
+
16
+ Read every Block Definition in `src/blocks/`. For each one, note its job, content shape, visual treatment, and Settings. Also look at the rendered site to learn its design language. You're done when you can say which jobs each block does and how two blocks for the same job differ.
17
+
18
+ ## 3. Find gaps
19
+
20
+ Collect evidence in this order:
21
+
22
+ 1. **Upcoming needs** from the operator's answer.
23
+ 2. **The jobs checklist.** Compare [Jobs](references/jobs.md) with the Block Definitions. A job counts as covered only when an existing block, or an existing block with a Setting, does it well.
24
+ 3. **The source site**, when the project was migrated from one. Look for sections and page types that `camoxify-progress.md` or the current Pages don't reproduce.
25
+ 4. **Competitors' sites**, only when the site's category is clear. Take the jobs their pages and sections do, and leave their designs and copy alone. Every suggestion is designed in the site's own visual language.
26
+
27
+ Decide between a new block and a Setting with Camoxify's "Reuse before creating" rule in [Inspect and decompose](../camoxify/SKILL.md#inspect-and-decompose).
28
+
29
+ You're done when every evidence source that applies has been checked, every job in the checklist is marked covered or a gap, and every gap is marked as a new block or a Setting.
30
+
31
+ ## 4. Propose and wait for approval
32
+
33
+ List the suggestions in the chat. Each one has:
34
+
35
+ - its **job**,
36
+ - its **kind**: new block, or new Setting on an existing block,
37
+ - the **closest existing block**,
38
+ - a **text wireframe** for a new block, or a **before/after sketch** for a new Setting, in a fenced code block so it renders in any harness.
39
+
40
+ ```text
41
+ Logo wall (new block). Job: social proof. Closest: feature-grid
42
+ +----------------------------------------------+
43
+ | Trusted by teams shipping daily |
44
+ | [logo] [logo] [logo] [logo] [logo] |
45
+ +----------------------------------------------+
46
+ ```
47
+
48
+ Stop suggesting when the next idea would be filler; there is no target count. Then wait. The operator approves, cuts items, or asks for more or fewer. Revise until they approve a list. Nothing is built before that.
49
+
50
+ ## 5. Build the approved list
51
+
52
+ Make only these kinds of change:
53
+
54
+ - **New Block Definitions**, following [Block Definitions](../camox/references/block-definitions.md), [Localized code](../camox/SKILL.md#localized-code) (every visible text in fields, internal links through `useLocalizedHref` without a locale prefix), and Camoxify's rules on [styling](../camoxify/SKILL.md#use-the-existing-styling-stack), [genericity and default content](../camoxify/SKILL.md#keep-definitions-generic), [repeaters](../camoxify/SKILL.md#model-repetition-not-a-fixed-screenshot), [Settings](../camoxify/SKILL.md#use-settings-for-constrained-choices), and [formatted text](../camoxify/SKILL.md#keep-formatted-text-in-one-field).
55
+ - **New Settings on existing Block Definitions**, with defaults that leave current rendering unchanged. Existing content schemas stay exactly as they are. Before editing the block, capture it in the Block Catalog at desktop and mobile, opened at `/camox/blocks/<id>` on the running dev server; step 6 compares against this capture.
56
+ - **Description edits.** A new block's `description` says when to choose it over its siblings. Edit the siblings' descriptions so each one names what sets it apart, and the Agent can choose between them.
57
+
58
+ New blocks live only in code and the Block Catalog. Leave Pages, layouts, and publishing alone, and create no temporary Pages. This rules out new Layout-only Blocks: the Block Catalog never shows them, and they only sync once a layout uses them. An existing Layout-only Block that gains a Setting has no Block Catalog page either, so capture and check it on a Page whose layout uses it.
59
+
60
+ ## 6. Verify
61
+
62
+ Finish with a Block Catalog link to each new block, so the operator and the product marketing team can review them live. Delete any block the operator rejects, along with any description changes made only for it.
63
+
64
+ The run is complete when:
65
+
66
+ - every approved item is built, and every rejected one is deleted,
67
+ - the project's `check` script passes, and so does `vp lint --type-aware --type-check`, since `vp check` skips type errors unless the project's Vite config sets `lint.options.typeCheck`,
68
+ - every new block renders correctly with its default content at desktop and mobile in the Block Catalog at `/camox/blocks/<id>`,
69
+ - every existing block that gained a Setting renders the same as its capture with default Settings.
@@ -0,0 +1,39 @@
1
+ # Jobs
2
+
3
+ Jobs that marketing pages and sections commonly do. Each entry describes what the content achieves for the visitor, never how it looks. Design every block for a job in the site's own visual language.
4
+
5
+ ## Section jobs
6
+
7
+ - **Introduction**: say what the product or page is and who it is for.
8
+ - **Explanation**: show how the product works or what a feature does.
9
+ - **Benefits**: turn features into outcomes for the visitor.
10
+ - **Social proof**: show that others use and trust the product, through customers, quotes, numbers, ratings, or press.
11
+ - **Credibility**: back claims with certifications, security, compliance, or awards.
12
+ - **Objection handling**: answer the doubts that stop a visitor from acting, such as questions, risks, or "why not the alternative".
13
+ - **Comparison**: set the product against alternatives, plans, or the status quo.
14
+ - **Pricing**: say what it costs and what each option includes.
15
+ - **Conversion**: ask the visitor to act, such as sign up, buy, book, or download.
16
+ - **Lead capture**: collect contact details in exchange for something.
17
+ - **Demonstration**: let the visitor see the product working, through media, a demo, or an interactive sample.
18
+ - **Process**: walk through the steps to get started or to get a result.
19
+ - **Integration**: show what the product connects to.
20
+ - **Team and story**: introduce the people and the reasons behind the product.
21
+ - **Announcement**: flag something new or time-limited.
22
+ - **Navigation aid**: help visitors find related pages, resources, or the next step.
23
+ - **Long-form reading**: carry an article, guide, or document.
24
+
25
+ ## Page jobs
26
+
27
+ - **Launch page**: present a new product or feature.
28
+ - **Comparison page**: argue for the product against a named alternative.
29
+ - **Customer story**: tell one customer's problem, solution, and results.
30
+ - **Use-case or industry page**: speak to one audience or problem.
31
+ - **Pricing page**: let visitors choose a plan.
32
+ - **Event registration**: get visitors to sign up for a webinar, meetup, or conference.
33
+ - **Changelog**: record what changed and when.
34
+ - **Resource or download page**: offer a guide, report, or template.
35
+ - **Integration page**: explain one integration.
36
+ - **About page**: explain who is behind the product.
37
+ - **Careers page**: attract applicants.
38
+ - **Contact page**: route visitors to the right channel.
39
+ - **Legal page**: carry terms, privacy, or policy text.
@@ -7,6 +7,14 @@ description: Turn an existing website, screenshot, or design into an editable Ca
7
7
 
8
8
  Work in the existing Camox project; project creation and authentication are onboarding prerequisites. Match the reference while making the result reusable and easy to edit.
9
9
 
10
+ ## Scope the run
11
+
12
+ Take the scope from the request. A first run usually covers the shell and the homepage; a later run may cover the whole site, reusing what earlier runs built.
13
+
14
+ For multi-page runs, build the page list before coding: read the sitemap (`/sitemap.xml`, or the one named in `/robots.txt`) and follow navigation and footer links. Group pages that share one structure, such as blog posts, case studies, or docs articles, into a **page family**: one layout and set of blocks, built and verified on a representative page, then repeated for the rest through the CLI. Confirm the list and its families with the user before building.
15
+
16
+ Track progress in `camoxify-progress.md` at the project root: one line per page with its status (pending, built, populated, verified) and any new definitions it introduced. Update it as each page advances. When resuming, start from that file and the project's current definitions.
17
+
10
18
  ## Inspect and decompose
11
19
 
12
20
  Inspect the source URL with available browsing tools, or use the supplied screenshot/design. If the URL is inaccessible, ask for screenshots. Check multiple pages and viewport sizes when available; don't infer a whole site's structure from one crop.
@@ -16,23 +24,47 @@ For URL references, use a browser when available to explore interactive states:
16
24
  Before coding, map the reference to:
17
25
 
18
26
  - **Layout:** the consistent shell across pages, such as navigation and footer. Editable shell content still lives in blocks, placed by the layout.
19
- - **Page blocks:** independently useful sections such as a hero, feature grid, testimonials, or pricing. Reuse existing definitions where they fit. Avoid both a monolithic page block and separate blocks for every heading or card.
27
+ - **Page blocks:** independently useful sections such as a hero, feature grid, testimonials, or pricing. Avoid both a monolithic page block and separate blocks for every heading or card.
28
+
29
+ Reuse before creating. When a section matches an existing definition's content shape and visual treatment, use it, adding a setting for a small variation. Create a new definition when the visual treatment or the content shape is meaningfully different; distinct designs for the same job keep pages varied. A new definition's `description` says when to choose it over similar existing blocks.
30
+
20
31
  - **Fields and repeaters:** editable content within each section, grouped by meaning rather than visual position.
32
+ - **Artifacts:** bespoke visuals found on the reference, such as animated illustrations, 3D scenes, or one-off interactive widgets. Recreate each as an artifact source on a `field.artifact` of the block around it, not as a one-off block. Read [Artifacts](../camox/references/artifacts.md).
21
33
 
22
34
  Read [Block Definitions](../camox/references/block-definitions.md) and, when creating or changing a shell, [Layout Definitions](../camox/references/layout-definitions.md).
23
35
 
36
+ ## Use the existing styling stack
37
+
38
+ Learn the project's **Theme** before styling: read its main stylesheet, the CSS file that imports Tailwind. Its tokens and the comments beside them say what each color, font, text size, radius and spacing is for, and which **Color Schemes** a block can switch to. Take each token's meaning from its comment, not from its name.
39
+
40
+ Reuse the project's **Components**, such as its button or card, and compose or extend them to match the reference. Build a new Component only when none fits.
41
+
42
+ Style through the Theme's utilities rather than raw colors or stock palette classes, so a rebrand stays one stylesheet edit. To match the reference's colors, fonts and radii, change the Theme's values in the stylesheet; when the reference needs a role the Theme lacks, add a token there with a comment explaining it.
43
+
44
+ If the reference uses another styling system, recreate its appearance and behavior with the project's Components and Tailwind utilities; do not copy or introduce the reference's styling dependencies.
45
+
46
+ Express layout, spacing, typography, colors, responsive behavior, and interactive states through Tailwind and the project's Components. Do not install other styling libraries or add separate stylesheets or inline styles.
47
+
24
48
  ## Keep definitions generic
25
49
 
26
50
  Block names, schemas, rendering, and **default content** must work on other pages. Use `feature-grid`, not `acme-homepage-features`. Defaults are neutral examples, not the reference's copy, links, assets, or item data.
27
51
 
28
52
  Apply reference-specific content **only to instances through the CLI**, never by changing definition defaults or hardcoding it in JSX.
29
53
 
54
+ Keep the result translatable, following [Localized code](../camox/SKILL.md#localized-code): every visible text in the shell, navigation and footer included, lives in blocks, and internal links go through `useLocalizedHref`, written without a locale prefix.
55
+
30
56
  ## Model repetition, not a fixed screenshot
31
57
 
32
- Use `Type.Repeater` for every repeated content structure: lists, rows, columns, tables, carousels, navigation items, and cards. Never use `field1`, `field2`, etc., or hardcoded content arrays. Nest repeaters for structures such as footer columns containing links or table rows containing cells.
58
+ Use `field.repeater` for every repeated content structure: lists, rows, columns, tables, carousels, navigation items, and cards. Never use `field1`, `field2`, etc., or hardcoded content arrays. Nest repeaters for structures such as footer columns containing links or table rows containing cells.
33
59
 
34
60
  Choose bounds from real constraints, not the number of items visible in the reference. Many sibling fields are a code smell: look for repeated groups or separate sections. Model enough flexibility to add, remove, reorder, and rewrite content without code changes; don't expose every CSS value as a field.
35
61
 
62
+ ## Use settings for constrained choices
63
+
64
+ Before adding a `content` field, ask whether the editor is writing free-form content or choosing from known options. For configuration choices such as alignment, theme, size, or variant, use `settings` with `setting.enum`. For on/off choices such as showing a background or enabling autoplay, use `settings` with `setting.boolean`.
65
+
66
+ Do not model these choices as `content` fields using `field.string`, even if rendering code could interpret strings such as `"left"`, `"dark"`, or `"true"`. Editors should choose a valid option or toggle a setting, not type a magic string. Reserve `content`'s `field.string` for genuinely free-form text, such as headings and descriptions. Enum defaults must match an option key.
67
+
36
68
  ## Keep formatted text in one field
37
69
 
38
70
  Use inline formatting and `textStyle`, including highlights, instead of fields such as `headingStart`, `coloredWord`, and `headingEnd`. Editors should be able to move the emphasis by editing content alone. Read [Field styling](../camox/references/field-styling.md).
@@ -50,3 +82,7 @@ Use inline formatting and `textStyle`, including highlights, instead of fields s
50
82
  Follow the [Camox CLI workflow](../camox/SKILL.md#content-and-cli) to assign layouts, place blocks, and populate content, including shared navigation/footer content. Don't publish unless asked.
51
83
 
52
84
  Compare the rendered site with the reference at desktop and mobile sizes. Verify that fields remain editable and that longer text and different repeater counts still work. Fix the reusable implementation, not just the one captured arrangement.
85
+
86
+ In multi-page runs, give this full check to every page that introduced or changed a definition, and to each page family's representative page. Compare the remaining pages with their source at desktop size. The run is complete when every page in `camoxify-progress.md` is verified.
87
+
88
+ Once the run is complete, suggest running `camox-expand` to build blocks for jobs the site will need next.