@carlonicora/nextjs-jsonapi 1.109.0 → 1.111.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/dist/{AssistantMessageInterface-Cs1yb-gF.d.ts → AssistantMessageInterface-BNzf1etU.d.ts} +14 -1
  2. package/dist/{AssistantMessageInterface-DQ3mH5L8.d.mts → AssistantMessageInterface-DrgHDjZ5.d.mts} +14 -1
  3. package/dist/{BlockNoteEditor-3XYBZLWO.js → BlockNoteEditor-7OKW6HHN.js} +19 -20
  4. package/dist/BlockNoteEditor-7OKW6HHN.js.map +1 -0
  5. package/dist/{BlockNoteEditor-EBFZG7AL.mjs → BlockNoteEditor-K7IIWM3S.mjs} +4 -5
  6. package/dist/{BlockNoteEditor-EBFZG7AL.mjs.map → BlockNoteEditor-K7IIWM3S.mjs.map} +1 -1
  7. package/dist/BlockNoteViewer-OPSCTHKJ.mjs +31 -0
  8. package/dist/BlockNoteViewer-OPSCTHKJ.mjs.map +1 -0
  9. package/dist/BlockNoteViewer-UW5VZ5UF.js +31 -0
  10. package/dist/BlockNoteViewer-UW5VZ5UF.js.map +1 -0
  11. package/dist/billing/index.d.mts +41 -41
  12. package/dist/billing/index.d.ts +41 -41
  13. package/dist/billing/index.js +346 -347
  14. package/dist/billing/index.js.map +1 -1
  15. package/dist/billing/index.mjs +3 -4
  16. package/dist/billing/index.mjs.map +1 -1
  17. package/dist/{chunk-7E3O52U5.js → chunk-6XWW67F2.js} +25 -12
  18. package/dist/chunk-6XWW67F2.js.map +1 -0
  19. package/dist/{chunk-UHO3KUUH.js → chunk-AKXUNE3A.js} +977 -850
  20. package/dist/chunk-AKXUNE3A.js.map +1 -0
  21. package/dist/{chunk-UOYIWJEJ.mjs → chunk-CWVYU4YZ.mjs} +18 -5
  22. package/dist/{chunk-UOYIWJEJ.mjs.map → chunk-CWVYU4YZ.mjs.map} +1 -1
  23. package/dist/{chunk-CRTVAQEK.mjs → chunk-TXVVZLUQ.mjs} +1388 -1261
  24. package/dist/chunk-TXVVZLUQ.mjs.map +1 -0
  25. package/dist/{chunk-CFI4WZ5R.js → chunk-U2HR57MY.js} +111 -5
  26. package/dist/chunk-U2HR57MY.js.map +1 -0
  27. package/dist/chunk-WPAQRB5Q.mjs +7 -0
  28. package/dist/chunk-WPAQRB5Q.mjs.map +1 -0
  29. package/dist/chunk-YBZVWLPH.js +7 -0
  30. package/dist/chunk-YBZVWLPH.js.map +1 -0
  31. package/dist/{chunk-MSNNAHDB.mjs → chunk-ZYPNVEHB.mjs} +112 -6
  32. package/dist/{chunk-MSNNAHDB.mjs.map → chunk-ZYPNVEHB.mjs.map} +1 -1
  33. package/dist/client/index.d.mts +3 -4
  34. package/dist/client/index.d.ts +3 -4
  35. package/dist/client/index.js +4 -5
  36. package/dist/client/index.js.map +1 -1
  37. package/dist/client/index.mjs +3 -4
  38. package/dist/components/index.d.mts +418 -415
  39. package/dist/components/index.d.ts +418 -415
  40. package/dist/components/index.js +6 -5
  41. package/dist/components/index.js.map +1 -1
  42. package/dist/components/index.mjs +5 -4
  43. package/dist/{config-CN23v3eJ.d.ts → config-BrkU_Ynz.d.ts} +2 -2
  44. package/dist/{config-gh88Qn4h.d.mts → config-snd2ToEB.d.mts} +2 -2
  45. package/dist/contexts/index.d.mts +15 -15
  46. package/dist/contexts/index.d.ts +15 -15
  47. package/dist/contexts/index.js +4 -5
  48. package/dist/contexts/index.js.map +1 -1
  49. package/dist/contexts/index.mjs +3 -4
  50. package/dist/core/index.d.mts +35 -4
  51. package/dist/core/index.d.ts +35 -4
  52. package/dist/core/index.js +2 -2
  53. package/dist/core/index.mjs +1 -1
  54. package/dist/features/help/index.d.mts +25 -66
  55. package/dist/features/help/index.d.ts +25 -66
  56. package/dist/features/help/index.js +114 -124
  57. package/dist/features/help/index.js.map +1 -1
  58. package/dist/features/help/index.mjs +95 -105
  59. package/dist/features/help/index.mjs.map +1 -1
  60. package/dist/features/help/server.d.mts +27 -65
  61. package/dist/features/help/server.d.ts +27 -65
  62. package/dist/features/help/server.js +4 -119
  63. package/dist/features/help/server.js.map +1 -1
  64. package/dist/features/help/server.mjs +4 -119
  65. package/dist/features/help/server.mjs.map +1 -1
  66. package/dist/help-content-config.interface-C5ESIod9.d.mts +13 -0
  67. package/dist/help-content-config.interface-C5ESIod9.d.ts +13 -0
  68. package/dist/index.d.mts +3 -3
  69. package/dist/index.d.ts +3 -3
  70. package/dist/index.js +3 -4
  71. package/dist/index.js.map +1 -1
  72. package/dist/index.mjs +2 -3
  73. package/dist/scripts/generate-web-module/__tests__/container.spec.d.ts +2 -0
  74. package/dist/scripts/generate-web-module/__tests__/container.spec.d.ts.map +1 -0
  75. package/dist/scripts/generate-web-module/__tests__/container.spec.js +54 -0
  76. package/dist/scripts/generate-web-module/__tests__/container.spec.js.map +1 -0
  77. package/dist/scripts/generate-web-module/__tests__/data-service.spec.d.ts +2 -0
  78. package/dist/scripts/generate-web-module/__tests__/data-service.spec.d.ts.map +1 -0
  79. package/dist/scripts/generate-web-module/__tests__/data-service.spec.js +19 -0
  80. package/dist/scripts/generate-web-module/__tests__/data-service.spec.js.map +1 -0
  81. package/dist/scripts/generate-web-module/__tests__/editor.spec.d.ts +2 -0
  82. package/dist/scripts/generate-web-module/__tests__/editor.spec.d.ts.map +1 -0
  83. package/dist/scripts/generate-web-module/__tests__/editor.spec.js +35 -0
  84. package/dist/scripts/generate-web-module/__tests__/editor.spec.js.map +1 -0
  85. package/dist/scripts/generate-web-module/__tests__/fixtures.d.ts +4 -0
  86. package/dist/scripts/generate-web-module/__tests__/fixtures.d.ts.map +1 -0
  87. package/dist/scripts/generate-web-module/__tests__/fixtures.js +45 -0
  88. package/dist/scripts/generate-web-module/__tests__/fixtures.js.map +1 -0
  89. package/dist/scripts/generate-web-module/__tests__/model-interface.spec.d.ts +2 -0
  90. package/dist/scripts/generate-web-module/__tests__/model-interface.spec.d.ts.map +1 -0
  91. package/dist/scripts/generate-web-module/__tests__/model-interface.spec.js +74 -0
  92. package/dist/scripts/generate-web-module/__tests__/model-interface.spec.js.map +1 -0
  93. package/dist/scripts/generate-web-module/__tests__/plumbing.spec.d.ts +2 -0
  94. package/dist/scripts/generate-web-module/__tests__/plumbing.spec.d.ts.map +1 -0
  95. package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js +21 -0
  96. package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js.map +1 -0
  97. package/dist/scripts/generate-web-module/__tests__/selector.spec.d.ts +2 -0
  98. package/dist/scripts/generate-web-module/__tests__/selector.spec.d.ts.map +1 -0
  99. package/dist/scripts/generate-web-module/__tests__/selector.spec.js +25 -0
  100. package/dist/scripts/generate-web-module/__tests__/selector.spec.js.map +1 -0
  101. package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.d.ts +2 -0
  102. package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.d.ts.map +1 -0
  103. package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.js +55 -0
  104. package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.js.map +1 -0
  105. package/dist/scripts/generate-web-module/generator.d.ts.map +1 -1
  106. package/dist/scripts/generate-web-module/generator.js +14 -11
  107. package/dist/scripts/generate-web-module/generator.js.map +1 -1
  108. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts +5 -1
  109. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts.map +1 -1
  110. package/dist/scripts/generate-web-module/templates/components/container.template.js +43 -63
  111. package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -1
  112. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts +7 -4
  113. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -1
  114. package/dist/scripts/generate-web-module/templates/components/content.template.js +103 -14
  115. package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -1
  116. package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts +1 -1
  117. package/dist/scripts/generate-web-module/templates/components/editor.template.js +139 -150
  118. package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
  119. package/dist/scripts/generate-web-module/templates/components/index.d.ts +0 -1
  120. package/dist/scripts/generate-web-module/templates/components/index.d.ts.map +1 -1
  121. package/dist/scripts/generate-web-module/templates/components/index.js +1 -3
  122. package/dist/scripts/generate-web-module/templates/components/index.js.map +1 -1
  123. package/dist/scripts/generate-web-module/templates/components/list-container.template.js +2 -2
  124. package/dist/scripts/generate-web-module/templates/components/list.template.d.ts.map +1 -1
  125. package/dist/scripts/generate-web-module/templates/components/list.template.js +6 -1
  126. package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -1
  127. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -1
  128. package/dist/scripts/generate-web-module/templates/components/selector.template.js +48 -52
  129. package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -1
  130. package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts.map +1 -1
  131. package/dist/scripts/generate-web-module/templates/data/interface.template.js +38 -11
  132. package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -1
  133. package/dist/scripts/generate-web-module/templates/data/model.template.d.ts.map +1 -1
  134. package/dist/scripts/generate-web-module/templates/data/model.template.js +83 -26
  135. package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -1
  136. package/dist/scripts/generate-web-module/templates/data/service.template.d.ts.map +1 -1
  137. package/dist/scripts/generate-web-module/templates/data/service.template.js +21 -1
  138. package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -1
  139. package/dist/scripts/generate-web-module/templates/index.d.ts +1 -1
  140. package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -1
  141. package/dist/scripts/generate-web-module/templates/index.js +1 -2
  142. package/dist/scripts/generate-web-module/templates/index.js.map +1 -1
  143. package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -1
  144. package/dist/scripts/generate-web-module/templates/module.template.js +11 -3
  145. package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -1
  146. package/dist/scripts/generate-web-module/templates/table-hook.template.js +31 -1
  147. package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -1
  148. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +0 -1
  149. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -1
  150. package/dist/scripts/generate-web-module/transformers/import-resolver.js +0 -1
  151. package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -1
  152. package/dist/scripts/generate-web-module/transformers/relationship-key.d.ts +5 -0
  153. package/dist/scripts/generate-web-module/transformers/relationship-key.d.ts.map +1 -0
  154. package/dist/scripts/generate-web-module/transformers/relationship-key.js +21 -0
  155. package/dist/scripts/generate-web-module/transformers/relationship-key.js.map +1 -0
  156. package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -1
  157. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +2 -0
  158. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -1
  159. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +21 -0
  160. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -1
  161. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +17 -0
  162. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -1
  163. package/dist/server/index.js +3 -3
  164. package/dist/server/index.mjs +1 -1
  165. package/dist/testing/index.d.mts +1 -2
  166. package/dist/testing/index.d.ts +1 -2
  167. package/package.json +1 -6
  168. package/scripts/generate-web-module/__tests__/container.spec.ts +58 -0
  169. package/scripts/generate-web-module/__tests__/data-service.spec.ts +18 -0
  170. package/scripts/generate-web-module/__tests__/editor.spec.ts +36 -0
  171. package/scripts/generate-web-module/__tests__/fixtures.ts +43 -0
  172. package/scripts/generate-web-module/__tests__/model-interface.spec.ts +77 -0
  173. package/scripts/generate-web-module/__tests__/plumbing.spec.ts +21 -0
  174. package/scripts/generate-web-module/__tests__/selector.spec.ts +24 -0
  175. package/scripts/generate-web-module/__tests__/table-module-list.spec.ts +59 -0
  176. package/scripts/generate-web-module/generator.ts +14 -13
  177. package/scripts/generate-web-module/templates/components/container.template.ts +51 -66
  178. package/scripts/generate-web-module/templates/components/content.template.ts +107 -15
  179. package/scripts/generate-web-module/templates/components/editor.template.ts +140 -152
  180. package/scripts/generate-web-module/templates/components/index.ts +0 -1
  181. package/scripts/generate-web-module/templates/components/list-container.template.ts +2 -2
  182. package/scripts/generate-web-module/templates/components/list.template.ts +6 -1
  183. package/scripts/generate-web-module/templates/components/selector.template.ts +48 -52
  184. package/scripts/generate-web-module/templates/data/interface.template.ts +44 -12
  185. package/scripts/generate-web-module/templates/data/model.template.ts +88 -28
  186. package/scripts/generate-web-module/templates/data/service.template.ts +23 -1
  187. package/scripts/generate-web-module/templates/index.ts +0 -1
  188. package/scripts/generate-web-module/templates/module.template.ts +13 -3
  189. package/scripts/generate-web-module/templates/table-hook.template.ts +31 -2
  190. package/scripts/generate-web-module/transformers/import-resolver.ts +0 -2
  191. package/scripts/generate-web-module/transformers/relationship-key.ts +18 -0
  192. package/scripts/generate-web-module/transformers/relationship-resolver.ts +2 -0
  193. package/scripts/generate-web-module/types/json-schema.interface.ts +9 -0
  194. package/scripts/generate-web-module/types/template-data.interface.ts +7 -0
  195. package/src/client/config.ts +1 -1
  196. package/src/components/index.ts +1 -0
  197. package/src/components/viewers/BlockNoteViewer.tsx +27 -0
  198. package/src/components/viewers/BlockNoteViewerContainer.tsx +10 -0
  199. package/src/core/registry/helpStore.ts +2 -19
  200. package/src/features/assistant-message/components/parts/MessageSourcesContainer.tsx +4 -0
  201. package/src/features/assistant-message/components/parts/tabs/ContentsTab.tsx +5 -5
  202. package/src/features/assistant-message/components/parts/tabs/ReferencesTab.tsx +3 -2
  203. package/src/features/help/components/HelpArticleBody.tsx +27 -19
  204. package/src/features/help/components/HelpHint.tsx +20 -11
  205. package/src/features/help/components/HelpSearchResultRow.tsx +16 -6
  206. package/src/features/help/components/HelpSideNav.tsx +54 -19
  207. package/src/features/help/components/__tests__/HelpAskAi.spec.tsx +1 -1
  208. package/src/features/help/components/__tests__/HelpHint.spec.tsx +17 -17
  209. package/src/features/help/components/__tests__/HelpSearchResultRow.spec.tsx +18 -19
  210. package/src/features/help/components/__tests__/HelpSideNav.spec.tsx +20 -22
  211. package/src/features/help/contexts/HelpContext.spec.tsx +1 -3
  212. package/src/features/help/index.ts +3 -6
  213. package/src/features/help/interfaces/help-content-config.interface.ts +0 -5
  214. package/src/features/help/server-entry.ts +6 -15
  215. package/src/features/help/types/help-article.types.ts +2 -4
  216. package/src/features/how-to/HowToModule.ts +14 -1
  217. package/src/features/how-to/components/forms/HowToEditor.tsx +108 -4
  218. package/src/features/how-to/data/HowTo.spec.ts +43 -0
  219. package/src/features/how-to/data/HowTo.ts +47 -4
  220. package/src/features/how-to/data/HowToInterface.ts +14 -1
  221. package/src/features/how-to/data/HowToService.ts +61 -0
  222. package/dist/BlockNoteEditor-3XYBZLWO.js.map +0 -1
  223. package/dist/chunk-3J7RQBF3.js +0 -123
  224. package/dist/chunk-3J7RQBF3.js.map +0 -1
  225. package/dist/chunk-7E3O52U5.js.map +0 -1
  226. package/dist/chunk-CFI4WZ5R.js.map +0 -1
  227. package/dist/chunk-CQID6RCF.mjs +0 -38
  228. package/dist/chunk-CQID6RCF.mjs.map +0 -1
  229. package/dist/chunk-CRTVAQEK.mjs.map +0 -1
  230. package/dist/chunk-MZTKPPET.mjs +0 -123
  231. package/dist/chunk-MZTKPPET.mjs.map +0 -1
  232. package/dist/chunk-UHO3KUUH.js.map +0 -1
  233. package/dist/chunk-YQQHAFBS.js +0 -38
  234. package/dist/chunk-YQQHAFBS.js.map +0 -1
  235. package/dist/features/help/server/createHelpAssetRouteHandler.d.mts +0 -11
  236. package/dist/features/help/server/createHelpAssetRouteHandler.d.ts +0 -11
  237. package/dist/features/help/server/createHelpAssetRouteHandler.js +0 -43
  238. package/dist/features/help/server/createHelpAssetRouteHandler.js.map +0 -1
  239. package/dist/features/help/server/createHelpAssetRouteHandler.mjs +0 -43
  240. package/dist/features/help/server/createHelpAssetRouteHandler.mjs.map +0 -1
  241. package/dist/help-content-config.interface-B9L02u9i.d.mts +0 -50
  242. package/dist/help-content-config.interface-B9L02u9i.d.ts +0 -50
  243. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts +0 -14
  244. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts.map +0 -1
  245. package/dist/scripts/generate-web-module/templates/components/details.template.js +0 -112
  246. package/dist/scripts/generate-web-module/templates/components/details.template.js.map +0 -1
  247. package/scripts/generate-web-module/templates/components/details.template.ts +0 -116
  248. package/src/features/help/components/mdx/Callout.tsx +0 -21
  249. package/src/features/help/components/mdx/EntityRef.tsx +0 -18
  250. package/src/features/help/components/mdx/KeyBinding.tsx +0 -6
  251. package/src/features/help/components/mdx/Related.tsx +0 -33
  252. package/src/features/help/components/mdx/Screenshot.tsx +0 -9
  253. package/src/features/help/components/mdx/Steps.tsx +0 -21
  254. package/src/features/help/components/mdx/Video.tsx +0 -8
  255. package/src/features/help/components/mdx/mdx-server-components.ts +0 -23
  256. package/src/features/help/components/mdx/mdxComponents.ts +0 -9
  257. package/src/features/help/hooks/useHelpArticle.ts +0 -7
  258. package/src/features/help/hooks/useHelpManifest.ts +0 -5
  259. package/src/features/help/server/__tests__/createHelpAssetRouteHandler.spec.ts +0 -43
  260. package/src/features/help/server/createHelpAssetRouteHandler.ts +0 -35
  261. package/src/features/help/server/generateHelpArticleMetadata.ts +0 -18
  262. package/src/features/help/server/generateHelpArticleStaticParams.ts +0 -7
  263. package/src/features/help/server/generateHelpModeStaticParams.ts +0 -5
  264. package/src/features/help/server/getHelpContent.ts +0 -17
  265. package/src/features/help/server/index.ts +0 -8
  266. package/src/features/help/server/serializeHelpArticle.tsx +0 -46
  267. package/src/features/help/utils/__tests__/helpNavigation.spec.ts +0 -70
  268. package/src/features/help/utils/helpNavigation.ts +0 -29
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { FrontendTemplateData, FrontendField } from "../types/template-data.interface";
8
- import { toCamelCase } from "../transformers/name-transformer";
8
+ import { toCamelCase, pluralize } from "../transformers/name-transformer";
9
9
 
