alinea 2.0.0-preview.13 → 2.0.0-preview.14

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 (323) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/.mcp/later/dev.json +1 -1
  3. package/dist/.mcp/online/dev.json +1 -1
  4. package/dist/adapter/next/cms.js +6 -2
  5. package/dist/adapter/next/context.js +1 -4
  6. package/dist/adapter/next/handler.js +12 -1
  7. package/dist/backend/CommitEntries.d.ts +17 -0
  8. package/dist/backend/CommitEntries.js +39 -0
  9. package/dist/backend/Handler.d.ts +9 -0
  10. package/dist/backend/Handler.js +80 -20
  11. package/dist/backend/api/BasicAuth.js +5 -1
  12. package/dist/backend/api/CreateBackend.js +2 -2
  13. package/dist/backend/api/DatabaseApi.js +18 -5
  14. package/dist/backend/api/GithubApi.d.ts +13 -0
  15. package/dist/backend/api/GithubApi.js +170 -30
  16. package/dist/backend/api/database.js +2 -2
  17. package/dist/backend/util/Syncable.d.ts +6 -0
  18. package/dist/backend/util/Syncable.js +3 -1
  19. package/dist/bundled.d.ts +911 -151
  20. package/dist/chunks/{chunk-5H4D5JQL.js → chunk-2PP7WJUJ.js} +1 -11
  21. package/dist/chunks/{chunk-QHHMKJUO.js → chunk-7DDPSWRY.js} +465 -2
  22. package/dist/chunks/{chunk-HOETF6HN.js → chunk-PMGIIYDD.js} +1 -0
  23. package/dist/chunks/{chunk-3QFSGTGF.js → chunk-SCV3TEXM.js} +1 -1
  24. package/dist/chunks/{chunk-UPY2RUZF.js → chunk-TWBREZWC.js} +30 -0
  25. package/dist/cli/Generate.js +20 -7
  26. package/dist/cli/Mcp.js +1 -1
  27. package/dist/cli/Serve.js +1 -1
  28. package/dist/cli/bin.js +1 -1
  29. package/dist/cli/build/BuildEmitter.d.ts +5 -1
  30. package/dist/cli/build/BuildEmitter.js +8 -3
  31. package/dist/cli/generate/CompileConfig.js +10 -6
  32. package/dist/cli/generate/DevDB.d.ts +4 -1
  33. package/dist/cli/generate/DevDB.js +15 -7
  34. package/dist/cli/generate/GenerateDashboard.js +1 -0
  35. package/dist/cli/generate/MediaLocations.d.ts +18 -0
  36. package/dist/cli/generate/MediaLocations.js +69 -0
  37. package/dist/cli/serve/CreateLocalServer.js +1 -0
  38. package/dist/cli/serve/mcp/DevMcp.js +1 -1
  39. package/dist/cloud/CloudRemote.js +21 -7
  40. package/dist/cms.d.ts +4 -2
  41. package/dist/components/Alert.js +2 -2
  42. package/dist/components/AppShell.js +2 -2
  43. package/dist/components/AsyncList.d.ts +52 -0
  44. package/dist/components/AsyncList.js +69 -0
  45. package/dist/components/Breadcrumbs.js +6 -6
  46. package/dist/components/Button.js +4 -4
  47. package/dist/components/Calendar.js +18 -18
  48. package/dist/components/Checkbox.js +10 -10
  49. package/dist/components/CheckboxGroup.js +2 -2
  50. package/dist/components/Collapsible.js +5 -5
  51. package/dist/components/ColorSwatch.js +2 -2
  52. package/dist/components/ColorSwatchPicker.js +4 -4
  53. package/dist/components/ComboBox.d.ts +27 -9
  54. package/dist/components/ComboBox.js +40 -9
  55. package/dist/components/Command.js +13 -13
  56. package/dist/components/ContentCard.js +28 -28
  57. package/dist/components/ContentGrid.js +11 -11
  58. package/dist/components/DataList.js +1 -1
  59. package/dist/components/DateField.d.ts +4 -0
  60. package/dist/components/DateField.js +2 -2
  61. package/dist/components/DatePicker.js +2 -2
  62. package/dist/components/DateRangePicker.js +2 -2
  63. package/dist/components/Dialog.d.ts +14 -3
  64. package/dist/components/Dialog.js +38 -11
  65. package/dist/components/DropZone.js +6 -6
  66. package/dist/components/DropdownMenu.js +8 -8
  67. package/dist/components/Empty.js +4 -4
  68. package/dist/components/Field.d.ts +1 -1
  69. package/dist/components/Field.js +18 -12
  70. package/dist/components/Form.d.ts +23 -0
  71. package/dist/components/Form.js +64 -0
  72. package/dist/components/Link.d.ts +8 -1
  73. package/dist/components/List.js +27 -27
  74. package/dist/components/MediaPreview.js +5 -5
  75. package/dist/components/MultipleSelect.d.ts +13 -8
  76. package/dist/components/MultipleSelect.js +25 -25
  77. package/dist/components/NavRail.js +7 -7
  78. package/dist/components/NumberField.d.ts +5 -1
  79. package/dist/components/NumberField.js +6 -4
  80. package/dist/components/Page.js +6 -6
  81. package/dist/components/Popover.js +2 -2
  82. package/dist/components/PreviewFrame.js +8 -8
  83. package/dist/components/RadioGroup.js +10 -10
  84. package/dist/components/Resizable.js +1 -1
  85. package/dist/components/SearchField.js +8 -8
  86. package/dist/components/Select.d.ts +13 -10
  87. package/dist/components/Select.js +18 -32
  88. package/dist/components/Sheet.js +6 -6
  89. package/dist/components/Sidebar.js +5 -5
  90. package/dist/components/SortableList.js +12 -12
  91. package/dist/components/Spinner.js +4 -4
  92. package/dist/components/Stack.d.ts +20 -0
  93. package/dist/components/Stack.js +45 -0
  94. package/dist/components/Surface.js +1 -1
  95. package/dist/components/Switch.js +5 -5
  96. package/dist/components/Table.d.ts +11 -1
  97. package/dist/components/Table.js +81 -53
  98. package/dist/components/Tabs.js +6 -6
  99. package/dist/components/TagGroup.js +10 -10
  100. package/dist/components/TextField.d.ts +7 -1
  101. package/dist/components/TextField.js +20 -11
  102. package/dist/components/TimeField.d.ts +6 -1
  103. package/dist/components/TimeField.js +51 -26
  104. package/dist/components/Timestamp.js +1 -1
  105. package/dist/components/ToggleGroup.js +2 -2
  106. package/dist/components/Toolbar.js +3 -3
  107. package/dist/components/Tooltip.js +5 -4
  108. package/dist/components/Tree.js +27 -27
  109. package/dist/components/internal/DateControl.js +11 -11
  110. package/dist/components/internal/FormScope.d.ts +2 -0
  111. package/dist/components/internal/FormScope.js +8 -0
  112. package/dist/components/internal/ListBoxGroup.d.ts +12 -0
  113. package/dist/components/internal/ListBoxGroup.js +46 -0
  114. package/dist/components/internal/ListBoxOption.d.ts +2 -2
  115. package/dist/components/internal/ListBoxOption.js +5 -5
  116. package/dist/components/internal/Locale.js +2 -2
  117. package/dist/components/internal/PopoverSurface.js +5 -4
  118. package/dist/components/internal/SelectionCheckbox.js +5 -5
  119. package/dist/components/internal/Slot.js +1 -1
  120. package/dist/components/internal/ToggleButton.js +4 -4
  121. package/dist/components/internal/Trigger.js +2 -2
  122. package/dist/components/internal/useDragDrop.js +2 -2
  123. package/dist/components/types.d.ts +4 -0
  124. package/dist/components.d.ts +3 -0
  125. package/dist/components.js +3 -0
  126. package/dist/core/Client.js +12 -1
  127. package/dist/core/Config.d.ts +6 -0
  128. package/dist/core/Config.js +12 -0
  129. package/dist/core/DashboardEntry.d.ts +19 -0
  130. package/dist/core/DashboardEntry.js +0 -0
  131. package/dist/core/Document.d.ts +2 -2
  132. package/dist/core/Document.js +10 -3
  133. package/dist/core/EntryFields.d.ts +8 -2
  134. package/dist/core/Field.d.ts +5 -0
  135. package/dist/core/Filter.d.ts +19 -2
  136. package/dist/core/Filter.js +5 -0
  137. package/dist/core/Graph.d.ts +10 -2
  138. package/dist/core/Overview.d.ts +74 -5
  139. package/dist/core/Overview.js +1 -0
  140. package/dist/core/Root.js +1 -1
  141. package/dist/core/Scope.d.ts +5 -0
  142. package/dist/core/Scope.js +22 -5
  143. package/dist/core/Type.d.ts +25 -6
  144. package/dist/core/Type.js +25 -7
  145. package/dist/core/db/LocalStore.d.ts +7 -0
  146. package/dist/core/field/CreateField.d.ts +1 -2
  147. package/dist/core/source/Source.d.ts +2 -0
  148. package/dist/core/source/Source.js +11 -0
  149. package/dist/core/util/Async.d.ts +2 -0
  150. package/dist/core/util/Async.js +12 -1
  151. package/dist/core/util/Checks.js +1 -1
  152. package/dist/core/util/Slugs.js +4 -3
  153. package/dist/dashboard/App.css +13 -11
  154. package/dist/dashboard/App.d.ts +1 -0
  155. package/dist/dashboard/App.js +22 -6
  156. package/dist/dashboard/Href.d.ts +37 -0
  157. package/dist/dashboard/Href.js +46 -0
  158. package/dist/dashboard/app/ActivityStatus.js +34 -12
  159. package/dist/dashboard/app/AuthView.js +2 -2
  160. package/dist/dashboard/app/BlockSettings.d.ts +9 -0
  161. package/dist/dashboard/app/BlockSettings.js +16 -0
  162. package/dist/dashboard/app/BlockSheet.js +3 -3
  163. package/dist/dashboard/app/BlockSheetStoryFrame.js +2 -2
  164. package/dist/dashboard/app/CompactField.js +15 -15
  165. package/dist/dashboard/app/CroppedImage.js +2 -2
  166. package/dist/dashboard/app/DashboardLayout.d.ts +1 -1
  167. package/dist/dashboard/app/DashboardLayout.js +12 -0
  168. package/dist/dashboard/app/EntryActions.d.ts +11 -0
  169. package/dist/dashboard/app/EntryActions.js +30 -0
  170. package/dist/dashboard/app/EntryFields.d.ts +15 -2
  171. package/dist/dashboard/app/EntryFields.js +26 -5
  172. package/dist/dashboard/app/EntryHeader.js +17 -7
  173. package/dist/dashboard/app/EntryReferences.js +2 -2
  174. package/dist/dashboard/app/EntrySidebar.js +25 -23
  175. package/dist/dashboard/app/EntrySidebarPreview.js +2 -2
  176. package/dist/dashboard/app/EntryValidationModal.js +3 -3
  177. package/dist/dashboard/app/Explorer.js +84 -45
  178. package/dist/dashboard/app/ExplorerBatchActions.js +4 -4
  179. package/dist/dashboard/app/ExplorerControls.js +5 -5
  180. package/dist/dashboard/app/ExplorerList.js +2 -2
  181. package/dist/dashboard/app/ExplorerModal.js +3 -3
  182. package/dist/dashboard/app/ExplorerOverviewHeader.d.ts +6 -0
  183. package/dist/dashboard/app/ExplorerOverviewHeader.js +28 -0
  184. package/dist/dashboard/app/ExplorerTable.js +7 -7
  185. package/dist/dashboard/app/ExplorerTabs.d.ts +12 -0
  186. package/dist/dashboard/app/ExplorerTabs.js +42 -0
  187. package/dist/dashboard/app/ImageEditor.js +31 -31
  188. package/dist/dashboard/app/LinkPicker.js +3 -3
  189. package/dist/dashboard/app/LocaleMenu.js +9 -9
  190. package/dist/dashboard/app/MediaPreviewCell.js +5 -5
  191. package/dist/dashboard/app/OverviewCell.js +12 -10
  192. package/dist/dashboard/app/RemoveDialog.js +5 -0
  193. package/dist/dashboard/app/SearchBar.js +4 -4
  194. package/dist/dashboard/app/SettingsTab.d.ts +6 -0
  195. package/dist/dashboard/app/SettingsTab.js +27 -0
  196. package/dist/dashboard/app/SidebarTree.js +19 -19
  197. package/dist/dashboard/app/WorkspaceMenu.js +6 -6
  198. package/dist/dashboard/app/WorkspaceRoots.js +102 -98
  199. package/dist/dashboard/app/editor/FileEditor.js +18 -18
  200. package/dist/dashboard/app/pages/EntryPage.js +42 -39
  201. package/dist/dashboard/app/pages/RootPage.js +3 -3
  202. package/dist/dashboard/app/pages/SplashPage.js +94 -79
  203. package/dist/dashboard/app/pages/UsersPage.js +9 -9
  204. package/dist/dashboard/app/ui/DashboardModal.js +14 -14
  205. package/dist/dashboard/atoms/activity.d.ts +1 -0
  206. package/dist/dashboard/atoms/activity.js +8 -5
  207. package/dist/dashboard/atoms/config.d.ts +2 -0
  208. package/dist/dashboard/atoms/dashboard.d.ts +11 -0
  209. package/dist/dashboard/atoms/dashboard.js +8 -0
  210. package/dist/dashboard/atoms/editor.d.ts +7 -1
  211. package/dist/dashboard/atoms/editor.js +14 -2
  212. package/dist/dashboard/atoms/explorer.d.ts +15 -0
  213. package/dist/dashboard/atoms/explorer.js +43 -14
  214. package/dist/dashboard/atoms/graph.d.ts +5 -0
  215. package/dist/dashboard/atoms/graph.js +26 -1
  216. package/dist/dashboard/atoms/nav.d.ts +16 -10
  217. package/dist/dashboard/atoms/nav.js +96 -36
  218. package/dist/dashboard/atoms/overview.d.ts +26 -4
  219. package/dist/dashboard/atoms/overview.js +41 -12
  220. package/dist/dashboard/atoms/root.js +7 -1
  221. package/dist/dashboard/boot/ActivityEvent.d.ts +1 -1
  222. package/dist/dashboard/boot/Boot.js +6 -0
  223. package/dist/dashboard/boot/DashboardWorker.d.ts +6 -0
  224. package/dist/dashboard/boot/DashboardWorker.js +89 -8
  225. package/dist/dashboard/boot/LoadWorker.js +3 -0
  226. package/dist/dashboard/boot/SyncEvent.d.ts +7 -0
  227. package/dist/dashboard/boot/SyncEvent.js +14 -0
  228. package/dist/dashboard/boot/WorkerDB.d.ts +1 -0
  229. package/dist/dashboard/boot/WorkerDB.js +3 -0
  230. package/dist/dashboard/cms/EntryTable.d.ts +4 -3
  231. package/dist/dashboard/cms/EntryTable.js +2 -2
  232. package/dist/dashboard/cms/ViewProps.d.ts +3 -2
  233. package/dist/dashboard/cms/hooks.d.ts +34 -18
  234. package/dist/dashboard/cms/hooks.js +43 -18
  235. package/dist/dashboard/contexts.d.ts +3 -2
  236. package/dist/dashboard/contexts.js +3 -2
  237. package/dist/dashboard/hook/UseEntryEditor.d.ts +1 -1
  238. package/dist/dashboard/hook/UseEntryEditor.js +7 -3
  239. package/dist/dashboard/icons.d.ts +1 -0
  240. package/dist/dashboard/icons.js +15 -0
  241. package/dist/database/BrowserEntryStore.js +35 -13
  242. package/dist/database/DatabaseTables.js +1 -1
  243. package/dist/database/EntryDatabase.d.ts +10 -1
  244. package/dist/database/EntryDatabase.js +31 -10
  245. package/dist/database/EntryStore.js +1 -11
  246. package/dist/database/entry/EntryTable.d.ts +5 -3
  247. package/dist/database/entry/EntryTable.js +25 -8
  248. package/dist/database/query/Condition.d.ts +4 -1
  249. package/dist/database/query/Condition.js +16 -5
  250. package/dist/database/query/EntryQuery.js +59 -7
  251. package/dist/database/query/Relation.d.ts +13 -1
  252. package/dist/database/query/Relation.js +19 -1
  253. package/dist/database/query/Search.d.ts +7 -1
  254. package/dist/database/query/Search.js +54 -9
  255. package/dist/database/sync/EntrySyncer.d.ts +2 -0
  256. package/dist/database/sync/EntrySyncer.js +4 -2
  257. package/dist/database/sync/Ingest.d.ts +2 -1
  258. package/dist/database/sync/Ingest.js +20 -6
  259. package/dist/field/code/CodeField.view.js +12 -12
  260. package/dist/field/link/EntryLink.d.ts +4 -5
  261. package/dist/field/link/FileLink.d.ts +2 -2
  262. package/dist/field/link/ImageLink.d.ts +2 -5
  263. package/dist/field/link/ImageLink.js +0 -1
  264. package/dist/field/link/LinkField.view.js +18 -15
  265. package/dist/field/list/ListField.view.js +50 -35
  266. package/dist/field/metadata/MetadataField.view.js +14 -14
  267. package/dist/field/richtext/RichTextBlock.js +39 -35
  268. package/dist/field/richtext/RichTextExtensions.js +23 -23
  269. package/dist/field/richtext/RichTextField.view.js +6 -6
  270. package/dist/field/richtext/RichTextInsertMenu.js +5 -5
  271. package/dist/field/richtext/RichTextToolbar.js +26 -1
  272. package/dist/field/richtext/Toolbar.d.ts +9 -1
  273. package/dist/field/richtext/Toolbar.js +12 -2
  274. package/dist/field/select/SelectField.view.js +1 -0
  275. package/dist/index.css +174 -23
  276. package/dist/index.d.ts +1 -0
  277. package/dist/index.js +3 -1
  278. package/dist/next.d.ts +2 -0
  279. package/dist/next.edge.d.ts +2 -0
  280. package/dist/picker/entry/EntryPicker.d.ts +3 -1
  281. package/dist/picker/entry/EntryPicker.js +15 -1
  282. package/dist/preview/react.js +3 -0
  283. package/dist/query.d.ts +26 -14
  284. package/dist/query.js +4 -0
  285. package/dist/types.d.ts +8 -5
  286. package/dist/ui/Stack.d.ts +4 -2
  287. package/docs/ai-setup.md +3 -3
  288. package/docs/cli.md +4 -2
  289. package/docs/components/combo-box.md +105 -16
  290. package/docs/components/date-field.md +1 -1
  291. package/docs/components/dialog.md +51 -0
  292. package/docs/components/form.md +67 -0
  293. package/docs/components/link.md +10 -0
  294. package/docs/components/multiple-select.md +9 -17
  295. package/docs/components/number-field.md +5 -1
  296. package/docs/components/select.md +9 -20
  297. package/docs/components/stack.md +42 -0
  298. package/docs/components/table.md +83 -0
  299. package/docs/components/text-field.md +9 -1
  300. package/docs/components/time-field.md +5 -1
  301. package/docs/components.md +39 -1
  302. package/docs/configuration.md +3 -1
  303. package/docs/custom-fields.md +135 -7
  304. package/docs/deploy/self-host.md +2 -0
  305. package/docs/deploy.md +7 -4
  306. package/docs/fields/image.md +0 -1
  307. package/docs/fields/list.md +19 -0
  308. package/docs/fields/object.md +5 -3
  309. package/docs/fields/rich-text.md +31 -0
  310. package/docs/index.md +8 -6
  311. package/docs/instant-publishing.md +23 -2
  312. package/docs/overviews.md +128 -4
  313. package/docs/query/filtering.md +2 -0
  314. package/docs/query/related.md +58 -9
  315. package/docs/query.md +52 -1
  316. package/docs/quickstart.md +5 -5
  317. package/docs/schema/document.md +2 -0
  318. package/docs/schema/type.md +3 -1
  319. package/docs/upgrading.md +100 -19
  320. package/llms-full.txt +1022 -125
  321. package/package.json +1 -1
  322. package/readme.md +2 -2
  323. package/dist/chunks/{chunk-4ZA6KZHS.js → chunk-6FQ5IMNA.js} +3 -3
