@conduction/nextcloud-vue 2.56.0-beta.6 → 2.56.0-beta.8

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 (254) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +80 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +232 -10
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
  13. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
  15. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +46 -0
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  20. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
  21. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
  22. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
  23. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
  24. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
  25. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
  27. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  28. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
  29. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  30. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
  31. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
  32. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
  33. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
  34. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
  35. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
  39. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
  40. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
  41. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +10 -2
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
  46. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +303 -144
  48. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
  53. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
  54. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
  55. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
  56. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
  57. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
  61. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +171 -12
  62. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
  64. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
  66. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
  67. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
  68. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
  70. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
  72. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  73. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +10 -0
  74. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js.map +1 -0
  75. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +316 -0
  76. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -0
  77. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +105 -0
  78. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -0
  79. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
  80. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
  81. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
  82. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
  83. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
  84. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
  85. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
  86. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
  87. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
  88. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
  89. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
  90. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
  91. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
  92. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
  95. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
  96. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
  97. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
  98. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
  99. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
  100. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
  101. package/dist/esm/components/CnPageRenderer/pageTypes.js +1 -0
  102. package/dist/esm/components/CnPageRenderer/pageTypes.js.map +1 -1
  103. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
  104. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  105. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
  106. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  107. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
  108. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  109. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
  111. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
  112. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
  113. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
  114. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
  115. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
  116. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
  117. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
  118. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
  119. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
  120. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
  121. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
  122. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
  123. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
  124. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
  125. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
  126. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
  127. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
  128. package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
  129. package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
  130. package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
  131. package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
  132. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  133. package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
  134. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  135. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
  136. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  137. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
  138. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  139. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
  140. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
  141. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
  142. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
  143. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
  144. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
  145. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
  146. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
  147. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
  148. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
  149. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
  150. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
  151. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
  152. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  153. package/dist/esm/index.js +16 -0
  154. package/dist/esm/index.js.map +1 -1
  155. package/dist/esm/l10n/en.json.js +55 -1
  156. package/dist/esm/l10n/en.json.js.map +1 -1
  157. package/dist/esm/l10n/nl.json.js +55 -1
  158. package/dist/esm/l10n/nl.json.js.map +1 -1
  159. package/dist/esm/nextcloud-vue.css +1036 -19
  160. package/dist/esm/utils/dateVariant.js +124 -0
  161. package/dist/esm/utils/dateVariant.js.map +1 -0
  162. package/dist/esm/utils/detailActionModel.js +260 -0
  163. package/dist/esm/utils/detailActionModel.js.map +1 -0
  164. package/dist/esm/utils/dueRule.js +84 -0
  165. package/dist/esm/utils/dueRule.js.map +1 -0
  166. package/dist/esm/utils/fetchAggregate.js +37 -1
  167. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  168. package/dist/esm/utils/fetchFilterCounts.js +186 -0
  169. package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
  170. package/dist/esm/utils/headers.js +28 -2
  171. package/dist/esm/utils/headers.js.map +1 -1
  172. package/dist/esm/utils/libraryWidgetKeys.js +5 -0
  173. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  174. package/dist/esm/utils/readPath.js +30 -0
  175. package/dist/esm/utils/readPath.js.map +1 -0
  176. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  177. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  178. package/dist/nextcloud-vue.cjs.js +16019 -9528
  179. package/dist/nextcloud-vue.cjs.js.map +2 -2
  180. package/dist/nextcloud-vue.css +1036 -19
  181. package/l10n/en.json +55 -1
  182. package/l10n/nl.json +55 -1
  183. package/package.json +1 -1
  184. package/scripts/.jsdoc-baselines.json +4 -0
  185. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  186. package/src/components/CnAppNav/CnAppNav.md +26 -0
  187. package/src/components/CnAppNav/CnAppNav.vue +145 -2
  188. package/src/components/CnBannerWidget/CnBannerWidget.vue +373 -11
  189. package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
  190. package/src/components/CnBoardView/CnBoardView.vue +77 -0
  191. package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
  192. package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
  193. package/src/components/CnBrandStripe/index.js +4 -0
  194. package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
  195. package/src/components/CnConversationThread/CnConversationThread.md +51 -0
  196. package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
  197. package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
  198. package/src/components/CnConversationThread/index.js +4 -0
  199. package/src/components/CnDashboardPage/CnDashboardPage.vue +10 -2
  200. package/src/components/CnDetailPage/CnDetailPage.md +16 -0
  201. package/src/components/CnDetailPage/CnDetailPage.vue +687 -49
  202. package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
  203. package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
  204. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
  205. package/src/components/CnDocumentReviewList/index.js +4 -0
  206. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +191 -14
  207. package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
  208. package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
  209. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +484 -0
  210. package/src/components/CnLinkCardsPage/index.js +4 -0
  211. package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
  212. package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
  213. package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
  214. package/src/components/CnNextStepCard/index.js +4 -0
  215. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
  216. package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
  217. package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
  218. package/src/components/CnPageRenderer/pageTypes.js +1 -0
  219. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
  220. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
  221. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
  222. package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
  223. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
  224. package/src/components/CnSegmentedControl/index.js +4 -0
  225. package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
  226. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
  227. package/src/components/CnStackedBarWidget/index.js +14 -0
  228. package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
  229. package/src/components/CnStackedBarWidgetForm/index.js +4 -0
  230. package/src/components/CnTabs/CnTab.vue +35 -0
  231. package/src/components/CnTabs/CnTabs.vue +244 -10
  232. package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
  233. package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
  234. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
  235. package/src/components/CnWeekStripWidget/index.js +14 -0
  236. package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
  237. package/src/components/CnWeekStripWidgetForm/index.js +4 -0
  238. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
  239. package/src/components/index.js +12 -0
  240. package/src/css/detail-page.css +57 -2
  241. package/src/index.js +16 -0
  242. package/src/schemas/app-manifest-v2.schema.json +433 -3
  243. package/src/schemas/app-manifest.schema.json +2 -1
  244. package/src/types/manifest.d.ts +1 -1
  245. package/src/utils/dateVariant.js +120 -0
  246. package/src/utils/detailActionModel.js +256 -0
  247. package/src/utils/dueRule.js +94 -0
  248. package/src/utils/fetchAggregate.js +36 -0
  249. package/src/utils/fetchFilterCounts.js +182 -0
  250. package/src/utils/headers.js +28 -2
  251. package/src/utils/index.js +1 -0
  252. package/src/utils/libraryWidgetKeys.js +5 -0
  253. package/src/utils/readPath.js +27 -0
  254. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -0,0 +1,457 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+