10
10
  /**
11
11
  * Generate the table structure hook file content
@@ -96,7 +96,7 @@ registerTableGenerator(Modules.${names.pascalCase}, use${names.pascalCase}TableS
96
96
  * Generate field column map entries
97
97
  */
98
98
  function generateFieldColumnEntries(data: FrontendTemplateData): string {
99
- const { names, fields, extendsContent } = data;
99
+ const { names, fields, extendsContent, relationships } = data;
100
100
  const entries: string[] = [];
101
101
 
102
102
  // Name field (with link and tooltip for tldr if Content-extending)
@@ -183,8 +183,37 @@ function generateFieldColumnEntries(data: FrontendTemplateData): string {
183
183
  enableSorting: false,
184
184
  enableHiding: false,
185
185
  }),`);
186
+ } else if (field.type === "date" || field.type === "datetime") {
187
+ entries.push(` [${names.pascalCase}Fields.${field.name}]: () =>
188
+ cellDate({
189
+ name: "${field.name}",
190
+ title: t(\`features.${names.camelCase}.fields.${field.name}.label\`),
191
+ }),`);
186
192
  }
187
193
  });
188
194
 
195
+ // Relationship columns (rendered when flagged for table display)
196
+ relationships
197
+ .filter((rel) => rel.showInTable)
198
+ .forEach((rel) => {
199
+ const key = rel.single
200
+ ? toCamelCase(rel.alias || rel.variant || rel.name)
201
+ : pluralize(toCamelCase(rel.alias || rel.name));
202
+ const accessor = rel.single
203
+ ? `${names.camelCase}.${key}?.name ?? ""`
204
+ : `${names.camelCase}.${key}.map((r) => r.name).join(", ")`;
205
+ entries.push(` [${names.pascalCase}Fields.${key}]: () => ({
206
+ id: "${key}",
207
+ accessorKey: "${key}",
208
+ header: t(\`generic.relationships.${key}.label\`),
209
+ cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
210
+ const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
211
+ return <span>{${accessor}}</span>;
212
+ },
213
+ enableSorting: false,
214
+ enableHiding: false,
215
+ }),`);
216
+ });
217
+
189
218
  return entries.join("\n");
