@carlonicora/nextjs-jsonapi 1.108.0 → 1.110.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 (144) hide show
  1. package/dist/{AssistantInterface-BYgI5z1-.d.mts → AssistantInterface-B1c8FhGA.d.mts} +2 -0
  2. package/dist/{AssistantInterface-DfDcz0gJ.d.ts → AssistantInterface-BBUHxOCd.d.ts} +2 -0
  3. package/dist/{AssistantMessageInterface-BpEhx2pC.d.ts → AssistantMessageInterface-BNzf1etU.d.ts} +16 -1
  4. package/dist/{AssistantMessageInterface-DJ3Me16Y.d.mts → AssistantMessageInterface-DrgHDjZ5.d.mts} +16 -1
  5. package/dist/{AuthComponent-B6DIk8Vf.d.ts → AuthComponent-Cd7lcYif.d.ts} +1 -1
  6. package/dist/{AuthComponent-BKI0ZbtD.d.mts → AuthComponent-DdxCFgUZ.d.mts} +1 -1
  7. package/dist/{BlockNoteEditor-2AXSTGGG.js → BlockNoteEditor-7OKW6HHN.js} +19 -19
  8. package/dist/{BlockNoteEditor-2AXSTGGG.js.map → BlockNoteEditor-7OKW6HHN.js.map} +1 -1
  9. package/dist/{BlockNoteEditor-XVIBGXHF.mjs → BlockNoteEditor-K7IIWM3S.mjs} +4 -4
  10. package/dist/BlockNoteViewer-OPSCTHKJ.mjs +31 -0
  11. package/dist/BlockNoteViewer-OPSCTHKJ.mjs.map +1 -0
  12. package/dist/BlockNoteViewer-UW5VZ5UF.js +31 -0
  13. package/dist/BlockNoteViewer-UW5VZ5UF.js.map +1 -0
  14. package/dist/{auth.interface-BBUgMZzs.d.ts → auth.interface-8b601idJ.d.ts} +1 -1
  15. package/dist/{auth.interface-XYEREOD6.d.mts → auth.interface-CXBF8Mhi.d.mts} +1 -1
  16. package/dist/billing/index.d.mts +41 -41
  17. package/dist/billing/index.d.ts +41 -41
  18. package/dist/billing/index.js +346 -346
  19. package/dist/billing/index.mjs +3 -3
  20. package/dist/{chunk-VLDLERJN.js → chunk-6XWW67F2.js} +29 -9
  21. package/dist/chunk-6XWW67F2.js.map +1 -0
  22. package/dist/{chunk-HC3JFN3C.js → chunk-AKXUNE3A.js} +1004 -862
  23. package/dist/chunk-AKXUNE3A.js.map +1 -0
  24. package/dist/{chunk-CFECWLHH.mjs → chunk-CWVYU4YZ.mjs} +23 -3
  25. package/dist/chunk-CWVYU4YZ.mjs.map +1 -0
  26. package/dist/{chunk-56XBGQGU.mjs → chunk-TXVVZLUQ.mjs} +1422 -1280
  27. package/dist/chunk-TXVVZLUQ.mjs.map +1 -0
  28. package/dist/{chunk-RXXZGPC3.js → chunk-U2HR57MY.js} +265 -113
  29. package/dist/chunk-U2HR57MY.js.map +1 -0
  30. package/dist/chunk-WPAQRB5Q.mjs +7 -0
  31. package/dist/chunk-WPAQRB5Q.mjs.map +1 -0
  32. package/dist/chunk-YBZVWLPH.js +7 -0
  33. package/dist/chunk-YBZVWLPH.js.map +1 -0
  34. package/dist/{chunk-N3NVIPSU.mjs → chunk-ZYPNVEHB.mjs} +235 -83
  35. package/dist/{chunk-N3NVIPSU.mjs.map → chunk-ZYPNVEHB.mjs.map} +1 -1
  36. package/dist/client/index.d.mts +9 -18
  37. package/dist/client/index.d.ts +9 -18
  38. package/dist/client/index.js +4 -4
  39. package/dist/client/index.mjs +3 -3
  40. package/dist/components/index.d.mts +421 -417
  41. package/dist/components/index.d.ts +421 -417
  42. package/dist/components/index.js +6 -4
  43. package/dist/components/index.js.map +1 -1
  44. package/dist/components/index.mjs +5 -3
  45. package/dist/{config-CLQynoaa.d.ts → config-BrkU_Ynz.d.ts} +4 -1
  46. package/dist/{config-k61pe_o2.d.mts → config-snd2ToEB.d.mts} +4 -1
  47. package/dist/contexts/index.d.mts +31 -20
  48. package/dist/contexts/index.d.ts +31 -20
  49. package/dist/contexts/index.js +4 -4
  50. package/dist/contexts/index.mjs +3 -3
  51. package/dist/core/index.d.mts +75 -11
  52. package/dist/core/index.d.ts +75 -11
  53. package/dist/core/index.js +2 -2
  54. package/dist/core/index.mjs +1 -1
  55. package/dist/features/help/index.css +29 -0
  56. package/dist/features/help/index.css.map +1 -0
  57. package/dist/features/help/index.d.mts +74 -0
  58. package/dist/features/help/index.d.ts +74 -0
  59. package/dist/features/help/index.js +522 -0
  60. package/dist/features/help/index.js.map +1 -0
  61. package/dist/features/help/index.mjs +522 -0
  62. package/dist/features/help/index.mjs.map +1 -0
  63. package/dist/features/help/server.d.mts +33 -0
  64. package/dist/features/help/server.d.ts +33 -0
  65. package/dist/features/help/server.js +8 -0
  66. package/dist/features/help/server.js.map +1 -0
  67. package/dist/features/help/server.mjs +8 -0
  68. package/dist/features/help/server.mjs.map +1 -0
  69. package/dist/help-content-config.interface-C5ESIod9.d.mts +13 -0
  70. package/dist/help-content-config.interface-C5ESIod9.d.ts +13 -0
  71. package/dist/index.d.mts +10 -8
  72. package/dist/index.d.ts +10 -8
  73. package/dist/index.js +3 -3
  74. package/dist/index.mjs +2 -2
  75. package/dist/{notification.interface-aLEJbA_g.d.ts → notification.interface-C1T1C2ee.d.ts} +1 -100
  76. package/dist/{notification.interface-DLZGtV7Z.d.mts → notification.interface-DIxR23eS.d.mts} +1 -100
  77. package/dist/{s3.service-CVgLWaDc.d.mts → s3.service-0BTClOYO.d.mts} +2 -2
  78. package/dist/{s3.service-SLlX0Zbz.d.ts → s3.service-CT27Fm1s.d.ts} +2 -2
  79. package/dist/server/index.d.mts +4 -3
  80. package/dist/server/index.d.ts +4 -3
  81. package/dist/server/index.js +3 -3
  82. package/dist/server/index.mjs +1 -1
  83. package/dist/testing/index.d.mts +1 -2
  84. package/dist/testing/index.d.ts +1 -2
  85. package/dist/types-CQSjy7et.d.mts +101 -0
  86. package/dist/types-DHOxe8rc.d.ts +101 -0
  87. package/dist/usePageUrlGenerator-tjq2mlDV.d.ts +14 -0
  88. package/dist/usePageUrlGenerator-uOnyJ6j2.d.mts +14 -0
  89. package/dist/{useSocket-BkxHHujj.d.mts → useSocket-B1fMIr17.d.mts} +1 -1
  90. package/dist/{useSocket-CMDjWFYm.d.ts → useSocket-BdJTBXKv.d.ts} +1 -1
  91. package/package.json +15 -1
  92. package/src/client/config.ts +9 -1
  93. package/src/components/index.ts +1 -0
  94. package/src/components/viewers/BlockNoteViewer.tsx +27 -0
  95. package/src/components/viewers/BlockNoteViewerContainer.tsx +10 -0
  96. package/src/core/registry/helpStore.ts +28 -0
  97. package/src/features/assistant/contexts/AssistantContext.tsx +35 -19
  98. package/src/features/assistant/contexts/__tests__/AssistantContext.spec.tsx +66 -6
  99. package/src/features/assistant/data/Assistant.ts +2 -0
  100. package/src/features/assistant/data/AssistantInterface.ts +2 -0
  101. package/src/features/assistant/data/AssistantService.ts +18 -8
  102. package/src/features/assistant-message/components/parts/MessageSourcesContainer.tsx +4 -0
  103. package/src/features/assistant-message/components/parts/MessageSourcesPanel.tsx +6 -4
  104. package/src/features/assistant-message/components/parts/tabs/ContentsTab.tsx +5 -1
  105. package/src/features/assistant-message/components/parts/tabs/ReferencesTab.tsx +3 -1
  106. package/src/features/assistant-message/data/AssistantMessage.ts +27 -1
  107. package/src/features/assistant-message/data/AssistantMessageInterface.ts +1 -0
  108. package/src/features/assistant-message/data/__tests__/AssistantMessage.spec.ts +7 -3
  109. package/src/features/help/components/HelpArticleBody.tsx +62 -0
  110. package/src/features/help/components/HelpAskAi.tsx +36 -0
  111. package/src/features/help/components/HelpAssistantSheet.tsx +53 -0
  112. package/src/features/help/components/HelpHeader.tsx +40 -0
  113. package/src/features/help/components/HelpHint.tsx +86 -0
  114. package/src/features/help/components/HelpSearchResultRow.tsx +61 -0
  115. package/src/features/help/components/HelpSideNav.tsx +119 -0
  116. package/src/features/help/components/HelpTOC.tsx +49 -0
  117. package/src/features/help/components/__tests__/HelpAskAi.spec.tsx +68 -0
  118. package/src/features/help/components/__tests__/HelpAssistantSheet.spec.tsx +36 -0
  119. package/src/features/help/components/__tests__/HelpHint.spec.tsx +50 -0
  120. package/src/features/help/components/__tests__/HelpSearchResultRow.spec.tsx +58 -0
  121. package/src/features/help/components/__tests__/HelpSideNav.spec.tsx +50 -0
  122. package/src/features/help/contexts/HelpContext.spec.tsx +26 -0
  123. package/src/features/help/contexts/HelpContext.tsx +24 -0
  124. package/src/features/help/hooks/useHelp.ts +1 -0
  125. package/src/features/help/hooks/useHelpFilter.ts +27 -0
  126. package/src/features/help/i18n-keys.ts +34 -0
  127. package/src/features/help/index.ts +24 -0
  128. package/src/features/help/interfaces/help-content-config.interface.ts +12 -0
  129. package/src/features/help/server-entry.ts +11 -0
  130. package/src/features/help/types/help-article.types.ts +35 -0
  131. package/src/features/help/utils/articleUrl.ts +13 -0
  132. package/src/features/how-to/HowToModule.ts +14 -1
  133. package/src/features/how-to/components/forms/HowToEditor.tsx +108 -4
  134. package/src/features/how-to/data/HowTo.spec.ts +43 -0
  135. package/src/features/how-to/data/HowTo.ts +64 -3
  136. package/src/features/how-to/data/HowToInterface.ts +14 -0
  137. package/src/features/how-to/data/HowToService.ts +61 -0
  138. package/src/index.ts +4 -0
  139. package/dist/chunk-56XBGQGU.mjs.map +0 -1
  140. package/dist/chunk-CFECWLHH.mjs.map +0 -1
  141. package/dist/chunk-HC3JFN3C.js.map +0 -1
  142. package/dist/chunk-RXXZGPC3.js.map +0 -1
  143. package/dist/chunk-VLDLERJN.js.map +0 -1
  144. /package/dist/{BlockNoteEditor-XVIBGXHF.mjs.map → BlockNoteEditor-K7IIWM3S.mjs.map} +0 -0