6
+ <template>
7
+ <section
8
+ class="cn-conversation-thread"
9
+ data-testid="cn-conversation-thread"
10
+ :aria-labelledby="title ? titleId : null"
11
+ :aria-label="title ? null : fallbackLabel">
12
+ <component
13
+ :is="titleTag"
14
+ v-if="title"
15
+ :id="titleId"
16
+ class="cn-conversation-thread__title">
17
+ {{ title }}
18
+ </component>
19
+
20
+ <p v-if="rows.length === 0" class="cn-conversation-thread__empty" data-testid="cn-conversation-empty">
21
+ {{ emptyText }}
22
+ </p>
23
+
24
+ <ol v-else class="cn-conversation-thread__messages">
25
+ <li
26
+ v-for="row in rows"
27
+ :key="row.key"
28
+ class="cn-conversation-thread__message"
29
+ :class="`cn-conversation-thread__message--${row.side}`"
30
+ data-testid="cn-conversation-message"
31
+ :data-side="row.side">
32
+ <span class="cn-conversation-thread__avatar" aria-hidden="true">{{ row.initials }}</span>
33
+ <div class="cn-conversation-thread__bubble">
34
+ <span class="cn-conversation-thread__meta">
35
+ <span class="cn-conversation-thread__author">{{ row.author }}</span>
36
+ <time v-if="row.timeLabel" :datetime="row.datetime || null">{{ row.timeLabel }}</time>
37
+ </span>
38
+ <span class="cn-conversation-thread__text">{{ row.text }}</span>
39
+ </div>
40
+ </li>
41
+ </ol>
42
+
43
+ <form
44
+ v-if="allowReply"
45
+ class="cn-conversation-thread__reply"
46
+ data-testid="cn-conversation-reply"
47
+ @submit.prevent="onSend">
48
+ <label :for="inputId" class="cn-conversation-thread__reply-label">{{ replyLabel }}</label>
49
+ <textarea
50
+ :id="inputId"
51
+ v-model="draft"
52
+ class="cn-conversation-thread__input"
53
+ rows="2"
54
+ :placeholder="placeholder || null"
55
+ :disabled="sending"
56
+ :aria-describedby="errorMessage ? errorId : null"
57
+ data-testid="cn-conversation-input"
58
+ @keydown.ctrl.enter.prevent="onSend"
59
+ @keydown.meta.enter.prevent="onSend" />
60
+ <p
61
+ v-if="errorMessage"
62
+ :id="errorId"
63
+ class="cn-conversation-thread__error"
64
+ role="alert"
65
+ data-testid="cn-conversation-error">
66
+ {{ errorMessage }}
67
+ </p>
68
+ <div class="cn-conversation-thread__reply-actions">
69
+ <!-- @slot reply-actions Extra buttons before Send, for example "Save draft". -->
70
+ <!-- @binding {string} draft The text in the reply box. -->
71
+ <slot name="reply-actions" :draft="draft" />
72
+ <NcButton
73
+ variant="primary"
74
+ type="submit"
75
+ :disabled="!canSend"
76
+ data-testid="cn-conversation-send">
77
+ {{ sendLabel }}
78
+ </NcButton>
79
+ </div>
80
+ </form>
81
+ </section>
82
+ </template>
83
+
84
+ <script>
85
+ import axios from '@nextcloud/axios'
86
+ import { translate as t } from '@nextcloud/l10n'
87
+ import { NcButton } from '@nextcloud/vue'
88
+ import { prefixUrl } from '../../utils/headers.js'
89
+
90
+ let conversationThreadUid = 0
91
+
92
+ /**
93
+ * CnConversationThread: the messages between you and the other party, and a box to answer.
94
+ *
95
+ * Each message has an author, a time and a side: `us` for the organisation,
96
+ * `them` for the resident or customer. The reply box emits `send`, and also
97
+ * posts to `endpoint` when one is set. It needs no Talk and no other app:
98
+ * where the messages are stored is the host's business.
99
+ *
100
+ * ```vue
101
+ * <CnConversationThread
102
+ * title="Contact with the resident"
103
+ * reply-label="Answer to the resident"
104
+ * :messages="[
105
+ * { id: 1, author: 'Sanne de Vries', time: '2026-10-04T10:52:00', side: 'them',
106
+ * text: 'Thank you. I would like to see all documents.' },
107
+ * { id: 2, time: '2026-10-04T10:24:00', side: 'us',
108
+ * text: 'Construction starts in March.' },
109
+ * ]"
110
+ * @send="saveReply" />
111
+ * ```
112
+ *
113
+ * The `conversation` detail widget type renders this thread from a field on
114
+ * the record.
115
+ */
116
+ export default {
117
+ name: 'CnConversationThread',
118
+
119
+ components: {
120
+ NcButton,
121
+ },
122
+
123
+ props: {
124
+ /**
125
+ * The messages, oldest first: `{ id?, author?, time?, text, side? }`.
126
+ * `side` is `us` or `them` (default `them`). `time` is an ISO
127
+ * date, or any text to show as is.
128
+ */
129
+ messages: {
130
+ type: Array,
131
+ default: () => [],
132
+ },
133
+
134
+ /** Heading of the thread. */
135
+ title: {
136
+ type: String,
137
+ default: '',
138
+ },
139
+
140
+ /** Element the heading renders as. */
141
+ titleTag: {
142
+ type: String,
143
+ default: 'h3',
144
+ },
145
+
146
+ /** Whether the reply box renders. Turn it off for a closed or read-only record. */
147
+ allowReply: {
148
+ type: Boolean,
149
+ default: true,
150
+ },
151
+
152
+ /** Label of the reply box. */
153
+ replyLabel: {
154
+ type: String,
155
+ default: () => t('nextcloud-vue', 'Reply'),
156
+ },
157
+
158
+ /** Placeholder inside the reply box. */
159
+ placeholder: {
160
+ type: String,
161
+ default: '',
162
+ },
163
+
164
+ /** Label of the send button. */
165
+ sendLabel: {
166
+ type: String,
167
+ default: () => t('nextcloud-vue', 'Send'),
168
+ },
169
+
170
+ /** Author shown on an `us` message that names none. */
171
+ usLabel: {
172
+ type: String,
173
+ default: () => t('nextcloud-vue', 'You'),
174
+ },
175
+
176
+ /**
177
+ * URL the reply is POSTed to as `{ message }`. Empty means the host
178
+ * handles `send` itself and nothing is requested.
179
+ */
180
+ endpoint: {
181
+ type: String,
182
+ default: '',
183
+ },
184
+
185
+ /** Extra fields sent along with `message` when `endpoint` is set. */
186
+ payload: {
187
+ type: Object,
188
+ default: () => ({}),
189
+ },
190
+
191
+ /** Shown when there are no messages yet. */
192
+ emptyText: {
193
+ type: String,
194
+ default: () => t('nextcloud-vue', 'No messages yet.'),
195
+ },
196
+ },
197
+
198
+ emits: [
199
+ /** A reply was submitted. Payload: the text. */
200
+ 'send',
201
+ /** The endpoint accepted the reply. Payload: the response body. */
202
+ 'sent',
203
+ /** The endpoint refused the reply. Payload: the error. */
204
+ 'error',
205
+ ],
206
+
207
+ data() {
208
+ conversationThreadUid += 1
209
+ return {
210
+ draft: '',
211
+ sending: false,
212
+ errorMessage: '',
213
+ titleId: `cn-conversation-title-${conversationThreadUid}`,
214
+ inputId: `cn-conversation-input-${conversationThreadUid}`,
215
+ errorId: `cn-conversation-error-${conversationThreadUid}`,
216
+ }
217
+ },
218
+
219
+ computed: {
220
+ /**
221
+ * The messages, with author, side, time and initials resolved.
222
+ *
223
+ * @return {Array<{key: string, author: string, initials: string, side: string, text: string, timeLabel: string, datetime: string}>} The rows.
224
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
225
+ */
226
+ rows() {
227
+ return (Array.isArray(this.messages) ? this.messages : [])
228
+ .filter((message) => message && typeof message === 'object')
229
+ .map((message, index) => {
230
+ const side = message.side === 'us' ? 'us' : 'them'
231
+ const author = typeof message.author === 'string' && message.author !== ''
232
+ ? message.author
233
+ : (side === 'us' ? this.usLabel : '')
234
+ const time = this.formatTime(message.time)
235
+ return {
236
+ key: message.id === undefined || message.id === null ? `message-${index}` : String(message.id),
237
+ author,
238
+ initials: this.initialsOf(author),
239
+ side,
240
+ text: message.text === undefined || message.text === null ? '' : String(message.text),
241
+ timeLabel: time.label,
242
+ datetime: time.datetime,
243
+ }
244
+ })
245
+ },
246
+
247
+ /**
248
+ * Whether there is something to send and nothing in flight.
249
+ *
250
+ * @return {boolean} True when Send is enabled.
251
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
252
+ */
253
+ canSend() {
254
+ return !this.sending && this.draft.trim() !== ''
255
+ },
256
+
257
+ /**
258
+ * Accessible name for a thread without a title.
259
+ *
260
+ * @return {string} The label.
261
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
262
+ */
263
+ fallbackLabel() {
264
+ return t('nextcloud-vue', 'Conversation')
265
+ },
266
+ },
267
+
268
+ methods: {
269
+ /**
270
+ * Up to two initials of a name, for the avatar.
271
+ *
272
+ * @param {string} name The author.
273
+ * @return {string} The initials, upper case.
274
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
275
+ */
276
+ initialsOf(name) {
277
+ const parts = String(name || '').trim().split(/\s+/).filter(Boolean)
278
+ if (parts.length === 0) {
279
+ return ''
280
+ }
281
+ const first = parts[0].charAt(0)
282
+ const last = parts.length > 1 ? parts[parts.length - 1].charAt(0) : ''
283
+ return (first + last).toUpperCase()
284
+ },
285
+
286
+ /**
287
+ * Turn a message time into what the reader sees and what `<time>` carries.
288
+ *
289
+ * @param {unknown} value An ISO date, a Date, or text to show as is.
290
+ * @return {{label: string, datetime: string}} The visible label and the machine value.
291
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
292
+ */
293
+ formatTime(value) {
294
+ if (value === null || value === undefined || value === '') {
295
+ return { label: '', datetime: '' }
296
+ }
297
+ const date = value instanceof Date ? value : new Date(value)
298
+ const looksLikeDate = value instanceof Date || typeof value === 'number' || /^\d{4}-\d{2}-\d{2}/.test(String(value))
299
+ if (!looksLikeDate || Number.isNaN(date.getTime())) {
300
+ return { label: String(value), datetime: '' }
301
+ }
302
+ const label = date.toLocaleString(undefined, { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })
303
+ return { label, datetime: date.toISOString() }
304
+ },
305
+
306
+ /**
307
+ * Submit the reply: tell the host, and post it when an endpoint is set.
308
+ *
309
+ * Without an endpoint the box clears straight away, because the host
310
+ * owns the send. With one it clears only when the request succeeds, so
311
+ * a refused reply is still there to try again.
312
+ *
313
+ * @return {Promise<void>}
314
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
315
+ */
316
+ async onSend() {
317
+ if (!this.canSend) {
318
+ return
319
+ }
320
+ const text = this.draft.trim()
321
+ this.errorMessage = ''
322
+ this.$emit('send', text)
323
+ if (!this.endpoint) {
324
+ this.draft = ''
325
+ return
326
+ }
327
+ this.sending = true
328
+ try {
329
+ const response = await axios.post(prefixUrl(this.endpoint), { ...this.payload, message: text })
330
+ this.draft = ''
331
+ this.$emit('sent', response?.data)
332
+ } catch (error) {
333
+ this.errorMessage = t('nextcloud-vue', 'The message could not be sent. Try again.')
334
+ this.$emit('error', error)
335
+ } finally {
336
+ this.sending = false
337
+ }
338
+ },
339
+ },
340
+ }
341
+ </script>
342
+
343
+ <style scoped>
344
+ .cn-conversation-thread {
345
+ display: flex;
346
+ flex-direction: column;
347
+ gap: calc(3.5 * var(--default-grid-baseline));
348
+ }
349
+
350
+ .cn-conversation-thread__title {
351
+ font-size: 1.1em;
352
+ font-weight: 700;
353
+ margin: 0;
354
+ }
355
+
356
+ .cn-conversation-thread__empty {
357
+ color: var(--color-text-maxcontrast);
358
+ margin: 0;
359
+ }
360
+
361
+ .cn-conversation-thread__messages {
362
+ display: flex;
363
+ flex-direction: column;
364
+ gap: calc(3.5 * var(--default-grid-baseline));
365
+ list-style: none;
366
+ margin: 0;
367
+ padding: 0;
368
+ }
369
+
370
+ .cn-conversation-thread__message {
371
+ display: flex;
372
+ gap: calc(3 * var(--default-grid-baseline));
373
+ }
374
+
375
+ .cn-conversation-thread__message--us {
376
+ flex-direction: row-reverse;
377
+ }
378
+
379
+ .cn-conversation-thread__avatar {
380
+ align-items: center;
381
+ background: var(--color-background-dark);
382
+ border-radius: 50%;
383
+ color: var(--color-main-text);
384
+ display: flex;
385
+ flex: none;
386
+ font-size: 0.8em;
387
+ font-weight: 700;
388
+ height: 34px;
389
+ justify-content: center;
390
+ width: 34px;
391
+ }
392
+
393
+ .cn-conversation-thread__bubble {
394
+ background: var(--color-background-hover);
395
+ border-radius: var(--border-radius-large);
396
+ border-start-start-radius: var(--border-radius-small, 4px);
397
+ color: var(--color-main-text);
398
+ display: flex;
399
+ flex: 1;
400
+ flex-direction: column;
401
+ gap: var(--default-grid-baseline);
402
+ min-width: 0;
403
+ padding: calc(3 * var(--default-grid-baseline)) calc(3.5 * var(--default-grid-baseline));
404
+ }
405
+
406
+ .cn-conversation-thread__message--us .cn-conversation-thread__bubble {
407
+ background: var(--color-primary-element-light);
408
+ border-start-end-radius: var(--border-radius-small, 4px);
409
+ border-start-start-radius: var(--border-radius-large);
410
+ color: var(--color-primary-element-light-text);
411
+ }
412
+
413
+ .cn-conversation-thread__meta {
414
+ display: flex;
415
+ flex-wrap: wrap;
416
+ font-size: 0.9em;
417
+ gap: calc(2 * var(--default-grid-baseline));
418
+ }
419
+
420
+ .cn-conversation-thread__author {
421
+ font-weight: 600;
422
+ }
423
+
424
+ .cn-conversation-thread__text {
425
+ overflow-wrap: anywhere;
426
+ white-space: pre-wrap;
427
+ }
428
+
429
+ .cn-conversation-thread__reply {
430
+ display: flex;
431
+ flex-direction: column;
432
+ gap: calc(2 * var(--default-grid-baseline));
433
+ }
434
+
435
+ .cn-conversation-thread__reply-label {
436
+ font-weight: 600;
437
+ }
438
+
439
+ .cn-conversation-thread__input {
440
+ box-sizing: border-box;
441
+ margin: 0;
442
+ resize: vertical;
443
+ width: 100%;
444
+ }
445
+
446
+ .cn-conversation-thread__error {
447
+ color: var(--color-error-text, var(--color-error));
448
+ margin: 0;
449
+ }
450
+
451
+ .cn-conversation-thread__reply-actions {
452
+ display: flex;
453
+ flex-wrap: wrap;
454
+ gap: calc(2.5 * var(--default-grid-baseline));
455
+ justify-content: flex-end;
456
+ }
457
+ </style>
@@ -0,0 +1,144 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+
6
+ <template>
7
+ <div class="cn-case-widget-card">
8
+ <CnConversationThread
9
+ :messages="messages"
10
+ :title="content.title || ''"
11
+ :allowReply="content.allowReply !== false"
12
+ :replyLabel="content.replyLabel || undefined"
13
+ :placeholder="content.placeholder || ''"
14
+ :sendLabel="content.sendLabel || undefined"
15
+ :endpoint="endpoint"
16
+ :emptyText="content.emptyText || undefined"
17
+ @sent="onSent" />
18
+ </div>
19
+ </template>
20
+
21
+ <script>
22
+ import CnConversationThread from './CnConversationThread.vue'
23
+ import { readPath } from '../../utils/readPath.js'
24
+
25
+ /**
26
+ * CnConversationWidget: the `conversation` detail widget type.
27
+ *
28
+ * Renders CnConversationThread from a list on the bound record:
29
+ * `content: { field, endpoint?, authorField?, timeField?, textField?, sideField?, usValue? }`.
30
+ * `@objectId` in `endpoint` is replaced by the record's id. A reply the
31
+ * endpoint accepts is shown at once, until the record is read again.
32
+ * Resolved by its registry key, not exported.
33
+ */
34
+ export default {
35
+ name: 'CnConversationWidget',
36
+
37
+ components: {
38
+ CnConversationThread,
39
+ },
40
+
41
+ props: {
42
+ /** The widget's content. `field` is the dot-path to the messages on the record. */
43
+ content: {
44
+ type: Object,
45
+ default: () => ({}),
46
+ },
47
+
48
+ /** The bound record. */
49
+ objectData: {
50
+ type: Object,
51
+ default: null,
52
+ },
53
+
54
+ /** The bound record's id. */
55
+ objectId: {
56
+ type: [String, Number],
57
+ default: '',
58
+ },
59
+ },
60
+
61
+ data() {
62
+ return {
63
+ /** Replies sent from this widget that the record does not hold yet. */
64
+ sentHere: [],
65
+ }
66
+ },
67
+
68
+ computed: {
69
+ /**
70
+ * The record's messages in the thread's shape, plus the replies sent here.
71
+ *
72
+ * @return {Array<object>} The messages.
73
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
74
+ */
75
+ messages() {
76
+ const c = this.content
77
+ const value = readPath(this.objectData || {}, c.field || 'messages')
78
+ const usValue = String(c.usValue === undefined ? 'us' : c.usValue)
79
+ const stored = (Array.isArray(value) ? value : [])
80
+ .filter((message) => message && typeof message === 'object')
81
+ .map((message) => ({
82
+ id: message.id,
83
+ author: readPath(message, c.authorField || 'author'),
84
+ time: readPath(message, c.timeField || 'time'),
85
+ text: readPath(message, c.textField || 'text'),
86
+ side: String(readPath(message, c.sideField || 'side')) === usValue ? 'us' : 'them',
87
+ }))
88
+ return [...stored, ...this.sentHere]
89
+ },
90
+
91
+ /**
92
+ * The endpoint with the record's id filled in.
93
+ *
94
+ * @return {string} The URL, or '' when the content names none.
95
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
96
+ */
97
+ endpoint() {
98
+ const url = this.content.endpoint
99
+ if (typeof url !== 'string' || url === '') {
100
+ return ''
101
+ }
102
+ return url.replace(/@objectId/g, encodeURIComponent(String(this.objectId ?? '')))
103
+ },
104
+ },
105
+
106
+ watch: {
107
+ // The record was read again, so it now holds what was sent from here.
108
+ objectData() {
109
+ this.sentHere = []
110
+ },
111
+ },
112
+
113
+ methods: {
114
+ /**
115
+ * Show an accepted reply at once, without waiting for the record.
116
+ *
117
+ * @param {object} data The endpoint's response.
118
+ * @return {void}
119
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-conversation-thread
120
+ */
121
+ onSent(data) {
122
+ const text = data?.message ?? data?.text
123
+ if (typeof text !== 'string' || text === '') {
124
+ return
125
+ }
126
+ this.sentHere.push({
127
+ id: `sent-${this.sentHere.length}`,
128
+ time: new Date().toISOString(),
129
+ text,
130
+ side: 'us',
131
+ })
132
+ },
133
+ },
134
+ }
135
+ </script>
136
+
137
+ <style scoped>
138
+ .cn-case-widget-card {
139
+ background: var(--color-main-background);
140
+ border: 1px solid var(--color-border);
141
+ border-radius: var(--border-radius-large);
142
+ padding: calc(5 * var(--default-grid-baseline));
143
+ }
144
+ </style>
@@ -0,0 +1,4 @@
1
+ import CnConversationThread from './CnConversationThread.vue'
2
+
3
+ export default CnConversationThread
4
+ export { CnConversationThread }
@@ -2748,16 +2748,24 @@ export default {
2748
2748
  * the registry branch forwards to CnBannerWidget), or in the legacy