190
219
  }
@@ -177,7 +177,6 @@ export interface ModuleFilePaths {
177
177
  selector: string;
178
178
  multiSelector: string;
179
179
  list: string;
180
- details: string;
181
180
  content: string;
182
181
  container: string;
183
182
  listContainer: string;
@@ -220,7 +219,6 @@ export function buildFilePaths(
220
219
  selector: `${basePath}/components/forms/${names.pascalCase}Selector.tsx`,
221
220
  multiSelector: `${basePath}/components/forms/${names.pascalCase}MultiSelector.tsx`,
222
221
  list: `${basePath}/components/lists/${names.pascalCase}List.tsx`,
223
- details: `${basePath}/components/details/${names.pascalCase}Details.tsx`,
224
222
  content: `${basePath}/components/details/${names.pascalCase}Content.tsx`,
225
223
  container: `${basePath}/components/containers/${names.pascalCase}Container.tsx`,
226
224
  listContainer: `${basePath}/components/containers/${names.pascalCase}ListContainer.tsx`,
@@ -0,0 +1,18 @@
1
+ import { FrontendRelationship } from "../types/template-data.interface";
2
+ import { toKebabCase, pluralize } from "./name-transformer";
3
+
4
+ /** JSON:API wire key for a relationship. Schema dtoKey wins; otherwise derive
5
+ * identically to the backend mapper so both sides of the wire agree. */
6
+ export function resolveRelationshipKey(rel: FrontendRelationship): string {
7
+ if (rel.dtoKey) return rel.dtoKey;
8
+ // NOTE: Author relationships are normally NOT serialized through this helper — the backend
9
+ // special-cases Author via contextKey/currentUser and the model template skips Author
10
+ // serialization, so callers must not rely on this helper for Author wire keys.
11
+ if (rel.variant) {
12
+ return rel.single ? rel.variant.toLowerCase() : pluralize(rel.variant.toLowerCase());
13
+ }
14
+ if (rel.alias) {
15
+ return rel.single ? toKebabCase(rel.alias) : pluralize(toKebabCase(rel.alias));
16
+ }
17
+ return rel.single ? rel.name.toLowerCase() : pluralize(rel.name.toLowerCase());
18
+ }
@@ -122,6 +122,8 @@ export function resolveRelationship(rel: JsonRelationshipDefinition, targetHasNa
122
122
  modelKebab,
123
123
  fields,
124
124
  targetHasName: targetHasNameMap?.get(rel.name) ?? true, // Foundation entities default to true
125
+ dtoKey: rel.dtoKey ?? "",
126
+ showInTable: rel.showInTable === true,
125
127
  };
126
128
  }
127
129
 
@@ -13,6 +13,7 @@ export interface JsonFieldDefinition {
13
13
  name: string;
14
14
  type: string;
15
15
  nullable: boolean;
16
+ readOnly?: boolean;
16
17
  }
17
18
 
18
19
  /**
@@ -29,6 +30,10 @@ export interface JsonRelationshipDefinition {
29
30
  toNode: boolean; // Backend-specific, ignored in frontend
30
31
  nullable: boolean;
31
32
  fields?: JsonFieldDefinition[]; // Relationship property fields (stored on edges)
33
+ /** Explicit JSON:API wire key. When absent it is derived identically to the backend mapper. */
34
+ dtoKey?: string;
35
+ description?: string;
36
+ showInTable?: boolean;
32
37
  }