@@ -1,4 +1,3 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
1
  import React__default, { ReactElement } from 'react';
3
2
  import { J as JsonApiConfig } from '../JsonApiContext-Bsm_Q2oe.js';
4
3
  import { A as ApiRequestDataTypeInterface } from '../ApiRequestDataTypeInterface-CYEcRUrh.js';
@@ -37,7 +36,7 @@ declare const defaultMockConfig: JsonApiConfig;
37
36
  * );
38
37
  * ```
39
38
  */
40
- declare function MockJsonApiProvider({ children, config }: MockJsonApiProviderProps): react_jsx_runtime.JSX.Element;
39
+ declare function MockJsonApiProvider({ children, config }: MockJsonApiProviderProps): React__default.JSX.Element;
41
40
 
42
41
  interface CreateMockModuleOptions {
43
42
  name: string;
@@ -0,0 +1,101 @@
1
+ import { LucideIcon } from 'lucide-react';
2
+ import { A as ApiRequestDataTypeInterface, F as FieldSelector } from './ApiRequestDataTypeInterface-CYEcRUrh.mjs';
3
+
4
+ /**
5
+ * Permission actions
6
+ */
7
+ declare enum Action {
8
+ Read = "read",
9
+ Create = "create",
10
+ Update = "update",
11
+ Delete = "delete"
12
+ }
13
+ /**
14
+ * Generic permission check type.
15
+ * Can be a boolean or a function that checks permissions dynamically.
16
+ * @template T - The data type being checked
17
+ * @template U - The user type (defaults to PermissionUser)
18
+ */
19
+ type PermissionCheck<T, U = PermissionUser> = boolean | ((user?: U | string, data?: T) => boolean);
20
+ /**
21
+ * Page URL configuration for modules
22
+ */
23
+ type PageUrl = {
24
+ pageUrl?: string;
25
+ };
26
+ /**
27
+ * Module permission definition wrapper
28
+ */
29
+ type ModulePermissionDefinition<T> = {
30
+ interface: T;
31
+ };
32
+ /**
33
+ * Base module definition
34
+ */
35
+ type ModuleDefinition = {
36
+ pageUrl?: string;
37
+ name: string;
38
+ model: any;
39
+ feature?: string;
40
+ moduleId?: string;
41
+ };
42
+ /**
43
+ * Permission configuration for a module.
44
+ * Can be a boolean (allow/deny all) or a string path for dynamic checks.
45
+ */
46
+ interface PermissionConfig {
47
+ create: boolean | string;
48
+ read: boolean | string;
49
+ update: boolean | string;
50
+ delete: boolean | string;
51
+ }
52
+ /**
53
+ * Generic interface for a module that has permissions.
54
+ * Apps should ensure their Module class implements this.
55
+ */
56
+ interface PermissionModule {
57
+ id: string;
58
+ permissions: PermissionConfig;
59
+ }
60
+ /**
61
+ * Generic interface for a user that has modules with permissions.
62
+ * Apps should ensure their User class implements this.
63
+ */
64
+ interface PermissionUser {
65
+ id: string;
66
+ modules: PermissionModule[];
67
+ }
68
+ /**
69
+ * Module definition with permissions - extends ApiRequestDataTypeInterface
70
+ */
71
+ type ModuleWithPermissions = ApiRequestDataTypeInterface & {
72
+ pageUrl?: string;
73
+ feature?: string;
74
+ moduleId?: string;
75
+ icon?: LucideIcon;
76
+ inclusions?: Record<string, {
77
+ types?: string[];
78
+ fields?: FieldSelector<any>[];
79
+ include?: string[];
80
+ }>;
81
+ };
82
+ /**
83
+ * Factory type for creating module definitions
84
+ */
85
+ type ModuleFactory = (params: {
86
+ pageUrl?: string;
87
+ name: string;
88
+ cache?: string | "days" | "default" | "hours" | "max" | "minutes" | "seconds" | "weeks";
89
+ model: any;
90
+ feature?: string;
91
+ moduleId?: string;
92
+ icon?: LucideIcon;
93
+ identifier?: string[];
94
+ inclusions?: Record<string, {
95
+ types?: string[];
96
+ fields?: FieldSelector<any>[];
97
+ include?: string[];
98
+ }>;
99
+ }) => ModuleWithPermissions;
100
+
101
+ export { Action as A, type ModuleWithPermissions as M, type PageUrl as P, type PermissionCheck as a, type ModulePermissionDefinition as b, type ModuleDefinition as c, type PermissionConfig as d, type PermissionModule as e, type PermissionUser as f, type ModuleFactory as g };
@@ -0,0 +1,101 @@
1
+ import { LucideIcon } from 'lucide-react';
2
+ import { A as ApiRequestDataTypeInterface, F as FieldSelector } from './ApiRequestDataTypeInterface-CYEcRUrh.js';
3
+
4
+ /**
5
+ * Permission actions
6
+ */
7
+ declare enum Action {
8
+ Read = "read",
9
+ Create = "create",
10
+ Update = "update",
11
+ Delete = "delete"
12
+ }
13
+ /**
14
+ * Generic permission check type.
15
+ * Can be a boolean or a function that checks permissions dynamically.
16
+ * @template T - The data type being checked
17
+ * @template U - The user type (defaults to PermissionUser)
18
+ */
19
+ type PermissionCheck<T, U = PermissionUser> = boolean | ((user?: U | string, data?: T) => boolean);
20
+ /**
21
+ * Page URL configuration for modules
22
+ */
23
+ type PageUrl = {
24
+ pageUrl?: string;
25
+ };
26
+ /**
27
+ * Module permission definition wrapper
28
+ */
29
+ type ModulePermissionDefinition<T> = {
30
+ interface: T;
31
+ };
32
+ /**
33
+ * Base module definition
34
+ */
35
+ type ModuleDefinition = {
36
+ pageUrl?: string;
37
+ name: string;
38
+ model: any;
39
+ feature?: string;
40
+ moduleId?: string;
41
+ };
42
+ /**
43
+ * Permission configuration for a module.
44
+ * Can be a boolean (allow/deny all) or a string path for dynamic checks.
45
+ */
46
+ interface PermissionConfig {
47
+ create: boolean | string;
48
+ read: boolean | string;
49
+ update: boolean | string;
50
+ delete: boolean | string;
51
+ }
52
+ /**
53
+ * Generic interface for a module that has permissions.
54
+ * Apps should ensure their Module class implements this.
55
+ */
56
+ interface PermissionModule {
57
+ id: string;
58
+ permissions: PermissionConfig;
59
+ }
60
+ /**
61
+ * Generic interface for a user that has modules with permissions.
62
+ * Apps should ensure their User class implements this.
63
+ */
64
+ interface PermissionUser {
65
+ id: string;
66
+ modules: PermissionModule[];
67
+ }
68
+ /**
69
+ * Module definition with permissions - extends ApiRequestDataTypeInterface
70
+ */
71
+ type ModuleWithPermissions = ApiRequestDataTypeInterface & {
72
+ pageUrl?: string;
73
+ feature?: string;
74
+ moduleId?: string;
75
+ icon?: LucideIcon;
76
+ inclusions?: Record<string, {
77
+ types?: string[];
78
+ fields?: FieldSelector<any>[];
79
+ include?: string[];
80
+ }>;
81
+ };
82
+ /**
83
+ * Factory type for creating module definitions
84
+ */
85
+ type ModuleFactory = (params: {
86
+ pageUrl?: string;
87
+ name: string;
88
+ cache?: string | "days" | "default" | "hours" | "max" | "minutes" | "seconds" | "weeks";
89
+ model: any;
90
+ feature?: string;
91
+ moduleId?: string;
92
+ icon?: LucideIcon;
93
+ identifier?: string[];
94
+ inclusions?: Record<string, {
95
+ types?: string[];
96
+ fields?: FieldSelector<any>[];
97
+ include?: string[];
98
+ }>;
99
+ }) => ModuleWithPermissions;
100
+
101
+ export { Action as A, type ModuleWithPermissions as M, type PageUrl as P, type PermissionCheck as a, type ModulePermissionDefinition as b, type ModuleDefinition as c, type PermissionConfig as d, type PermissionModule as e, type PermissionUser as f, type ModuleFactory as g };
@@ -0,0 +1,14 @@
1
+ import { P as PageUrl } from './types-DHOxe8rc.js';
2
+
3
+ declare function usePageUrlGenerator(): (params: {
4
+ page?: PageUrl | string;
5
+ id?: string;
6
+ childPage?: PageUrl | string;
7
+ childId?: string;
8
+ additionalParameters?: {
9
+ [key: string]: string | string[] | undefined;
10
+ };
11
+ language?: string;
12
+ }) => string;
13
+
14
+ export { usePageUrlGenerator as u };
@@ -0,0 +1,14 @@
1
+ import { P as PageUrl } from './types-CQSjy7et.mjs';
2
+
3
+ declare function usePageUrlGenerator(): (params: {
4
+ page?: PageUrl | string;
5
+ id?: string;
6
+ childPage?: PageUrl | string;
7
+ childId?: string;
8
+ additionalParameters?: {
9
+ [key: string]: string | string[] | undefined;
10
+ };
11
+ language?: string;
12
+ }) => string;
13
+
14
+ export { usePageUrlGenerator as u };
@@ -1,4 +1,4 @@
1
- import { N as NotificationInterface } from './notification.interface-DLZGtV7Z.mjs';
1
+ import { N as NotificationInterface } from './notification.interface-DIxR23eS.mjs';
2
2
 
3
3
  interface UseSocketOptions {
4
4
  token: string;
@@ -1,4 +1,4 @@
1
- import { N as NotificationInterface } from './notification.interface-aLEJbA_g.js';
1
+ import { N as NotificationInterface } from './notification.interface-C1T1C2ee.js';
2
2
 
3
3
  interface UseSocketOptions {
4
4
  token: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carlonicora/nextjs-jsonapi",
3
- "version": "1.108.0",
3
+ "version": "1.110.0",
4
4
  "description": "Next.js JSON:API client with server/client support and caching",
5
5
  "author": "Carlo Nicora",
6
6
  "license": "GPL-3.0-or-later",
@@ -52,6 +52,16 @@
52
52
  "types": "./dist/billing/index.d.ts",
53
53
  "import": "./dist/billing/index.mjs",
54
54
  "require": "./dist/billing/index.js"
55
+ },
56
+ "./help": {
57
+ "types": "./dist/features/help/index.d.ts",
58
+ "import": "./dist/features/help/index.mjs",
59
+ "require": "./dist/features/help/index.js"
60
+ },
61
+ "./help/server": {
62
+ "types": "./dist/features/help/server.d.ts",
63
+ "import": "./dist/features/help/server.mjs",
64
+ "require": "./dist/features/help/server.js"
55
65
  }
56
66
  },
57
67
  "scripts": {
@@ -139,6 +149,10 @@
139
149
  "react-markdown": "^10.1.0",
140
150
  "react-resizable-panels": "^4.11.1",
141
151
  "recharts": "^3.8.1",
152
+ "fuse.js": "^7.0.0",
153
+ "next-mdx-remote": "^5.0.0",
154
+ "rehype-autolink-headings": "^7.1.0",
155
+ "rehype-slug": "^6.0.0",
142
156
  "remark-gfm": "^4.0.1",
143
157
  "shadcn": "^4.8.0",
144
158
  "shepherd.js": "^15.2.2",
@@ -2,6 +2,8 @@
2
2
 
3
3
  import { ModuleWithPermissions } from "../permissions/types";
4
4
  import { setBootstrapper } from "../core/registry/bootstrapStore";
5
+ import { _setStaticHelpContent } from "../core/registry/helpStore";
6
+ import type { HelpContentConfig } from "../features/help/interfaces/help-content-config.interface";
5
7
 
6
8
  // Config storage for client-side contexts
7
9
  let _clientConfig: {
@@ -16,6 +18,7 @@ let _clientConfig: {
16
18
  /**
17
19
  * Configure the JSON:API client. This is the main configuration function.
18
20
  * This is typically called during app initialization.
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.
19
22
  */
20
23
  export function configureJsonApi(config: {
21
24
  apiUrl: string;
@@ -24,8 +27,13 @@ export function configureJsonApi(config: {
24
27
  bootstrapper?: () => void;
25
28
  additionalHeaders?: Record<string, string>;
26
29
  stripePublishableKey?: string;
30
+ helpContent?: HelpContentConfig;
27
31
  }): void {
28
- _clientConfig = config;
32
+ const { helpContent, ...rest } = config;
33
+ _clientConfig = rest;
34
+ if (helpContent) {
35
+ _setStaticHelpContent(helpContent);
36
+ }
29
37
  // Register and call bootstrapper to register all modules
30
38
  if (config.bootstrapper) {
31
39
  setBootstrapper(config.bootstrapper);
@@ -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
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Centralized help-content store accessible from client and server contexts.
3
+ * Uses a globalThis Symbol key to persist across HMR/Turbopack reloads and to
4
+ * bridge between the client-side `configureJsonApi` (in `client/config.ts`)
5
+ * and the HelpProvider context. Holds the brand-only HelpContentConfig.
6
+ *
7
+ * NO external dependencies to avoid circular imports.
8
+ *
9
+ * Pattern mirrors `bootstrapStore.ts`.
10
+ */
11
+
12
+ const HELP_CONTENT_KEY = Symbol.for("nextjs-jsonapi:helpContent");
13
+
14
+ const globalStore = globalThis as unknown as {
15
+ [HELP_CONTENT_KEY]?: unknown | null;
16
+ };
17
+
18
+ if (globalStore[HELP_CONTENT_KEY] === undefined) {
19
+ globalStore[HELP_CONTENT_KEY] = null;
20
+ }
21
+
22
+ export function _setStaticHelpContent(cfg: unknown | null): void {
23
+ globalStore[HELP_CONTENT_KEY] = cfg;
24
+ }
25
+
26
+ export function _getStaticHelpContent<T = unknown>(): T | null {
27
+ return (globalStore[HELP_CONTENT_KEY] as T | null) ?? null;
28
+ }
@@ -20,7 +20,7 @@ interface AssistantContextValue {
20
20
  sending: boolean;
21
21
  status?: string;
22
22
  failedMessageIds: Set<string>;
23
- sendMessage(content: string): Promise<void>;
23
+ sendMessage(content: string, opts?: { howToMode?: boolean; limitToHowToId?: string }): Promise<void>;
24
24
  retrySend(tempId: string): Promise<void>;
25
25
  selectThread(id: string): Promise<void>;
26
26
  startNew(): void;
@@ -34,10 +34,17 @@ interface Props {
34
34
  children: React.ReactNode;
35
35
  dehydratedAssistant?: JsonApiHydratedDataInterface;
36
36
  dehydratedMessages?: JsonApiHydratedDataInterface[];
37
+ /**
38
+ * When `true` (default), the provider mutates the browser URL on
39
+ * create/selectThread/startNew (e.g. `/assistants/{id}`). Set to `false`
40
+ * when the assistant is hosted inside a sheet / overlay so the user's
41
+ * current route is preserved.
42
+ */
43
+ manageUrl?: boolean;
37
44
  }
38
45
 
39
46
  function stripOptimistic(list: AssistantMessageInterface[]): AssistantMessageInterface[] {
40
- return list.filter((m) => !m.id.startsWith("tmp-"));
47
+ return list.filter((m) => !m.isOptimistic);
41
48
  }
42
49
 
43
50
  function nextPosition(list: AssistantMessageInterface[]): number {
@@ -55,7 +62,7 @@ function withPatchedTitle(source: AssistantInterface, title: string): AssistantI
55
62
  });
56
63
  }
57
64
 
58
- export function AssistantProvider({ children, dehydratedAssistant, dehydratedMessages }: Props) {
65
+ export function AssistantProvider({ children, dehydratedAssistant, dehydratedMessages, manageUrl = true }: Props) {
59
66
  const t = useTranslations();
60
67
  const generateUrl = usePageUrlGenerator();
61
68
 
@@ -73,7 +80,7 @@ export function AssistantProvider({ children, dehydratedAssistant, dehydratedMes
73
80
  const { socket } = useSocketContext();
74
81
 
75
82
  const sendMessage = useCallback(
76
- async (content: string) => {
83
+ async (content: string, opts?: { howToMode?: boolean; limitToHowToId?: string }) => {
77
84
  const trimmed = content.trim();
78
85
  if (!trimmed) return;
79
86
 
@@ -94,18 +101,24 @@ export function AssistantProvider({ children, dehydratedAssistant, dehydratedMes
94
101
 
95
102
  try {
96
103
  if (!assistant) {
97
- const created = await AssistantService.create({ firstMessage: trimmed });
104
+ const created = await AssistantService.create({
105
+ firstMessage: trimmed,
106
+ howToMode: opts?.howToMode,
107
+ limitToHowToId: opts?.limitToHowToId,
108
+ });
98
109
  const msgs = await AssistantMessageService.findByAssistant({ assistantId: created.id });
99
110
  setAssistant(created);
100
111
  setMessages(msgs);
101
112
  setThreads((prev) => [created, ...prev]);
102
- if (typeof window !== "undefined") {
113
+ if (manageUrl && typeof window !== "undefined") {
103
114
  window.history.replaceState(null, "", `/assistants/${created.id}`);
104
115
  }
105
116
  } else {
106
117
  const result = await AssistantService.appendMessage({
107
118
  assistantId: assistant.id,
108
119
  content: trimmed,
120
+ howToMode: opts?.howToMode,
121
+ limitToHowToId: opts?.limitToHowToId,
109
122
  });
110
123
  setMessages((prev) => [...stripOptimistic(prev), ...result]);
111
124
  }
@@ -142,17 +155,20 @@ export function AssistantProvider({ children, dehydratedAssistant, dehydratedMes
142
155
  [messages, sendMessage],
143
156
  );
144
157
 
145
- const selectThread = useCallback(async (id: string) => {
146
- const [target, msgs] = await Promise.all([
147
- AssistantService.findOne({ id }),
148
- AssistantMessageService.findByAssistant({ assistantId: id }),
149
- ]);
150
- setAssistant(target);
151
- setMessages(msgs);
152
- if (typeof window !== "undefined") {
153
- window.history.replaceState(null, "", `/assistants/${id}`);
154
- }
155
- }, []);
158
+ const selectThread = useCallback(
159
+ async (id: string) => {
160
+ const [target, msgs] = await Promise.all([
161
+ AssistantService.findOne({ id }),
162
+ AssistantMessageService.findByAssistant({ assistantId: id }),
163
+ ]);
164
+ setAssistant(target);
165
+ setMessages(msgs);
166
+ if (manageUrl && typeof window !== "undefined") {
167
+ window.history.replaceState(null, "", `/assistants/${id}`);
168
+ }
169
+ },
170
+ [manageUrl],
171
+ );
156
172
 
157
173
  const renameThread = useCallback(async (id: string, title: string) => {
158
174
  await AssistantService.rename({ id, title });
@@ -164,10 +180,10 @@ export function AssistantProvider({ children, dehydratedAssistant, dehydratedMes
164
180
  setAssistant(undefined);
165
181
  setMessages([]);
166
182
  setFailedMessageIds(new Set());
167
- if (typeof window !== "undefined") {
183
+ if (manageUrl && typeof window !== "undefined") {
168
184
  window.history.replaceState(null, "", "/assistants");
169
185
  }
170
- }, []);
186
+ }, [manageUrl]);
171
187
 
172
188
  const deleteThread = useCallback(async (id: string) => {
173
189
  await AssistantService.delete({ id });
@@ -264,7 +264,7 @@ describe("AssistantContext", () => {
264
264
 
265
265
  // Before the server responds, the optimistic user bubble must be visible.
266
266
  expect(result.current.messages.map((m) => m.content)).toContain("follow-up");
267
- expect(result.current.messages.some((m) => m.id.startsWith("tmp-") && m.role === "user")).toBe(true);
267
+ expect(result.current.messages.some((m) => m.isOptimistic && m.role === "user")).toBe(true);
268
268
  expect(result.current.sending).toBe(true);
269
269
 
270
270
  await act(async () => {
@@ -276,7 +276,7 @@ describe("AssistantContext", () => {
276
276
  });
277
277
 
278
278
  // After reconciliation, no tmp-* remains, and server messages are appended.
279
- expect(result.current.messages.some((m) => m.id.startsWith("tmp-"))).toBe(false);
279
+ expect(result.current.messages.some((m) => m.isOptimistic)).toBe(false);
280
280
  expect(result.current.messages.map((m) => m.content)).toEqual(["follow-up", "reply"]);
281
281
  expect(result.current.sending).toBe(false);
282
282
  });
@@ -307,7 +307,7 @@ describe("AssistantContext", () => {
307
307
 
308
308
  // Before the server responds: thread has exactly the optimistic user bubble.
309
309
  expect(result.current.messages).toHaveLength(1);
310
- expect(result.current.messages[0].id.startsWith("tmp-")).toBe(true);
310
+ expect(result.current.messages[0].isOptimistic).toBe(true);
311
311
  expect(result.current.messages[0].content).toBe("first question");
312
312
  expect(result.current.assistant).toBeUndefined();
313
313
  expect(result.current.sending).toBe(true);
@@ -319,11 +319,71 @@ describe("AssistantContext", () => {
319
319
 
320
320
  // After reconciliation: assistant set, URL replaced, server messages only.
321
321
  expect(result.current.assistant?.id).toBe("a-1");
322
- expect(result.current.messages.some((m) => m.id.startsWith("tmp-"))).toBe(false);
322
+ expect(result.current.messages.some((m) => m.isOptimistic)).toBe(false);
323
323
  expect(result.current.messages).toHaveLength(2);
324
324
  expect(replaceState).toHaveBeenCalledWith(null, "", "/assistants/a-1");
325
325
  });
326
326
 
327
+ it("forwards howToMode to AssistantService.create on first send", async () => {
328
+ const replaceState = vi.spyOn(window.history, "replaceState").mockImplementation(() => {});
329
+ const created = buildAssistantStub({ id: "a-1" });
330
+ AssistantService.create = vi.fn().mockResolvedValue(created);
331
+ AssistantMessageService.findByAssistant = vi.fn().mockResolvedValue([]);
332
+
333
+ const { result } = renderHook(() => useAssistantContext(), {
334
+ wrapper: ({ children }) => <AssistantProvider>{children}</AssistantProvider>,
335
+ });
336
+ await act(async () => {
337
+ await result.current.sendMessage("hi", { howToMode: true });
338
+ });
339
+
340
+ expect(AssistantService.create).toHaveBeenCalledWith(
341
+ expect.objectContaining({ firstMessage: "hi", howToMode: true }),
342
+ );
343
+ replaceState.mockRestore();
344
+ });
345
+
346
+ it("forwards howToMode to AssistantService.appendMessage on follow-up send", async () => {
347
+ const existing = buildAssistantDehydrated({ id: "a-2", title: "Existing" });
348
+ AssistantService.appendMessage = vi.fn().mockResolvedValue([]);
349
+
350
+ const { result } = renderHook(() => useAssistantContext(), {
351
+ wrapper: ({ children }) => <AssistantProvider dehydratedAssistant={existing}>{children}</AssistantProvider>,
352
+ });
353
+ await act(async () => {
354
+ await result.current.sendMessage("follow up", { howToMode: true, limitToHowToId: "ht-1" });
355
+ });
356
+
357
+ expect(AssistantService.appendMessage).toHaveBeenCalledWith(
358
+ expect.objectContaining({
359
+ assistantId: "a-2",
360
+ content: "follow up",
361
+ howToMode: true,
362
+ limitToHowToId: "ht-1",
363
+ }),
364
+ );
365
+ });
366
+
367
+ it("calls service without opts when called with content only (regression)", async () => {
368
+ const replaceState = vi.spyOn(window.history, "replaceState").mockImplementation(() => {});
369
+ const created = buildAssistantStub({ id: "a-1" });
370
+ AssistantService.create = vi.fn().mockResolvedValue(created);
371
+ AssistantMessageService.findByAssistant = vi.fn().mockResolvedValue([]);
372
+
373
+ const { result } = renderHook(() => useAssistantContext(), {
374
+ wrapper: ({ children }) => <AssistantProvider>{children}</AssistantProvider>,
375
+ });
376
+ await act(async () => {
377
+ await result.current.sendMessage("hi");
378
+ });
379
+
380
+ expect(AssistantService.create).toHaveBeenCalledWith(expect.objectContaining({ firstMessage: "hi" }));
381
+ const call = (AssistantService.create as ReturnType<typeof vi.fn>).mock.calls[0][0];
382
+ expect(call.howToMode).toBeUndefined();
383
+ expect(call.limitToHowToId).toBeUndefined();
384
+ replaceState.mockRestore();
385
+ });
386
+
327
387
  it("sendMessage failure: optimistic message stays and its id lands in failedMessageIds", async () => {
328
388
  const existing = buildAssistantDehydrated({ id: "a-x", title: "Ex" });
329
389
  AssistantService.appendMessage = vi.fn().mockRejectedValue(new Error("boom"));
@@ -335,7 +395,7 @@ describe("AssistantContext", () => {
335
395
  await result.current.sendMessage("oops").catch(() => {});
336
396
  });
337
397
 
338
- const optimistic = result.current.messages.find((m) => m.id.startsWith("tmp-"));
398
+ const optimistic = result.current.messages.find((m) => m.isOptimistic);
339
399
  expect(optimistic).toBeDefined();
340
400
  expect(optimistic!.content).toBe("oops");
341
401
  expect(result.current.failedMessageIds.has(optimistic!.id)).toBe(true);
@@ -368,7 +428,7 @@ describe("AssistantContext", () => {
368
428
  });
369
429
 
370
430
  expect(result.current.failedMessageIds.has(failedId!)).toBe(false);
371
- expect(result.current.messages.some((m) => m.id.startsWith("tmp-"))).toBe(false);
431
+ expect(result.current.messages.some((m) => m.isOptimistic)).toBe(false);
372
432
  expect(result.current.messages.map((m) => m.content)).toEqual(["retry-me", "ok"]);
373
433
  expect(appendMock).toHaveBeenCalledTimes(2);
374
434
  });
@@ -29,6 +29,8 @@ export class Assistant extends AbstractApiData implements AssistantInterface {
29
29
  attributes: {
30
30
  content: data.firstMessage,
31
31
  ...(data.title !== undefined ? { title: data.title } : {}),
32
+ ...(data.howToMode !== undefined ? { howToMode: data.howToMode } : {}),
33
+ ...(data.limitToHowToId !== undefined ? { limitToHowToId: data.limitToHowToId } : {}),
32
34
  },
33
35
  },
34
36
  included: [],
@@ -3,6 +3,8 @@ import { ApiDataInterface } from "../../../core";
3
3
  export type AssistantInput = {
4
4
  firstMessage: string;
5
5
  title?: string;
6
+ howToMode?: boolean;
7
+ limitToHowToId?: string;
6
8
  };
7
9
 
8
10
  export interface AssistantInterface extends ApiDataInterface {