2749
2749
  * manifest `props` blob (the fallback getStatsBlockProps and
2750
2750
  * getChartProps already honour for their own widget types). `text`
2751
- * is only meaningful for banners.
2751
+ * is only meaningful for banners: it is what the banner would show,
2752
+ * which for an attention card is its `title` when it has no `text`.
2752
2753
  *
2753
2754
  * @param {object} def Widget definition.
2755
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-an-attention-card-with-a-title-keeps-its-cell
2754
2756
  * @return {{ text: string, visibleWhen: (object|null) }}
2755
2757
  */
2756
2758
  widgetDisplayConfig(def) {
2757
2759
  const content = (def && typeof def.content === 'object' && def.content) || {}
2758
2760
  const props = (def && typeof def.props === 'object' && def.props) || {}
2761
+ // An attention card (`layout: "attention"`) is headed by `title`,
2762
+ // and its title only FALLS BACK to `text`. Reading `text` alone
2763
+ // called a card with a title and a reason empty, and gave its cell
2764
+ // up before its `visibleWhen` was even looked at.
2765
+ const layout = content.layout || props.layout || ''
2766
+ const title = layout === 'attention' ? (content.title || props.title || '') : ''
2759
2767
  return {
2760
- text: content.text || props.text || '',
2768
+ text: content.text || props.text || title || '',
2761
2769
  visibleWhen: (def && def.visibleWhen) || content.visibleWhen || props.visibleWhen || null,
2762
2770
  }
2763
2771
  },