33
38
 
34
39
  /**
@@ -44,4 +49,8 @@ export interface JsonModuleDefinition {
44
49
  relationships: JsonRelationshipDefinition[];
45
50
  /** Explicitly set whether this module extends Content. If not set, auto-detects based on fields. */
46
51
  extendsContent?: boolean;
52
+ featureId?: string;
53
+ displayProp?: string;
54
+ containerTabs?: { activity?: boolean; relations?: { module: string; listProp: string; directory: string }[] };
55
+ inclusions?: { related?: { endpoint: string; fields: string[] }[] };
47
56
  }
@@ -30,6 +30,7 @@ export interface FrontendField {
30
30
  formComponent: FormComponentType;
31
31
  nullable: boolean;
32
32
  isContentField: boolean; // Indicates BlockNoteEditor
33
+ readOnly?: boolean;
33
34
  }
34
35
 
35
36
  /**
@@ -68,6 +69,8 @@ export interface FrontendRelationship {
68
69
  modelKebab: string; // e.g., "user"
69
70
  fields?: FrontendField[]; // Relationship property fields (stored on edges)
70
71
  targetHasName: boolean; // Whether the target entity has a "name" field (or extends Content)
72
+ dtoKey: string; // resolved wire key; "" means derive at use-site
73
+ showInTable: boolean;
71
74
  }
72
75
 
73
76
  /**
@@ -153,6 +156,10 @@ export interface FrontendTemplateData {
153
156
 
154
157
  // For service generation
155
158
  relationshipServiceMethods: RelationshipServiceMethod[];
159
+ featureId?: string;
160
+ displayProp: string;
161
+ containerTabs: { activity: boolean; relations: { module: string; listProp: string; directory: string }[] };
162
+ relatedInclusions: { endpoint: string; fields: string[] }[];
156
163
  }
157
164
 
158
165
  /**
@@ -18,7 +18,7 @@ let _clientConfig: {
18
18
  /**
19
19
  * Configure the JSON:API client. This is the main configuration function.
20
20
  * This is typically called during app initialization.
21
- * @param config.helpContent - Optional help-content config (manifest, brand, redirects). Forwarded to the help feature's globalThis-backed store; not stored on the client config.
21
+ * @param config.helpContent - Optional help-content config (brand only). Forwarded to the help feature's globalThis-backed store; not stored on the client config.
22
22
  */