@@ -34,6 +34,7 @@ Components are the React components the dashboard is built from, import them fro
34
34
  - [ColorSwatchPicker](components/color-swatch-picker.md): Picks a color from a set of swatches.
35
35
  - [DropZone](components/drop-zone.md): A target to drop files or other content on.
36
36
  - [Field](components/field.md): The label, help text and error around your own input.
37
+ - [Form](components/form.md): Submits its fields and shows their validation errors.
37
38
 
38
39
  ### Collections
39
40
  - [Table](components/table.md): Rows and columns with sorting, selection and dragging.
@@ -67,6 +68,7 @@ Components are the React components the dashboard is built from, import them fro
67
68
  - [FoldIcon](components/fold-icon.md): A chevron that turns when its section opens.
68
69
 
69
70
  ### Layout
71
+ - [Stack](components/stack.md): Lays out its children in a row or column with a gap.
70
72
  - [Surface](components/surface.md): A bordered panel for a group of content.
71
73
  - [Collapsible](components/collapsible.md): A section that opens and closes.
72
74
  - [Page](components/page.md): The header, content and footer of a dashboard page.
@@ -111,7 +113,7 @@ export function PriorityView({field}: FieldViewProps<PriorityField>) {
111
113
  }
112
114
  ```
113
115
 
114
- The components are styled by the dashboard's stylesheet and theme, so they are meant for code that runs inside the dashboard.
116
+ The components are styled by the dashboard's stylesheet and theme. They are meant for code that runs inside the dashboard, but work on other pages too (see Theme tokens below).
115
117
 
116
118
  ## Conventions
117
119
 
@@ -129,3 +131,39 @@ The props of every component are typed, so your editor shows what each one accep
129
131
  ## Icons
130
132
 
131
133
  Components with an `icon` prop take an icon component (any component that renders an `<svg>`, such as those from [react-icons](https://react-icons.github.io/react-icons) or copied from [Icones](https://icones.js.org)) or an element. The icons of the dashboard itself are exported from `alinea/dashboard/icons`.
134
+
135
+ ## Theme tokens
136
+
137
+ The components are styled with CSS custom properties. `alinea/css` declares them, and its resets, only on elements with a `data-alinea-theme` attribute, so loading the stylesheet doesn't restyle the rest of a page. The dashboard sets the attribute on `<html>`, and popovers, dialogs and tooltips set it on themselves since they render outside the page. To use the components on a page of your site, import `alinea/css` and render them inside an element with `data-alinea-theme`. Add `data-theme="light"` or `data-theme="dark"` to that element to fix the color scheme, otherwise it follows the page.
138
+
139
+ A theme only sets the inputs, as `light-dark()` pairs where a value differs per color scheme. Every other token is derived from them. Set them on the element with `data-alinea-theme`, for example `<div data-alinea-theme className="brand-theme">`:
140
+
141
+ File: theme.css
142
+ ```css
143
+ .brand-theme {
144
+ --alinea-radius: 6px;
145
+ --alinea-bg: light-dark(#ffffff, #18181b);
146
+ --alinea-fg: light-dark(#27272a, #e4e4e7);
147
+ --alinea-primary: light-dark(#2f6b45, #5fbf82);
148
+ }
149
+ ```
150
+
151
+ Popovers, dialogs and tooltips render outside that element and carry the attribute themselves, so a class on the page doesn't reach them: use `[data-alinea-theme]` as the selector to theme every part of the components on a page.
152
+
153
+ Inputs:
154
+
155
+ - `--alinea-font`, `--alinea-mono-font`, `--alinea-font-size-base` and `--alinea-radius`.
156
+ - `--alinea-backdrop` (behind panels), `--alinea-bg` (panels), `--alinea-fg` (text) and `--alinea-border`.
157
+ - `--alinea-primary`, `--alinea-danger`, `--alinea-warning` and `--alinea-success`, with `--alinea-primary-fg`, `--alinea-danger-fg` and `--alinea-warning-fg` for text on them.
158
+ - `--alinea-ring`: the focus ring.
159
+ - `--alinea-workspace-color`: the `color` option of the current workspace, set by the dashboard. It falls back to `--alinea-primary`.
160
+
161
+ Derived tokens, to read in your own styles:
162
+
163
+ - Text: `--alinea-fg-strong`, `--alinea-fg-muted`, `--alinea-fg-disabled` and `--alinea-field-label`.
164
+ - Surfaces: `--alinea-bg-muted`, `--alinea-muted` (hover backgrounds), `--alinea-input`, `--alinea-overlay`, `--alinea-scrim` (behind dialogs) and `--alinea-border-strong`.
165
+ - Tones: `--alinea-selected` and `--alinea-selected-fg`, and a `-subtle` background with a `-subtle-fg` text color for `primary`, `danger`, `warning` and `success`, eg. `--alinea-danger-subtle`.
166
+ - Entry statuses: `--alinea-status-published`, `--alinea-status-draft` and `--alinea-status-unpublished`, each with a `-fg` color.
167
+ - Shape and motion: `--alinea-radius-sm`, `--alinea-radius-lg`, `--alinea-radius-xl`, `--alinea-shadow-raised`, `--alinea-shadow-muted`, `--alinea-shadow-tooltip`, `--alinea-shadow-modal` and `--alinea-transition`.
168
+
169
+ The color palette the default theme is built from (`--alinea-gray-500`, `--alinea-blue-800`, ...) is not part of this list and can change.
@@ -56,9 +56,11 @@ The URL of the API route that serves the dashboard and media files: the route th
56
56
 
57
57
  The URL of your website, either one string or one per environment: `{development, production}`, picked by `NODE_ENV`. A value without a protocol gets `https://`.
58
58
 
59
- - `alinea build` exits with `No baseUrl was set for the production build` when there is no production URL.
59
+ - It is required for production builds when `handlerUrl` is relative: `alinea build` exits, and queries on your site throw, with `Missing baseUrl in Alinea config: set baseUrl.production to the url of your site, …`. An absolute `handlerUrl` doesn't need it.
60
60
  - In production your site reaches the handler at `handlerUrl` resolved against this URL, to check for new content. That includes `next start` on your machine, which talks to the production URL.
61
61
 
62
+ Server renders have no request to take the origin from (static pages and pages from `generateStaticParams` are rendered without one, and reading the request headers would make every route dynamic), so Alinea doesn't guess it. Set `baseUrl.production`, or make `handlerUrl` absolute.
63
+
62
64
  Most projects read the production URL from an environment variable, see [Environment variables](#environment-variables) below.
63
65
 
64
66
  ## Dashboard options
@@ -28,7 +28,28 @@ export function range(
28
28
  }
29
29
  ```
30
30
 
31
- `Field.Create<Value, Options>` is the type of a field that stores `Value` (anything JSON can hold) and accepts your `Options` next to the standard ones: `initialValue`, `required`, `readOnly`, `hidden`, `shared` and `validate`. Queries return the stored value as it is.
31
+ `Field.Create<Value, Options>` is the type of a field that stores `Value` (anything JSON can hold) and accepts your `Options` next to the standard ones: `initialValue`, `required`, `readOnly`, `hidden`, `shared`, `validate` and `width`, the width of the field in the entry form. Queries return the stored value as it is.
32
+
33
+ To describe the options as an interface, extend `FieldOptions` from `alinea/core` and type the options argument with `WithoutLabel`:
34
+
35
+ File: fields/Rating.ts
36
+ ```tsx
37
+ import {Field} from 'alinea'
38
+ import type {FieldOptions, WithoutLabel} from 'alinea'
39
+
40
+ export interface RatingOptions extends FieldOptions<number> {
41
+ max?: number
42
+ }
43
+
44
+ export type RatingField = Field.Create<number, RatingOptions>
45
+
46
+ export function rating(
47
+ label: string,
48
+ options: WithoutLabel<RatingOptions> = {}
49
+ ): RatingField {
50
+ return Field.create({label, options, view: '@/fields/Rating.view'})
51
+ }
52
+ ```
32
53
 
33
54
  `Field.create` takes:
34
55
 
@@ -154,9 +175,13 @@ export function priority(
154
175
  }
155
176
  ```
156
177
 
178
+ The entry editor sets `--alinea-header-height` to the height of its header. A view that fills the visible editor can size itself with it, for example `height: calc(100dvh - var(--alinea-header-height))`.
179
+
180
+ Style your own markup with the [theme tokens](components.md#theme-tokens), such as `--alinea-fg-muted`, `--alinea-border` and `--alinea-radius`, so it follows light and dark mode. `--alinea-workspace-color` holds the `color` of the current workspace.
181
+
157
182
  ## The alinea/cms API
158
183
 
159
- Everything in `alinea/cms` works in field views and in the other components the dashboard renders: `Field.view` sections and custom type and root views.
184
+ Everything in `alinea/cms` works in field views and in the other components the dashboard renders for an entry: `Field.view` sections, entry `actions` and a type's `view`. A root's `view` is not part of an entry: the hooks that read the dashboard, such as `useGraph`, `useNavigate`, `useLocale` and `useUser`, and `EntryTable` work there, the field hooks, `EditField` and `EditFields` don't, and `useEntry()` returns `null`.
160
185
 
161
186
  Field hooks, for the field passed to a view or another field of the same entry, list row or object:
162
187
 
@@ -165,24 +190,55 @@ Field hooks, for the field passed to a view or another field of the same entry,
165
190
  - `useFieldOptions(field)`: the field's options, including `label` and the resolved `readOnly` and `hidden`.
166
191
  - `useFieldError(field)`: the validation message from `required` or `validate`, if any.
167
192
  - `useFieldKey(field)`: the key the field is stored under, such as `'title'`.
168
- - `useSiblingFieldValue(key)`: the value of another field in the same entry, list row or object, by its key.
193
+ - Other fields: pass any field of the same list row or object, of a parent row or of the entry to these hooks, it resolves in the nearest scope that holds it. Import the type from your schema (`useFieldValue(BlogPost.title)`) or pass the field through your field's options. A view shared by types that each define their own field passes its key instead: `useFieldValue('path')` reads that field of the same row, untyped. `useSiblingFieldValue(key)` is deprecated.
194
+
195
+ A field in the rows of a list can read a field of the entry, here to suggest the post title as a caption:
196
+
197
+ File: fields/Caption.view.tsx
198
+ ```tsx
199
+ import {
200
+ type FieldViewProps,
201
+ useField,
202
+ useFieldOptions,
203
+ useFieldValue
204
+ } from 'alinea/cms'
205
+ import {TextField} from 'alinea/components'
206
+ import {BlogPost} from '@/schema'
207
+ import type {CaptionField} from './Caption'
208
+
209
+ export function CaptionView({field}: FieldViewProps<CaptionField>) {
210
+ const [value, setValue] = useField(field)
211
+ const {label} = useFieldOptions(field)
212
+ // A field of the entry, read from inside a list row
213
+ const title = useFieldValue(BlogPost.title)
214
+ return (
215
+ <TextField
216
+ label={label}
217
+ value={value}
218
+ placeholder={title}
219
+ onValueChange={setValue}
220
+ />
221
+ )
222
+ }
223
+ ```
169
224
 
170
225
  Entry and dashboard hooks:
171
226
 
172
- - `useEntry()`: the entry being edited, with its `id`, `title`, `url`, `workspace`, `root`, `locale`, `status` and the stored `data`. `null` outside an entry.
227
+ - `useEntry()`: the entry being edited, with its `id`, `type`, `title`, `path`, `url`, `locale`, `status`, `workspace`, `root`, `parentId` and `data`. `data` holds the field values as saved, read the live value of a field while it is edited with `useFieldValue(field)`. `null` outside an entry.
173
228
  - `useLocale()`: the locale being edited, or `null` for content without translations.
174
229
  - `useGraph()`: query content from the dashboard with the same API as `cms`, for example to list entries in a picker.
175
230
  - `useUser()`, `usePolicy()`: the signed-in user and their [permissions](roles-permissions.md).
176
- - `useNavigate()`: returns a function that opens an entry or root in the dashboard: `navigate({workspace, root, entryId})`. The editor is asked to confirm first when there are unsaved changes.
231
+ - `useNavigate()`: returns a function that opens an entry or root in the dashboard: `navigate({entryId, locale})` looks up the entry's workspace and root, `navigate({workspace, root})` opens a root. Pass `view: 'edit'` or `view: 'overview'` to pick the form or the list of children. The editor is asked to confirm first when there are unsaved changes.
232
+ - `href(location)` from `alinea`: the href of the same locations, for a link that also opens in a new tab: `<a href={href({entryId: post.id})}>`. It is a plain function, so it also works in table rows, in the `help` of a field and on the server, where you prefix the dashboard's url: `${baseUrl}/admin${href({entryId})}`.
177
233
  - `usePreviewMetadata()`: the title, description and other metadata the live preview reports for the current page.
178
234
 
179
235
  Components and types:
180
236
 
181
237
  - `FieldChrome`: the label, help text and error of a field around your own control.
182
- - `EditField`, `EditFields`: render a field, or a record of fields, with the view it is configured with, for example to lay out existing fields in a `Field.view` section.
238
+ - `EditField`, `EditFields`: render a field, or a record of fields, with the view it is configured with, for example to lay out existing fields in a `Field.view` section. Pass an object field to `EditFields` to render its fields, or some of them, in the scope of the object: `<EditFields field={Product.dimensions} fields={{width: Product.dimensions.width}} />`.
183
239
  - `EntryTable`: a table of entries with the columns of an [overview](overviews.md#entry-tables-in-your-own-views), for example to list related entries in a `Field.view` section.
184
240
  - `FieldViewProps<F>`, `SectionViewProps`, `TypeViewProps`, `RootViewProps`: the props the dashboard passes to a field view, a `Field.view` component, a type's `view` and a root's `view`.
185
- - `OverviewCellProps`, `OverviewActionProps`, `OverviewEntry`: the props of an overview column's `view` and of an overview action, and the entry they receive.
241
+ - `OverviewCellProps`, `OverviewActionProps`, `OverviewEntry`: the props of an overview column's `view` and of an overview action, and the entry they receive. `EntryActionProps` and `DashboardEntry`: the props of an entry action and the entry `useEntry()` returns.
186
242
 
187
243
  ## Show a component between fields
188
244
 
@@ -218,6 +274,77 @@ export function ProductUrls() {
218
274
  }
219
275
  ```
220
276
 
277
+ ## Replace the form of a type
278
+
279
+ A type's `view` replaces the form of its entries with your own component, for example to edit the fields in a layout of your own. The entry header stays above it, with the title, the status, the publish and draft buttons, the type's `actions` and the sidebar with history and references. The view fills the rest of the editor, without padding.
280
+
281
+ File: schema/Product.ts
282
+ ```tsx
283
+ import {Config, Field} from 'alinea'
284
+
285
+ export const Product = Config.document('Product', {
286
+ view: '@/views/ProductView.view#ProductView',
287
+ fields: {
288
+ summary: Field.text('Summary', {multiline: true}),
289
+ price: Field.number('Price')
290
+ }
291
+ })
292
+ ```
293
+
294
+ File: views/ProductView.view.tsx
295
+ ```tsx
296
+ import {EditField, useField} from 'alinea/cms'
297
+ import {TextField} from 'alinea/components'
298
+ import {Product} from '@/schema/Product'
299
+
300
+ export function ProductView() {
301
+ const [summary = '', setSummary] = useField(Product.summary)
302
+ return (
303
+ <div style={{display: 'grid', gap: 16, padding: 24}}>
304
+ <TextField
305
+ label="Summary"
306
+ multiline
307
+ value={summary}
308
+ onValueChange={setSummary}
309
+ />
310
+ <EditField field={Product.price} />
311
+ </div>
312
+ )
313
+ }
314
+ ```
315
+
316
+ The view receives `TypeViewProps`: the `type` of the entry. It edits the entry like the form does: the field hooks, `EditField` and `EditFields` read and write the fields of the entry, the changes show up as unsaved edits in the header and are kept until the editor publishes or saves a draft, and leaving the entry with unsaved changes asks to confirm first. To fill the editor, size the view with `height: 100%`.
317
+
318
+ ## Add actions to the entry header
319
+
320
+ Buttons that act on the entry, such as an export or a button that creates a related entry, go in the `actions` of its type. They render in the entry header next to the publish button and receive `EntryActionProps`: the `entry` and the `locale` it is edited in. The hooks of `alinea/cms` read and write its fields.
321
+
322
+ File: schema/Product.ts
323
+ ```tsx
324
+ import {Config, Field} from 'alinea'
325
+
326
+ export const Product = Config.document('Product', {
327
+ fields: {title: Field.text('Title')},
328
+ actions: ['@/views/ExportProduct.view#ExportProduct']
329
+ })
330
+ ```
331
+
332
+ File: views/ExportProduct.view.tsx
333
+ ```tsx
334
+ import {type EntryActionProps, useFieldValue} from 'alinea/cms'
335
+ import {Button} from 'alinea/components'
336
+ import {Product} from '@/schema/Product'
337
+
338
+ export function ExportProduct({entry}: EntryActionProps) {
339
+ const title = useFieldValue(Product.title)
340
+ return (
341
+ <Button onClick={() => window.open(`/api/export/${entry.id}`)}>
342
+ Export {title}
343
+ </Button>
344
+ )
345
+ }
346
+ ```
347
+
221
348
  ## Upgrading from Alinea 1.x
222
349
 
223
350
  Field views from 1.x keep working, with deprecated APIs:
@@ -225,6 +352,7 @@ Field views from 1.x keep working, with deprecated APIs:
225
352
  - `useField` from `alinea/dashboard` returns `{value, mutator, options, label, error}`. Replace it with the tuple `useField` from `alinea/cms` and the separate `useFieldOptions` and `useFieldError`. `useFieldValue`, `useFieldOptions`, `useFieldError` and `useFieldKey` also move to `alinea/cms`, and `useFieldMutator` becomes `useFieldSetter`.
226
353
  - `InputLabel` from `alinea/dashboard` is now the `Field` component. It no longer shows `help` from `{...options}`: use `FieldChrome` from `alinea/cms`, which reads it from the field, or pass the help text to `Field` from `alinea/components` as `description`.
227
354
  - `useLocale`, `useGraph` and `useEntryEditor` from `alinea/dashboard/hook/*` become `useLocale`, `useGraph` and `useEntry` from `alinea/cms`.
355
+ - `useEntryEditor` still works as a shim for views that only read the entry: `activeVersion` has the `id`, `locale`, `path`, `status`, `title` and `type` of the entry, and `activeVersion.data` only has `path`. Reading another field of it throws, read field values with `useFieldValue(field)` instead. `useEntry()` returns the same entry as a `DashboardEntry`, with its saved field values in `data`.
228
356
  - Hooks imported from `alinea/dashboard/hooks` still work, but that module is internal: import them from `alinea/cms`.
229
357
  - Components from `alinea/ui` were removed. Use [`alinea/components`](components.md).
230
358
 
@@ -61,6 +61,8 @@ When two parts implement the same thing, the one listed last wins. That is how a
61
61
 
62
62
  Commits are made by the owner of the token. The editor who published is added as a `Co-authored-by` trailer with their name and email. When the branch moved since the dashboard last synced, the handler syncs and retries the commit up to three times.
63
63
 
64
+ To commit under another name, such as when your host only deploys commits of its members, pass `author: {name, email}`. GitHub links the commit to the account that owns the email. These commits are written through the REST API: GitHub does not mark them as verified, and a save usually spends three REST requests, plus one per uploaded file, where commits by the token owner spend none. Removing a file that is already gone costs one more, and when the branch moved in the meantime the save is repeated.
65
+
64
66
  ## Authentication
65
67
 
66
68
  ### Basic authentication
package/docs/deploy.md CHANGED
@@ -78,7 +78,7 @@ Without a `backend` option the handler connects to Alinea Cloud. Pass `backend`
78
78
 
79
79
  ### Commit hooks
80
80
 
81
- Two hooks on the handler run for every commit, whatever it contains. `beforeCommit` receives the changes before they are committed: inspect or rewrite them and return the mutations to commit, or return nothing to keep them as they are. `afterCommit` runs once the commit is made, with the mutations and the `sha` of the new commit: revalidate caches of your own, notify your team or trigger a deploy.
81
+ Two hooks on the handler run for every commit, whatever it contains. `beforeCommit` receives the changes before they are committed: inspect or rewrite them and return the mutations to commit, or return nothing to keep them as they are. `afterCommit` runs once the commit is made, with the mutations, the `sha` of the new commit, the changed `entries` as they are after the commit and the `removed` entries with the url they had: revalidate caches of your own, notify your team or trigger a deploy.
82
82
 
83
83
  File: app/(alinea)/api/cms/route.ts
84
84
  ```tsx
@@ -92,9 +92,12 @@ const handler = createHandler({
92
92
  // Inspect or rewrite the changes, return the mutations to commit
93
93
  return mutations
94
94
  },
95
- afterCommit({sha}) {
96
- // Pages that query content refresh on their own, revalidate your own caches
97
- revalidateTag('products', 'max')
95
+ afterCommit({entries, removed}) {
96
+ // Pages that query content refresh on their own, revalidate your own
97
+ // caches, eg. a feed that lists products
98
+ const changed = [...entries, ...removed]
99
+ if (changed.some(entry => entry.type === 'Product'))
100
+ revalidateTag('products', 'max')
98
101
  }
99
102
  })
100
103
 
@@ -36,7 +36,6 @@ The picker only offers images: files with an extension such as `.jpg`, `.png`, `
36
36
  Each image is resolved to an `ImageLink`:
37
37
 
38
38
  - `src`: the url of the image on your site, such as `/admin/file/landscape.jpg?v=3f2a…`. It's served through your Alinea handler, and the `v` parameter changes when the file is replaced, so caches never serve an outdated copy.
39
- - `url`: the same as `src`.
40
39
  - `width` and `height`: the dimensions in pixels.
41
40
  - `alt`: the alt text editors enter in the media library, in the locale of the entry you queried when the media root is [translated](../workspaces/media.md).
42
41
  - `focus`: the focal point as `{x, y}`, each between 0 and 1. Use it as `object-position` when you crop the image.
@@ -73,6 +73,25 @@ export default Config.type("List field", {
73
73
 
74
74
  Row types are ordinary [Types](../schema/type.md): they can have tabs, an `icon` that is shown in the add menu and nested lists. They don't need a title or path and don't have to be in the schema.
75
75
 
76
+ Options that change how a block looks rather than what it says, such as a background or a layout variant, can go in the type's `settings`. Editors find them in the block's settings sheet, below the label and anchor, which keeps the block form short. They're stored on the row next to its other fields:
77
+
78
+ ```tsx
79
+ const HeroBlock = Config.type('Hero', {
80
+ fields: {
81
+ title: Field.text('Title'),
82
+ text: Field.richText('Text')
83
+ },
84
+ settings: {
85
+ background: Field.select('Background', {
86
+ options: {light: 'Light', dark: 'Dark'},
87
+ initialValue: 'light'
88
+ })
89
+ }
90
+ })
91
+
92
+ // A queried row: {_type: 'HeroBlock', title, text, background, ...}
93
+ ```
94
+
76
95
  ## Value
77
96
 
78
97
  The value is an array of rows. Next to its fields, every row has:
@@ -41,11 +41,11 @@ An object with a key per field: `{street: '', zip: '', city: ''}`. New entries s
41
41
 
42
42
  ## Querying
43
43
 
44
- Select and sort on the object as a whole: the inner fields aren't registered in the schema, so `Venue.address.city` can't be used in `select` or `orderBy`. To filter on an inner value, use `has`:
44
+ Without a `select` the object is returned as a whole. Select or sort on a field inside it through the object, `Venue.address.city`, and filter on inner values with `has`:
45
45
 
46
46
  ```tsx
47
47
  import {cms} from '@/cms'
48
- import {Config, Field} from 'alinea'
48
+ import {Config, Field, Query} from 'alinea'
49
49
 
50
50
  export const Venue = Config.document('Venue', {
51
51
  fields: {
@@ -61,8 +61,10 @@ export const Venue = Config.document('Venue', {
61
61
  const venuesInGhent = await cms.find({
62
62
  type: Venue,
63
63
  filter: {address: {has: {city: 'Ghent'}}},
64
- select: {address: Venue.address}
64
+ select: {title: Query.title, street: Venue.address.street},
65
+ orderBy: {asc: Venue.address.street}
65
66
  })
67
+ // Array<{title: string, street: string}>
66
68
  ```
67
69
 
68
70
  ## Reusing a group of fields
@@ -160,6 +160,37 @@ export function Prose({doc}: {doc: TextDoc}) {
160
160
 
161
161
  The overridable tags are `h1`–`h6`, `p`, `b`, `i`, `ul`, `ol`, `li`, `blockquote`, `hr`, `img`, `br`, `small`, `sub`, `sup`, `a`, `table`, `tr`, `td` and `th`. Pass `text` to wrap every piece of text in a component. Headings get an `id` (the anchor an editor set, or a slug of the heading text), so you can link to sections.
162
162
 
163
+ ### Custom attributes
164
+
165
+ Attributes that a custom extension adds to a node are stored with the node, but only the `data-*` attributes its `renderHTML` produces reach the rendered elements and your tag overrides. Render an attribute as `data-*` to use it on your site:
166
+
167
+ ```tsx
168
+ import {Field} from 'alinea'
169
+ import {extensions} from 'alinea/field/richtext'
170
+
171
+ const Checklist = extensions.BulletList.extend({
172
+ addAttributes() {
173
+ return {
174
+ ...this.parent?.(),
175
+ checklist: {
176
+ default: null,
177
+ renderHTML: attributes =>
178
+ attributes.checklist ? {'data-checklist': 'true'} : {}
179
+ }
180
+ }
181
+ }
182
+ })
183
+
184
+ export const body = Field.richText('Body', {
185
+ extensions: defaults => ({...defaults, BulletList: Checklist})
186
+ })
187
+
188
+ // <RichText doc={body} ul={<ul className="list" />} /> renders
189
+ // <ul data-checklist="true" class="list">
190
+ ```
191
+
192
+ Content saved by Alinea 1.x only has the raw attribute (`checklist: true`). Its `data-*` attribute is added when the entry is saved again in the dashboard, until then views don't receive it.
193
+
163
194
  ### Blocks
164
195
 
165
196
  Blocks are rendered only when you pass a component for their type name. The component receives the block's fields as props:
package/docs/index.md CHANGED
@@ -45,7 +45,7 @@ The documentation of this version of Alinea, one Markdown file per page of https
45
45
  - [Querying content](query.md): Read content with the methods on your CMS instance.
46
46
  - [Structural](query/structural.md): Most queries start by saying which entries you want: of a type, at a url, in a root, in a locale. Then you choose what to get back and in which order.
47
47
  - [Filtering](query/filtering.md): Use filter to match entries on the values of their fields, and search to find entries by the words they contain.
48
- - [Related](query/related.md): A single query can also fetch entries related to the ones it finds: their parents, children and siblings, their translations, and the entries they link to.
48
+ - [Related](query/related.md): A single query can also fetch entries related to the ones it finds: their parents, children and siblings, their translations, the entries they link to and the…
49
49
  - [Advanced](query/advanced.md): Patterns for larger sites: nesting relations, querying several types at once, reusing parts of queries and controlling how fresh the content is.
50
50
  - [Examples](query/examples.md): Complete queries for pages most sites have. They use the example schema and the Next.js App Router, and assume a pages root without i18n unless noted.
51
51
 
@@ -80,7 +80,7 @@ The documentation of this version of Alinea, one Markdown file per page of https
80
80
  - [Code](components/code.md)
81
81
  - [Collapsible](components/collapsible.md)
82
82
  - [ColorSwatchPicker](components/color-swatch-picker.md)
83
- - [ComboBox](components/combo-box.md)
83
+ - [ComboBox](components/combo-box.md): Group items under a label with ComboBoxGroup. Like SelectItem, a ComboBoxItem can show an icon and a description below its label.
84
84
  - [Command](components/command.md): A searchable list of commands, like a command palette.
85
85
  - [ContentCard](components/content-card.md)
86
86
  - [ContentGrid](components/content-grid.md): A selectable grid of cards, like the media library.
@@ -95,13 +95,14 @@ The documentation of this version of Alinea, one Markdown file per page of https
95
95
  - [Field](components/field.md): The label, help text and error around your own input.
96
96
  - [FileTrigger](components/file-trigger.md)
97
97
  - [FoldIcon](components/fold-icon.md)
98
+ - [Form](components/form.md): A form whose fields show their validation errors. onSubmit receives the FormData of the named fields instead of the browser submitting the form.
98
99
  - [Heading](components/heading.md)
99
100
  - [Icon](components/icon.md)
100
101
  - [Kbd](components/kbd.md)
101
- - [Link](components/link.md)
102
+ - [Link](components/link.md): Besides href, the link passes target, rel, download, title, tabIndex, lang, hrefLang, referrerPolicy and aria-current to its <a>.
102
103
  - [List](components/list.md)
103
104
  - [MediaPreview](components/media-preview.md): A thumbnail of an image or an icon for other files.
104
- - [MultipleSelect](components/multiple-select.md)
105
+ - [MultipleSelect](components/multiple-select.md): The value is an array of the values of the picked items, strings or numbers.
105
106
  - [NavRail](components/nav-rail.md)
106
107
  - [NumberField](components/number-field.md): A number input with increment and decrement buttons.
107
108
  - [Page](components/page.md): The header, content and footer of a dashboard page.
@@ -110,10 +111,11 @@ The documentation of this version of Alinea, one Markdown file per page of https
110
111
  - [RadioGroup](components/radio-group.md)
111
112
  - [ResizablePanelGroup](components/resizable-panel-group.md)
112
113
  - [SearchField](components/search-field.md): A text input for search queries, with a clear button.
113
- - [Select](components/select.md): Picks a single value from a list of options. The value is the value of the chosen SelectItem, and editors can clear it unless the field is required.
114
+ - [Select](components/select.md): Picks a single value from a list of options. The value is the value of the chosen SelectItem, and onValueChange receives it, typed like the value you pass:…
114
115
  - [Sidebar](components/sidebar.md)
115
116
  - [SortableList](components/sortable-list.md): Rows that editors reorder by dragging, like list fields.
116
117
  - [Spinner](components/spinner.md)
118
+ - [Stack](components/stack.md): Lays out its children in a column, or a row with direction="row", with a gap between them: a number is in pixels, a string any CSS length.
117
119
  - [Surface](components/surface.md)
118
120
  - [Switch](components/switch.md): Turns a setting on or off, with an immediate effect. For choices that are saved later, as part of a form, use a Checkbox.
119
121
  - [Table](components/table.md): Rows and columns of data with sorting, selection, nested rows and drag and drop. Describe the columns up front, then render a TableRow for every item.
@@ -121,7 +123,7 @@ The documentation of this version of Alinea, one Markdown file per page of https
121
123
  - [TagGroup](components/tag-group.md)
122
124
  - [Text](components/text.md)
123
125
  - [TextField](components/text-field.md): A text input with a label, help text and a validation message. Set multiline for a text area that grows with its content.
124
- - [TimeField](components/time-field.md)
126
+ - [TimeField](components/time-field.md): The value is an ISO time string: 14:30, or 14:30:15 with granularity="second".
125
127
  - [Timestamp](components/timestamp.md): A date shown relative to now, with the full date on hover.
126
128
  - [Toggle](components/toggle.md): A button that stays pressed until it is pressed again.
127
129
  - [ToggleGroup](components/toggle-group.md): A row of toggles where one or several can be pressed.
@@ -10,6 +10,8 @@ Diagram: Publish flow: the dashboard publishes to your site's handler, which com
10
10
 
11
11
  ## Skip builds for content-only commits
12
12
 
13
+ Only set this up once your deployed site has a backend that syncs content at runtime: Alinea Cloud or a self-hosted backend. Without one, a deployment is how new content reaches your site, so skipping content-only builds would leave published changes offline.
14
+
13
15
  Because content is synced at runtime, a commit that only changes content does not need a new deployment. On Vercel you can skip those builds with an `ignoreCommand` in `vercel.json`. The command exits successfully, which tells Vercel to skip the build, when nothing outside the `content` directory changed since the last deployment. Comparing against `VERCEL_GIT_PREVIOUS_SHA` covers every commit in a push, not only the last one; on the first deployment of a branch it falls back to the previous commit:
14
16
 
15
17
  File: vercel.json
@@ -23,6 +25,25 @@ Note (info): Adjust `content` if your content lives in a different directory.
23
25
 
24
26
  ## Static pages
25
27
 
26
- Static pages are revalidated for you. Every page that queries content is tagged, and after each commit the handler marks those pages as stale. The next visit is still answered from the cache while Next.js rebuilds the page in the background, and the visit after that gets the new content. No visitor waits for a render, and no `revalidatePath` call is needed.
28
+ Static pages are revalidated for you. Every page that queries content is tagged, and after each commit the handler marks those pages as stale. The next visit is still answered from the cache while Next.js rebuilds the page in the background, and the visit after that gets the new content. No visitor waits for a render, and no `revalidatePath` call is needed. This includes pages generated with `generateStaticParams`: revalidation code in `afterCommit` that refreshes them by hand, such as `revalidateTag('_N_T_/layout')` or `revalidatePath('/', 'layout')`, can be removed.
29
+
30
+ Pages rendered at request time sync on their next render. The first one after a commit can still show the previous version while the shared content sha refreshes. Content pushed to your repository without the dashboard, for example a local edit, skips the handler: pages pick it up within five minutes, or within `syncInterval` seconds when your config sets it. Use the handler's `afterCommit` hook if other caches in your app depend on content, for example your own `fetch` tags. It receives the changed `entries` and the `removed` ones, see [Commit hooks](page:guides/deploy#commit-hooks):
31
+
32
+ File: app/(alinea)/api/cms/route.ts
33
+ ```tsx
34
+ import {cms} from '@/cms'
35
+ import {createHandler} from 'alinea/next'
36
+ import {revalidateTag} from 'next/cache'
27
37
 
28
- Pages rendered at request time sync on their next render. The first one after a commit can still show the previous version while the shared content sha refreshes. Content pushed to your repository without the dashboard, for example a local edit, skips the handler: pages pick it up within five minutes, or within `syncInterval` seconds when your config sets it. Use the handler's `afterCommit` hook if other caches in your app depend on content, for example your own `fetch` tags.
38
+ const handler = createHandler({
39
+ cms,
40
+ afterCommit({entries, removed}) {
41
+ const changed = [...entries, ...removed]
42
+ if (changed.some(entry => entry.type === 'Product'))
43
+ revalidateTag('products', 'max')
44
+ }
45
+ })
46
+
47
+ export const GET = handler
48
+ export const POST = handler
49
+ ```