@@ -405,6 +405,22 @@ The page header's overflow menu carries Refresh plus the mandatory trio Request
405
405
  | `showReportBug` | Boolean | `true` | Whether the Report-a-bug entry renders in the page-header menu. |
406
406
  | `showDocumentation` | Boolean | `true` | Whether the Documentation entry renders. Its target is resolved by the shared menu from the app-wide documentation base plus this page's id, so leaving it on costs the host nothing. |
407
407
 
408
+ ## The action model
409
+
410
+ Four levels of action and the case surfaces around them. All opt in. See [the reference page](../../../docs/components/cn-detail-page.md#the-action-model) for manifest examples.
411
+
412
+ | Prop | Type | Default | Description |
413
+ |---|---|---|---|
414
+ | `primaryActionByStage` | Object \| null | `null` | Map from a stage value to the primary action: an action object or the id of a header action. Falls back to `primaryAction`. |
415
+ | `stageField` | String | `'status'` | Dot-path to the record's stage. |
416
+ | `quickActions` | Array | `[]` | At most three always visible header buttons. |
417
+ | `actionsMenu` | Object \| null | `null` | `{ showRefresh?, showHelpLinks?, label? }` for the overflow menu. |
418
+ | `nextStep` | Object \| null | `null` | The "what now" card per stage, rendered by `CnNextStepCard`. |
419
+ | `typePill` | Object \| null | `null` | `{ field, colorMap?, labels?, variant? }`, a pill above the title. |
420
+ | `statusPill` | Object \| null | `null` | A second pill, same shape. |
421
+ | `sideColumn` | Array | `[]` | Widget definitions or widget ids, rendered as a column of cards beside the body. |
422
+ | `isAdmin` | Boolean \| null | `null` | Shows `adminOnly` header actions. `null` reads it from Nextcloud. |
423
+
408
424
  ## The record as a place
409
425
 
410
426
  `listNavigation`, `primaryAction` and `tabInAddress` make a record somewhere a handler stays. All three are off by default. See [the reference page](../../../docs/components/cn-detail-page.md#the-record-as-a-place).