23
23
  export function configureJsonApi(config: {
24
24
  apiUrl: string;
@@ -6,6 +6,7 @@ export * from "./containers";
6
6
  export * from "./contents";
7
7
  export * from "./details";
8
8
  export * from "./editors";
9
+ export { BlockNoteViewerContainer } from "./viewers/BlockNoteViewerContainer";
9
10
  export * from "./errors";
10
11
  export * from "./forms";
11
12
  export * from "./navigations";
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { useCreateBlockNote } from "@blocknote/react";
4
+ import type { PartialBlock } from "@blocknote/core";
5
+ // Mirror BlockNoteEditor.tsx's BlockNoteView import + style.css. Default schema
6
+ // only — no useCurrentUserContext, uploadImage, AI, or custom inline specs, so
7
+ // this can render on auth-free public pages.
8
+ import { BlockNoteView } from "@blocknote/shadcn";
9
+ import "@blocknote/shadcn/style.css";
10
+
11
+ function normalize(content: unknown): PartialBlock[] | undefined {
12
+ if (Array.isArray(content) && content.length > 0) return content as PartialBlock[];
13
+ if (typeof content === "string" && content.trim()) {
14
+ try {
15
+ const parsed = JSON.parse(content);
16
+ if (Array.isArray(parsed) && parsed.length > 0) return parsed as PartialBlock[];
17
+ } catch {
18
+ /* plain string — fall through to undefined (rendered as empty) */
19
+ }
20
+ }
21
+ return undefined;
22
+ }
23
+
24
+ export function BlockNoteViewer({ content }: { content: unknown }) {
25
+ const editor = useCreateBlockNote({ initialContent: normalize(content) });
26
+ return <BlockNoteView editor={editor} editable={false} theme="light" />;
27
+ }
@@ -0,0 +1,10 @@
1
+ "use client";
2
+
3
+ import dynamic from "next/dynamic";
4
+ import React from "react";
5
+
6
+ const BlockNoteViewer = dynamic(() => import("./BlockNoteViewer").then((m) => m.BlockNoteViewer), { ssr: false });
7
+
8
+ export const BlockNoteViewerContainer = React.memo(function ViewerContainer(props: { content: unknown }) {
9
+ return <BlockNoteViewer {...props} />;
10
+ });
@@ -1,32 +1,23 @@
1
1
  /**
2
2
  * Centralized help-content store accessible from client and server contexts.
3
- * Uses globalThis Symbol keys to persist across HMR/Turbopack reloads and to
3
+ * Uses a globalThis Symbol key to persist across HMR/Turbopack reloads and to
4
4
  * bridge between the client-side `configureJsonApi` (in `client/config.ts`)
5
- * and server-side consumers (HelpProvider, getHelpContent, serializeHelpArticle).
5
+ * and the HelpProvider context. Holds the brand-only HelpContentConfig.
6
6
  *
7
7
  * NO external dependencies to avoid circular imports.
8
8
  *
9
9
  * Pattern mirrors `bootstrapStore.ts`.
10
10
  */
11
11
 
12
- // Help content config shape is duplicated here as `unknown` to avoid pulling
13
- // the help feature into this core file. Callers cast on read.
14
- type HelpReader = (article: { path: string }) => Promise<string>;
15
-
16
12
  const HELP_CONTENT_KEY = Symbol.for("nextjs-jsonapi:helpContent");
17
- const HELP_READER_KEY = Symbol.for("nextjs-jsonapi:helpReader");
18
13
 
19
14
  const globalStore = globalThis as unknown as {
20
15
  [HELP_CONTENT_KEY]?: unknown | null;
21
- [HELP_READER_KEY]?: HelpReader | null;
22
16
  };
23
17
 
24
18
  if (globalStore[HELP_CONTENT_KEY] === undefined) {
25
19
  globalStore[HELP_CONTENT_KEY] = null;
26
20
  }
27
- if (globalStore[HELP_READER_KEY] === undefined) {
28
- globalStore[HELP_READER_KEY] = null;
29
- }
30
21
 
31
22
  export function _setStaticHelpContent(cfg: unknown | null): void {
32
23
  globalStore[HELP_CONTENT_KEY] = cfg;
@@ -35,11 +26,3 @@ export function _setStaticHelpContent(cfg: unknown | null): void {
35
26
  export function _getStaticHelpContent<T = unknown>(): T | null {
36
27
  return (globalStore[HELP_CONTENT_KEY] as T | null) ?? null;
37
28
  }
38
-
39
- export function setHelpReader(reader: HelpReader): void {
40
- globalStore[HELP_READER_KEY] = reader;
41
- }
42
-
43
- export function _getStaticHelpReader(): HelpReader | null {
44
- return globalStore[HELP_READER_KEY] ?? null;
45
- }
@@ -20,6 +20,10 @@ class SourcesFetcher extends ClientAbstractService {
20
20
  }): Promise<ApiDataInterface[]> {
21
21
  const endpoint = new EndpointCreator({ endpoint: params.module });
22
22
  endpoint.addAdditionalParam(params.idsParam, params.ids.join(","));
23
+ // The list endpoint paginates and does not honour the `<x>Ids` filter, so a cited
24
+ // entity beyond the default first page never comes back and its citation falls back
25
+ // to "<Type> <id>". Request the full set and resolve client-side by id.
26
+ endpoint.addAdditionalParam("fetchAll", "true");
23
27
  if (params.module.inclusions?.lists?.fields) endpoint.limitToFields(params.module.inclusions.lists.fields);
24
28
  if (params.module.inclusions?.lists?.types) endpoint.limitToType(params.module.inclusions.lists.types);
25
29
  return this.callApi<ApiDataInterface[]>({
@@ -64,11 +64,11 @@ export function ContentsTab({ citations, sources }: Props) {
64
64
  } catch {
65
65
  return null;
66
66
  }
67
- // Help-content HowTos are public articles, not admin records: route to
68
- // /help/<mode>/<slug> instead of the module's /administration/howtos
69
- // page when the entity carries a helpContentSlug.
70
- const helpContentSlug = (source as any).helpContentSlug as string | undefined;
71
- const href = helpContentSlug ? `/help/${helpContentSlug}` : generate({ page: module, id: source.id });
67
+ // Help HowTos are public articles, not admin records: route to
68
+ // /help/<mode>/<slug> instead of the module's /administration/howtos page.
69
+ const howToType = (source as any).howToType as string | undefined;
70
+ const slug = (source as any).slug as string | undefined;
71
+ const href = howToType && slug ? `/help/${howToType}/${slug}` : generate({ page: module, id: source.id });
72
72
  const name = (source as any).name ?? source.identifier;
73
73
  return (
74
74
  <TableRow key={`${source.type}/${source.id}`}>
@@ -33,8 +33,9 @@ export function ReferencesTab({ references }: Props) {
33
33
  } catch {
34
34
  return null;
35
35
  }
36
- const helpContentSlug = (ref as any).helpContentSlug as string | undefined;
37
- const href = helpContentSlug ? `/help/${helpContentSlug}` : generate({ page: module, id: ref.id });
36
+ const howToType = (ref as any).howToType as string | undefined;
37
+ const slug = (ref as any).slug as string | undefined;
38
+ const href = howToType && slug ? `/help/${howToType}/${slug}` : generate({ page: module, id: ref.id });
38
39
  return (
39
40
  <TableRow key={`${ref.type}/${ref.id}`}>
40
41
  <TableCell>
@@ -1,48 +1,56 @@
1
1
  "use client";
2
+
2
3
  import { ReactNode } from "react";
3
4
  import Link from "next/link";
4
5
  import { useFormatter, useTranslations } from "next-intl";
5
- import type { HelpArticle } from "../types/help-article.types";
6
6
  import { usePageUrlGenerator } from "../../../client";
7
- import { useHelp } from "../contexts/HelpContext";
8
- import { articleUrl } from "../utils/articleUrl";
9
- import { prevNextWithinMode } from "../utils/helpNavigation";
10
7
 
11
- export function HelpArticleBody({ article, children }: { article: HelpArticle; children: ReactNode }) {
8
+ type Sibling = { howToType: string; slug: string; title: string };
9
+
10
+ export function HelpArticleBody(props: {
11
+ howToType: string;
12
+ title: string;
13
+ summary?: string;
14
+ updatedAt?: string;
15
+ prev?: Sibling | null;
16
+ next?: Sibling | null;
17
+ children: ReactNode;
18
+ }) {
12
19
  const t = useTranslations();
13
20
  const format = useFormatter();
14
21
  const generateUrl = usePageUrlGenerator();
15
- const { manifest } = useHelp();
16
- const { prev, next } = prevNextWithinMode(manifest, article);
22
+ const { howToType, title, summary, updatedAt, prev, next, children } = props;
17
23
 
18
24
  return (
19
25
  <article className="prose dark:prose-invert max-w-none">
20
26
  <nav className="text-muted-foreground mb-2 text-xs">
21
27
  <Link href={generateUrl({ page: "/help" })}>Help</Link>
22
28
  {" · "}
23
- <Link href={generateUrl({ page: `/help/${article.mode}` })}>{t(`help.modes.${article.mode}`)}</Link>
29
+ <Link href={generateUrl({ page: `/help/${howToType}` })}>{t(`help.modes.${howToType}`)}</Link>
24
30
  </nav>
25
- <h1>{article.title}</h1>
26
- <p className="text-muted-foreground !mt-0 text-base">{article.summary}</p>
31
+ <h1>{title}</h1>
32
+ {summary ? <p className="text-muted-foreground !mt-0 text-base">{summary}</p> : null}
27
33
  {children}
28
34
  <hr className="my-6" />
29
- <div className="text-muted-foreground text-xs">
30
- <span>
31
- {t("help.article.lastUpdated", {
32
- date: format.dateTime(new Date(article.lastUpdated), { dateStyle: "short" }),
33
- })}
34
- </span>
35
- </div>
35
+ {updatedAt ? (
36
+ <div className="text-muted-foreground text-xs">
37
+ <span>
38
+ {t("help.article.lastUpdated", {
39
+ date: format.dateTime(new Date(updatedAt), { dateStyle: "short" }),
40
+ })}
41
+ </span>
42
+ </div>
43
+ ) : null}
36
44
  <div className="mt-4 flex justify-between text-sm">
37
45
  {prev ? (
38
- <Link href={articleUrl(generateUrl, prev)} className="hover:underline">
46
+ <Link href={generateUrl({ page: `/help/${prev.howToType}/${prev.slug}` })} className="hover:underline">
39
47
  ← {t("help.article.previous")}: {prev.title}
40
48
  </Link>
41
49
  ) : (
42
50
  <span />
43
51
  )}
44
52
  {next ? (
45
- <Link href={articleUrl(generateUrl, next)} className="hover:underline">
53
+ <Link href={generateUrl({ page: `/help/${next.howToType}/${next.slug}` })} className="hover:underline">
46
54
  {t("help.article.next")}: {next.title} →
47
55
  </Link>
48
56
  ) : (
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { useMemo, useState } from "react";
2
+ import { useEffect, useMemo, useState } from "react";
3
3
  import { useTranslations } from "next-intl";
4
4
  import Link from "next/link";
5
5
  import { HelpCircleIcon } from "lucide-react";
@@ -13,20 +13,29 @@ import {
13
13
  SheetDescription,
14
14
  } from "../../../shadcnui";
15
15
  import { usePageUrlGenerator } from "../../../client";
16
- import { useHelp } from "../contexts/HelpContext";
17
- import { articleUrl } from "../utils/articleUrl";
18
- import type { HelpArticle } from "../types/help-article.types";
16
+ import { HowToService } from "../../how-to/data/HowToService";
17
+ import type { HowToInterface } from "../../how-to/data/HowToInterface";
19
18
 
20
19
  export function HelpHint({ contextKey }: { contextKey: string }) {
21
20
  const t = useTranslations();
22
21
  const generateUrl = usePageUrlGenerator();
23
- const { manifest } = useHelp();
24
22
  const [open, setOpen] = useState(false);
25
- const [picked, setPicked] = useState<HelpArticle | null>(null);
23
+ const [picked, setPicked] = useState<HowToInterface | null>(null);
24
+
25
+ const [articles, setArticles] = useState<HowToInterface[]>([]);
26
+ useEffect(() => {
27
+ let active = true;
28
+ HowToService.findPublished()
29
+ .then((list) => active && setArticles(list))
30
+ .catch(() => active && setArticles([]));
31
+ return () => {
32
+ active = false;
33
+ };
34
+ }, []);
26
35
 
27
36
  const matches = useMemo(
28
- () => manifest.filter((a) => a.contextualKeys.includes(contextKey) && !a.draft),
29
- [manifest, contextKey],
37
+ () => articles.filter((a) => a.contextualKeys.includes(contextKey) && !a.draft),
38
+ [articles, contextKey],
30
39
  );
31
40
 
32
41
  if (matches.length === 0) return null;
@@ -45,13 +54,13 @@ export function HelpHint({ contextKey }: { contextKey: string }) {
45
54
  </SheetTrigger>
46
55
  <SheetContent side="right" className="w-full max-w-md sm:max-w-lg">
47
56
  <SheetHeader>
48
- <SheetTitle>{active ? active.title : t("help.hint.pickArticle")}</SheetTitle>
57
+ <SheetTitle>{active ? active.name : t("help.hint.pickArticle")}</SheetTitle>
49
58
  <SheetDescription>{active?.summary ?? ""}</SheetDescription>
50
59
  </SheetHeader>
51
60
  {active ? (
52
61
  <div className="mt-4 space-y-3">
53
62
  <p className="text-muted-foreground text-sm">{active.summary}</p>
54
- <Link href={articleUrl(generateUrl, active)} className="text-sm">
63
+ <Link href={generateUrl({ page: `/help/${active.howToType}/${active.slug}` })} className="text-sm">
55
64
  {t("help.hint.viewArticle")}
56
65
  </Link>
57
66
  </div>
@@ -64,7 +73,7 @@ export function HelpHint({ contextKey }: { contextKey: string }) {
64
73
  onClick={() => setPicked(a)}
65
74
  className="hover:bg-muted block w-full rounded px-2 py-1 text-left text-sm"
66
75
  >
67
- <div className="font-medium">{a.title}</div>
76
+ <div className="font-medium">{a.name}</div>
68
77
  <div className="text-muted-foreground text-xs">{a.summary}</div>
69
78
  </button>
70
79
  </li>
@@ -1,10 +1,12 @@
1
1
  "use client";
2
2
  import Link from "next/link";
3
3
  import { useTranslations } from "next-intl";
4
+ import { useEffect, useState } from "react";
4
5
  import { LifeBuoyIcon, ArrowUpRightIcon } from "lucide-react";
5
6
  import { CommandItem } from "../../../shadcnui";
6
7
  import { usePageUrlGenerator } from "../../../client";
7
- import { useHelp } from "../contexts/HelpContext";
8
+ import { HowToService } from "../../how-to/data/HowToService";
9
+ import type { HowToInterface } from "../../how-to/data/HowToInterface";
8
10
 
9
11
  export interface HelpSearchResultRowProps {
10
12
  result: { id: string; name: string; entityType: string };
@@ -14,8 +16,16 @@ export interface HelpSearchResultRowProps {
14
16
  export function HelpSearchResultRow({ result, onSelect }: HelpSearchResultRowProps) {
15
17
  const t = useTranslations();
16
18
  const generateUrl = usePageUrlGenerator();
17
- const { manifest } = useHelp();
18
- const article = manifest.find((a) => a.id === result.id);
19
+ const [article, setArticle] = useState<HowToInterface | null>(null);
20
+ useEffect(() => {
21
+ let active = true;
22
+ HowToService.findOne({ id: result.id })
23
+ .then((a) => active && setArticle(a))
24
+ .catch(() => active && setArticle(null));
25
+ return () => {
26
+ active = false;
27
+ };
28
+ }, [result.id]);
19
29
 
20
30
  if (!article) {
21
31
  return (
@@ -32,16 +42,16 @@ export function HelpSearchResultRow({ result, onSelect }: HelpSearchResultRowPro
32
42
  return (
33
43
  <CommandItem value={result.id} onSelect={onSelect} className="cursor-pointer p-0">
34
44
  <Link
35
- href={generateUrl({ page: `/help/${article.mode}/${article.slug}` })}
45
+ href={generateUrl({ page: `/help/${article.howToType}/${article.slug}` })}
36
46
  className="hover:bg-muted flex w-full items-center gap-3 rounded px-3 py-2"
37
47
  >
38
48
  <div className="bg-muted text-muted-foreground flex h-10 w-10 shrink-0 items-center justify-center rounded-full">
39
49
  <LifeBuoyIcon className="h-5 w-5" />
40
50
  </div>
41
51
  <div className="flex min-w-0 flex-1 flex-col">
42
- <span className="text-foreground truncate text-sm font-semibold">{article.title}</span>
52
+ <span className="text-foreground truncate text-sm font-semibold">{article.name}</span>
43
53
  <span className="text-muted-foreground truncate text-xs">
44
- {t(`help.modes.${article.mode}`)} · {article.summary}
54
+ {t(`help.modes.${article.howToType}`)} · {article.summary}
45
55
  </span>
46
56
  </div>
47
57
  <ArrowUpRightIcon className="text-muted-foreground h-5 w-5 shrink-0" />
@@ -1,24 +1,60 @@
1
1
  "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
2
4
  import { useTranslations } from "next-intl";
3
5
  import Link from "next/link";
4
6
  import { usePathname } from "next/navigation";
5
7
  import { HELP_MODES } from "../types/help-article.types";
6
8
  import { usePageUrlGenerator } from "../../../client";
7
- import { useHelp } from "../contexts/HelpContext";
8
9
  import { useHelpFilter } from "../hooks/useHelpFilter";
9
- import { buildHelpNav } from "../utils/helpNavigation";
10
- import { articleUrl, modeUrl } from "../utils/articleUrl";
10
+ import { HowToService } from "../../how-to/data/HowToService";
11
+ import type { HowToInterface } from "../../how-to/data/HowToInterface";
11
12
 
12
13
  export function HelpSideNav() {
13
14
  const t = useTranslations();
14
15
  const generateUrl = usePageUrlGenerator();
15
16
  const pathname = usePathname();
16
- const { manifest } = useHelp();
17
- const groups = buildHelpNav(manifest);
18
- const allArticles = groups.flatMap((g) => g.articles);
19
- const { query, setQuery, filtered } = useHelpFilter(allArticles);
17
+ const [articles, setArticles] = useState<HowToInterface[]>([]);
18
+
19
+ useEffect(() => {
20
+ let active = true;
21
+ HowToService.findPublished()
22
+ .then((list) => active && setArticles(list))
23
+ .catch(() => active && setArticles([]));
24
+ return () => {
25
+ active = false;
26
+ };
27
+ }, []);
28
+
29
+ // useHelpFilter indexes by title/summary/tags — map HowTo getters into the shape it expects.
30
+ const indexable = useMemo(
31
+ () =>
32
+ articles.map((a) => ({
33
+ id: a.id,
34
+ title: a.name,
35
+ summary: a.summary ?? "",
36
+ tags: a.tags,
37
+ mode: a.howToType ?? "",
38
+ slug: a.slug ?? "",
39
+ })),
40
+ [articles],
41
+ );
42
+ const { query, setQuery, filtered } = useHelpFilter(indexable as any);
20
43
  const filtering = query.trim().length > 0;
21
44
 
45
+ const groups = useMemo(
46
+ () =>
47
+ HELP_MODES.map((mode) => ({
48
+ mode,
49
+ articles: articles
50
+ .filter((a) => a.howToType === mode && !a.draft)
51
+ .sort((x, y) => (x.order ?? 0) - (y.order ?? 0)),
52
+ })),
53
+ [articles],
54
+ );
55
+
56
+ const isActive = (mode: string, slug: string) => pathname.endsWith(`/help/${mode}/${slug}`);
57
+
22
58
  return (
23
59
  <nav aria-label="Help navigation" className="flex h-full flex-col gap-3 overflow-y-auto p-4">
24
60
  <input
@@ -29,17 +65,17 @@ export function HelpSideNav() {
29
65
  className="border-input bg-background w-full rounded-md border px-3 py-2 text-sm"
30
66
  />
31
67
  {filtering ? (
32
- filtered.length === 0 ? (
68
+ (filtered as any[]).length === 0 ? (
33
69
  <p className="text-muted-foreground text-sm">{t("help.sideNav.noMatches")}</p>
34
70
  ) : (
35
71
  <ul className="space-y-1">
36
- {filtered.map((a) => (
72
+ {(filtered as any[]).map((a) => (
37
73
  <li key={a.id}>
38
74
  <Link
39
- href={articleUrl(generateUrl, a)}
75
+ href={generateUrl({ page: `/help/${a.mode}/${a.slug}` })}
40
76
  className={
41
77
  "hover:bg-muted block rounded px-2 py-1 text-sm " +
42
- (pathname.endsWith(`/help/${a.mode}/${a.slug}`) ? "bg-muted font-medium" : "")
78
+ (isActive(a.mode, a.slug) ? "bg-muted font-medium" : "")
43
79
  }
44
80
  >
45
81
  {a.title}
@@ -49,28 +85,27 @@ export function HelpSideNav() {
49
85
  </ul>
50
86
  )
51
87
  ) : (
52
- HELP_MODES.map((mode) => {
53
- const group = groups.find((g) => g.mode === mode)!;
88
+ groups.map((group) => {
54
89
  if (group.articles.length === 0) return null;
55
90
  return (
56
- <div key={mode}>
91
+ <div key={group.mode}>
57
92
  <Link
58
- href={modeUrl(generateUrl, mode)}
93
+ href={generateUrl({ page: `/help/${group.mode}` })}
59
94
  className="text-muted-foreground hover:text-foreground mb-1 block text-xs font-semibold tracking-wider uppercase"
60
95
  >
61
- {t(`help.modes.${mode}`)}
96
+ {t(`help.modes.${group.mode}`)}
62
97
  </Link>
63
98
  <ul className="space-y-0.5">
64
99
  {group.articles.map((a) => (
65
100
  <li key={a.id}>
66
101
  <Link
67
- href={articleUrl(generateUrl, a)}
102
+ href={generateUrl({ page: `/help/${a.howToType}/${a.slug}` })}
68
103
  className={
69
104
  "hover:bg-muted block rounded px-2 py-1 text-sm " +
70
- (pathname.endsWith(`/help/${a.mode}/${a.slug}`) ? "bg-muted font-medium" : "")
105
+ (isActive(a.howToType ?? "", a.slug ?? "") ? "bg-muted font-medium" : "")
71
106
  }
72
107
  >
73
- {a.title}
108
+ {a.name}
74
109
  </Link>
75
110
  </li>
76
111
  ))}
@@ -17,7 +17,7 @@ vi.mock("../HelpAssistantSheet", () => ({
17
17
  function setupConfig() {
18
18
  configureJsonApi({
19
19
  apiUrl: "http://localhost",
20
- helpContent: { manifest: [], namespaceUuid: "00000000-0000-5000-8000-000000000000" },
20
+ helpContent: { brand: { label: "narr8" } },
21
21
  });
22
22
  }
23
23