@btst/stack 2.3.0 → 2.5.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 (208) hide show
  1. package/dist/packages/stack/src/client/components/compose.cjs +1 -2
  2. package/dist/packages/stack/src/client/components/compose.mjs +1 -2
  3. package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.cjs +71 -0
  4. package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.mjs +68 -0
  5. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +87 -54
  6. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +87 -54
  7. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +2 -2
  8. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +2 -2
  9. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +89 -22
  10. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +90 -23
  11. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +110 -33
  12. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +112 -35
  13. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.cjs +1 -1
  14. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.mjs +1 -1
  15. package/dist/packages/stack/src/plugins/ai-chat/client/plugin.cjs +14 -21
  16. package/dist/packages/stack/src/plugins/ai-chat/client/plugin.mjs +15 -22
  17. package/dist/packages/stack/src/plugins/ai-chat/schemas.cjs +17 -1
  18. package/dist/packages/stack/src/plugins/ai-chat/schemas.mjs +17 -1
  19. package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +28 -45
  20. package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +22 -39
  21. package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.cjs +15 -2
  22. package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.mjs +16 -3
  23. package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.cjs +24 -1
  24. package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.mjs +24 -1
  25. package/dist/packages/stack/src/plugins/blog/client/components/pages/fill-blog-form-handler.cjs +26 -0
  26. package/dist/packages/stack/src/plugins/blog/client/components/pages/fill-blog-form-handler.mjs +24 -0
  27. package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.cjs +30 -1
  28. package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.mjs +30 -1
  29. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +18 -0
  30. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +18 -0
  31. package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +23 -27
  32. package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +24 -28
  33. package/dist/packages/stack/src/plugins/cms/api/mutations.cjs +48 -0
  34. package/dist/packages/stack/src/plugins/cms/api/mutations.mjs +46 -0
  35. package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +21 -18
  36. package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +21 -18
  37. package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +11 -15
  38. package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +12 -16
  39. package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +58 -62
  40. package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +58 -62
  41. package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +12 -12
  42. package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +13 -13
  43. package/dist/packages/stack/src/plugins/kanban/api/mutations.cjs +91 -0
  44. package/dist/packages/stack/src/plugins/kanban/api/mutations.mjs +87 -0
  45. package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +92 -118
  46. package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +89 -115
  47. package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.cjs +7 -3
  48. package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.mjs +7 -3
  49. package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +22 -29
  50. package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +23 -30
  51. package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.cjs +89 -0
  52. package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.mjs +89 -0
  53. package/dist/packages/stack/src/plugins/ui-builder/client/plugin.cjs +8 -8
  54. package/dist/packages/stack/src/plugins/ui-builder/client/plugin.mjs +9 -9
  55. package/dist/packages/stack/src/plugins/utils.cjs +42 -0
  56. package/dist/packages/stack/src/plugins/utils.mjs +41 -1
  57. package/dist/plugins/ai-chat/api/index.d.cts +1 -1
  58. package/dist/plugins/ai-chat/api/index.d.mts +1 -1
  59. package/dist/plugins/ai-chat/api/index.d.ts +1 -1
  60. package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
  61. package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
  62. package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
  63. package/dist/plugins/ai-chat/client/context/page-ai-context.cjs +92 -0
  64. package/dist/plugins/ai-chat/client/context/page-ai-context.d.cts +84 -0
  65. package/dist/plugins/ai-chat/client/context/page-ai-context.d.mts +84 -0
  66. package/dist/plugins/ai-chat/client/context/page-ai-context.d.ts +84 -0
  67. package/dist/plugins/ai-chat/client/context/page-ai-context.mjs +88 -0
  68. package/dist/plugins/ai-chat/client/hooks/index.d.cts +1 -1
  69. package/dist/plugins/ai-chat/client/hooks/index.d.mts +1 -1
  70. package/dist/plugins/ai-chat/client/hooks/index.d.ts +1 -1
  71. package/dist/plugins/ai-chat/client/index.d.cts +10 -10
  72. package/dist/plugins/ai-chat/client/index.d.mts +10 -10
  73. package/dist/plugins/ai-chat/client/index.d.ts +10 -10
  74. package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
  75. package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
  76. package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
  77. package/dist/plugins/blog/api/index.d.cts +2 -2
  78. package/dist/plugins/blog/api/index.d.mts +2 -2
  79. package/dist/plugins/blog/api/index.d.ts +2 -2
  80. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  81. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  82. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  83. package/dist/plugins/blog/client/index.d.cts +13 -13
  84. package/dist/plugins/blog/client/index.d.mts +13 -13
  85. package/dist/plugins/blog/client/index.d.ts +13 -13
  86. package/dist/plugins/blog/query-keys.d.cts +2 -2
  87. package/dist/plugins/blog/query-keys.d.mts +2 -2
  88. package/dist/plugins/blog/query-keys.d.ts +2 -2
  89. package/dist/plugins/client/index.cjs +1 -0
  90. package/dist/plugins/client/index.d.cts +8 -1
  91. package/dist/plugins/client/index.d.mts +8 -1
  92. package/dist/plugins/client/index.d.ts +8 -1
  93. package/dist/plugins/client/index.mjs +1 -1
  94. package/dist/plugins/cms/api/index.cjs +2 -0
  95. package/dist/plugins/cms/api/index.d.cts +2 -2
  96. package/dist/plugins/cms/api/index.d.mts +2 -2
  97. package/dist/plugins/cms/api/index.d.ts +2 -2
  98. package/dist/plugins/cms/api/index.mjs +1 -0
  99. package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
  100. package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
  101. package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
  102. package/dist/plugins/cms/client/index.d.cts +6 -6
  103. package/dist/plugins/cms/client/index.d.mts +6 -6
  104. package/dist/plugins/cms/client/index.d.ts +6 -6
  105. package/dist/plugins/cms/query-keys.d.cts +2 -2
  106. package/dist/plugins/cms/query-keys.d.mts +2 -2
  107. package/dist/plugins/cms/query-keys.d.ts +2 -2
  108. package/dist/plugins/form-builder/api/index.d.cts +2 -2
  109. package/dist/plugins/form-builder/api/index.d.mts +2 -2
  110. package/dist/plugins/form-builder/api/index.d.ts +2 -2
  111. package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
  112. package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
  113. package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
  114. package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
  115. package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
  116. package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
  117. package/dist/plugins/form-builder/client/index.d.cts +6 -6
  118. package/dist/plugins/form-builder/client/index.d.mts +6 -6
  119. package/dist/plugins/form-builder/client/index.d.ts +6 -6
  120. package/dist/plugins/form-builder/query-keys.d.cts +2 -2
  121. package/dist/plugins/form-builder/query-keys.d.mts +2 -2
  122. package/dist/plugins/form-builder/query-keys.d.ts +2 -2
  123. package/dist/plugins/kanban/api/index.cjs +4 -0
  124. package/dist/plugins/kanban/api/index.d.cts +1 -1
  125. package/dist/plugins/kanban/api/index.d.mts +1 -1
  126. package/dist/plugins/kanban/api/index.d.ts +1 -1
  127. package/dist/plugins/kanban/api/index.mjs +1 -0
  128. package/dist/plugins/kanban/client/index.d.cts +12 -12
  129. package/dist/plugins/kanban/client/index.d.mts +12 -12
  130. package/dist/plugins/kanban/client/index.d.ts +12 -12
  131. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  132. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  133. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  134. package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
  135. package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
  136. package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
  137. package/dist/plugins/ui-builder/client/index.d.cts +3 -3
  138. package/dist/plugins/ui-builder/client/index.d.mts +3 -3
  139. package/dist/plugins/ui-builder/client/index.d.ts +3 -3
  140. package/dist/plugins/ui-builder/index.d.cts +2 -2
  141. package/dist/plugins/ui-builder/index.d.mts +2 -2
  142. package/dist/plugins/ui-builder/index.d.ts +2 -2
  143. package/dist/shared/{stack.C-WUPMT6.d.cts → stack.B2xZTSiO.d.cts} +4 -4
  144. package/dist/shared/{stack.B1EeBt1b.d.ts → stack.B58oHdqm.d.mts} +33 -3
  145. package/dist/shared/{stack.CVDTkMoO.d.mts → stack.B8QD11QU.d.cts} +7 -7
  146. package/dist/shared/{stack.CVDTkMoO.d.cts → stack.B8QD11QU.d.mts} +7 -7
  147. package/dist/shared/{stack.CVDTkMoO.d.ts → stack.B8QD11QU.d.ts} +7 -7
  148. package/dist/shared/{stack.CIP6QS9l.d.ts → stack.BDVEpue1.d.ts} +1 -1
  149. package/dist/shared/{stack.C5dtIncc.d.mts → stack.BTvbxZvw.d.cts} +1 -1
  150. package/dist/shared/{stack.DaOcgmrM.d.ts → stack.BV9hnvu4.d.cts} +31 -7
  151. package/dist/shared/{stack.DaOcgmrM.d.cts → stack.BV9hnvu4.d.mts} +31 -7
  152. package/dist/shared/{stack.DaOcgmrM.d.mts → stack.BV9hnvu4.d.ts} +31 -7
  153. package/dist/shared/{stack.DdI5W6MB.d.mts → stack.BozPgbrZ.d.cts} +19 -19
  154. package/dist/shared/{stack.DdI5W6MB.d.ts → stack.BozPgbrZ.d.mts} +19 -19
  155. package/dist/shared/{stack.DdI5W6MB.d.cts → stack.BozPgbrZ.d.ts} +19 -19
  156. package/dist/shared/{stack.CP68pFEH.d.mts → stack.C9Mg2Q46.d.cts} +33 -3
  157. package/dist/shared/{stack.BeSm90va.d.ts → stack.CTDVxbrA.d.ts} +72 -14
  158. package/dist/shared/{stack.C-Ptrz8s.d.ts → stack.Cj_zKww4.d.ts} +4 -4
  159. package/dist/shared/{stack.TIBF2AOx.d.ts → stack.CxaFNQCV.d.mts} +89 -34
  160. package/dist/shared/{stack.CMh_EdxW.d.cts → stack.D-b5zbPm.d.cts} +72 -14
  161. package/dist/shared/{stack.Dw0Ly2TM.d.cts → stack.DTtmJPQO.d.mts} +1 -1
  162. package/dist/shared/{stack.BKfolAyK.d.ts → stack.DXnclTG7.d.ts} +11 -11
  163. package/dist/shared/{stack.snB1EDP7.d.cts → stack.DaZM10cp.d.cts} +11 -11
  164. package/dist/shared/{stack.Dg09R0oB.d.mts → stack.FVWf2JhZ.d.mts} +72 -14
  165. package/dist/shared/{stack.BIXEI6v_.d.mts → stack.cfCkioTe.d.mts} +11 -11
  166. package/dist/shared/{stack.6fUOjLs9.d.mts → stack.dH7u-TJH.d.mts} +4 -4
  167. package/dist/shared/{stack.BpolpQpf.d.cts → stack.j75TpKh2.d.ts} +89 -34
  168. package/dist/shared/{stack.rTy7-wQU.d.mts → stack.n1_i1p2B.d.cts} +89 -34
  169. package/dist/shared/{stack.IdtKDRka.d.cts → stack.sO33ZDhK.d.ts} +33 -3
  170. package/package.json +14 -1
  171. package/src/client/components/compose.tsx +7 -4
  172. package/src/plugins/ai-chat/api/page-tools.ts +111 -0
  173. package/src/plugins/ai-chat/api/plugin.ts +228 -72
  174. package/src/plugins/ai-chat/client/components/chat-input.tsx +2 -2
  175. package/src/plugins/ai-chat/client/components/chat-interface.tsx +154 -58
  176. package/src/plugins/ai-chat/client/components/chat-layout.tsx +166 -32
  177. package/src/plugins/ai-chat/client/components/chat-sidebar.tsx +1 -1
  178. package/src/plugins/ai-chat/client/context/page-ai-context.tsx +240 -0
  179. package/src/plugins/ai-chat/client/plugin.tsx +23 -31
  180. package/src/plugins/ai-chat/schemas.ts +16 -0
  181. package/src/plugins/blog/api/plugin.ts +31 -47
  182. package/src/plugins/blog/client/components/forms/post-forms.tsx +29 -2
  183. package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +28 -0
  184. package/src/plugins/blog/client/components/pages/fill-blog-form-handler.ts +38 -0
  185. package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +33 -1
  186. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +20 -0
  187. package/src/plugins/blog/client/plugin.tsx +36 -39
  188. package/src/plugins/client/index.ts +5 -1
  189. package/src/plugins/cms/api/index.ts +4 -0
  190. package/src/plugins/cms/api/mutations.ts +84 -0
  191. package/src/plugins/cms/api/plugin.ts +23 -17
  192. package/src/plugins/cms/client/plugin.tsx +18 -21
  193. package/src/plugins/cms/types.ts +7 -7
  194. package/src/plugins/form-builder/api/plugin.ts +64 -64
  195. package/src/plugins/form-builder/client/plugin.tsx +19 -18
  196. package/src/plugins/form-builder/types.ts +19 -24
  197. package/src/plugins/kanban/api/index.ts +6 -0
  198. package/src/plugins/kanban/api/mutations.ts +169 -0
  199. package/src/plugins/kanban/api/plugin.ts +123 -136
  200. package/src/plugins/kanban/client/hooks/kanban-hooks.tsx +4 -0
  201. package/src/plugins/kanban/client/plugin.tsx +35 -41
  202. package/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.tsx +132 -0
  203. package/src/plugins/ui-builder/client/plugin.tsx +11 -10
  204. package/src/plugins/ui-builder/types.ts +4 -4
  205. package/src/plugins/utils.ts +92 -1
  206. package/dist/shared/{stack.CBON0dWL.d.mts → stack.BQmuNl5p.d.cts} +2 -2
  207. package/dist/shared/{stack.CBON0dWL.d.ts → stack.BQmuNl5p.d.mts} +2 -2
  208. package/dist/shared/{stack.CBON0dWL.d.cts → stack.BQmuNl5p.d.ts} +2 -2
@@ -1,27 +1,37 @@
1
1
  "use client";
2
- import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { useState, useCallback } from 'react';
4
4
  import { Button } from '../../../../../../ui/src/components/button.mjs';
5
+ import { Badge } from '../../../../../../ui/src/components/badge.mjs';
5
6
  import { Sheet, SheetTrigger, SheetContent } from '../../../../../../ui/src/components/sheet.mjs';
6
- import { Menu, PanelLeftClose, PanelLeft } from 'lucide-react';
7
+ import { Sparkles, Trash2, X, Menu, PanelLeftClose, PanelLeft } from 'lucide-react';
7
8
  import { cn } from '../../../../../../ui/src/lib/utils.mjs';
8
9
  import { ChatSidebar } from './chat-sidebar.mjs';
9
10
  import { ChatInterface } from './chat-interface.mjs';
11
+ import { usePageAIContext } from '../../../../../../../plugins/ai-chat/client/context/page-ai-context.mjs';
10
12
 
11
- function ChatLayout({
12
- apiBaseURL,
13
- apiBasePath,
14
- conversationId,
15
- layout = "full",
16
- className,
17
- showSidebar = true,
18
- widgetHeight = "600px",
19
- initialMessages,
20
- onMessagesChange
21
- }) {
13
+ function ChatLayout(props) {
14
+ const {
15
+ apiBaseURL,
16
+ apiBasePath,
17
+ conversationId,
18
+ layout = "full",
19
+ className,
20
+ showSidebar = true,
21
+ initialMessages,
22
+ onMessagesChange
23
+ } = props;
24
+ const widgetHeight = props.layout === "widget" ? props.widgetHeight ?? "600px" : "600px";
25
+ const widgetWidth = props.layout === "widget" ? props.widgetWidth ?? "380px" : "380px";
26
+ const defaultOpen = props.layout === "widget" ? props.defaultOpen ?? false : false;
27
+ const showTrigger = props.layout === "widget" ? props.showTrigger ?? true : true;
22
28
  const [sidebarOpen, setSidebarOpen] = useState(true);
23
29
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
24
30
  const [chatResetKey, setChatResetKey] = useState(0);
31
+ const [widgetOpen, setWidgetOpen] = useState(defaultOpen);
32
+ const [widgetResetKey, setWidgetResetKey] = useState(0);
33
+ const [widgetEverOpened, setWidgetEverOpened] = useState(defaultOpen);
34
+ const pageAIContext = usePageAIContext();
25
35
  const apiPath = `${apiBaseURL}${apiBasePath}/chat`;
26
36
  const handleNewChat = useCallback(() => {
27
37
  if (!conversationId) {
@@ -29,26 +39,81 @@ function ChatLayout({
29
39
  }
30
40
  }, [conversationId]);
31
41
  if (layout === "widget") {
32
- return /* @__PURE__ */ jsx(
33
- "div",
34
- {
35
- className: cn(
36
- "flex flex-col w-full border rounded-xl overflow-hidden bg-background shadow-sm",
37
- className
38
- ),
39
- style: { height: widgetHeight },
40
- children: /* @__PURE__ */ jsx(
41
- ChatInterface,
42
- {
43
- apiPath,
44
- id: conversationId,
45
- variant: "widget",
46
- initialMessages,
47
- onMessagesChange
48
- }
49
- )
50
- }
51
- );
42
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-end gap-3", className), children: [
43
+ /* @__PURE__ */ jsxs(
44
+ "div",
45
+ {
46
+ className: cn(
47
+ "flex flex-col border rounded-xl overflow-hidden bg-background shadow-xl",
48
+ widgetOpen ? "flex" : "hidden"
49
+ ),
50
+ style: { height: widgetHeight, width: widgetWidth },
51
+ children: [
52
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-1.5 border-b bg-muted/40", children: [
53
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3 text-muted-foreground" }),
54
+ pageAIContext ? /* @__PURE__ */ jsx(
55
+ Badge,
56
+ {
57
+ variant: "secondary",
58
+ className: "text-xs",
59
+ "data-testid": "page-context-badge",
60
+ children: pageAIContext.routeName
61
+ }
62
+ ) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground font-medium", children: "AI Chat" }),
63
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
64
+ /* @__PURE__ */ jsx(
65
+ Button,
66
+ {
67
+ variant: "ghost",
68
+ size: "icon",
69
+ className: "h-5 w-5",
70
+ onClick: () => setWidgetResetKey((prev) => prev + 1),
71
+ "aria-label": "Clear chat",
72
+ title: "Clear chat",
73
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
74
+ }
75
+ ),
76
+ /* @__PURE__ */ jsx(
77
+ Button,
78
+ {
79
+ variant: "ghost",
80
+ size: "icon",
81
+ className: "h-5 w-5",
82
+ onClick: () => setWidgetOpen(false),
83
+ "aria-label": "Close chat",
84
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
85
+ }
86
+ )
87
+ ] }),
88
+ widgetEverOpened && /* @__PURE__ */ jsx(
89
+ ChatInterface,
90
+ {
91
+ apiPath,
92
+ id: conversationId,
93
+ variant: "widget",
94
+ initialMessages,
95
+ onMessagesChange
96
+ },
97
+ `widget-${conversationId ?? "new"}-${widgetResetKey}`
98
+ )
99
+ ]
100
+ }
101
+ ),
102
+ showTrigger && /* @__PURE__ */ jsx(
103
+ Button,
104
+ {
105
+ size: "icon",
106
+ className: "h-12 w-12 rounded-full shadow-lg",
107
+ onClick: () => {
108
+ setWidgetOpen((prev) => !prev);
109
+ setWidgetEverOpened(true);
110
+ },
111
+ "aria-label": widgetOpen ? "Close chat" : "Open chat",
112
+ "data-testid": "widget-trigger",
113
+ children: widgetOpen ? /* @__PURE__ */ jsx(X, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(Sparkles, { className: "h-5 w-5" })
114
+ }
115
+ )
116
+ ] });
52
117
  }
53
118
  return /* @__PURE__ */ jsxs(
54
119
  "div",
@@ -77,7 +142,7 @@ function ChatLayout({
77
142
  }
78
143
  ),
79
144
  /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [
80
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-2 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60", children: [
145
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-2 border-b bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60", children: [
81
146
  showSidebar && /* @__PURE__ */ jsxs(Sheet, { open: mobileSidebarOpen, onOpenChange: setMobileSidebarOpen, children: [
82
147
  /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
83
148
  Button,
@@ -111,7 +176,19 @@ function ChatLayout({
111
176
  children: sidebarOpen ? /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(PanelLeft, { className: "h-5 w-5" })
112
177
  }
113
178
  ),
114
- /* @__PURE__ */ jsx("div", { className: "flex-1" })
179
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
180
+ pageAIContext && /* @__PURE__ */ jsxs(
181
+ Badge,
182
+ {
183
+ variant: "secondary",
184
+ className: "text-xs gap-1 mr-2",
185
+ "data-testid": "page-context-badge",
186
+ children: [
187
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
188
+ pageAIContext.routeName
189
+ ]
190
+ }
191
+ )
115
192
  ] }),
116
193
  /* @__PURE__ */ jsx(
117
194
  ChatInterface,
@@ -108,7 +108,7 @@ function ChatSidebar({
108
108
  ]
109
109
  }
110
110
  ) }),
111
- /* @__PURE__ */ jsxRuntime.jsx(scrollArea.ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-center text-sm text-muted-foreground", children: localization.CHAT_LOADING }) : conversations.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-center text-sm text-muted-foreground", children: localization.SIDEBAR_NO_CONVERSATIONS }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: conversations.map((conversation) => /* @__PURE__ */ jsxRuntime.jsxs(
111
+ /* @__PURE__ */ jsxRuntime.jsx(scrollArea.ScrollArea, { className: "flex-1 [&_[data-slot=scroll-area-viewport]>div]:!block", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-center text-sm text-muted-foreground", children: localization.CHAT_LOADING }) : conversations.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-center text-sm text-muted-foreground", children: localization.SIDEBAR_NO_CONVERSATIONS }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: conversations.map((conversation) => /* @__PURE__ */ jsxRuntime.jsxs(
112
112
  "div",
113
113
  {
114
114
  className: utils.cn(
@@ -106,7 +106,7 @@ function ChatSidebar({
106
106
  ]
107
107
  }
108
108
  ) }),
109
- /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsx("div", { className: "p-2", children: isLoading ? /* @__PURE__ */ jsx("div", { className: "p-4 text-center text-sm text-muted-foreground", children: localization.CHAT_LOADING }) : conversations.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-4 text-center text-sm text-muted-foreground", children: localization.SIDEBAR_NO_CONVERSATIONS }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: conversations.map((conversation) => /* @__PURE__ */ jsxs(
109
+ /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1 [&_[data-slot=scroll-area-viewport]>div]:!block", children: /* @__PURE__ */ jsx("div", { className: "p-2", children: isLoading ? /* @__PURE__ */ jsx("div", { className: "p-4 text-center text-sm text-muted-foreground", children: localization.CHAT_LOADING }) : conversations.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-4 text-center text-sm text-muted-foreground", children: localization.SIDEBAR_NO_CONVERSATIONS }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: conversations.map((conversation) => /* @__PURE__ */ jsxs(
110
110
  "div",
111
111
  {
112
112
  className: cn(
@@ -22,10 +22,10 @@ function createConversationsLoader(config) {
22
22
  };
23
23
  try {
24
24
  if (hooks?.beforeLoadConversations) {
25
- const canLoad = await hooks.beforeLoadConversations(context);
26
- if (!canLoad) {
27
- throw new Error("Load prevented by beforeLoadConversations hook");
28
- }
25
+ await client.runClientHookWithShim(
26
+ () => hooks.beforeLoadConversations(context),
27
+ "Load prevented by beforeLoadConversations hook"
28
+ );
29
29
  }
30
30
  const client$1 = client.createApiClient({
31
31
  baseURL: apiBaseURL,
@@ -38,13 +38,10 @@ function createConversationsLoader(config) {
38
38
  const conversations = queryClient.getQueryData(
39
39
  listQuery.queryKey
40
40
  ) || null;
41
- const canContinue = await hooks.afterLoadConversations(
42
- conversations,
43
- context
41
+ await client.runClientHookWithShim(
42
+ () => hooks.afterLoadConversations(conversations, context),
43
+ "Load prevented by afterLoadConversations hook"
44
44
  );
45
- if (canContinue === false) {
46
- throw new Error("Load prevented by afterLoadConversations hook");
47
- }
48
45
  }
49
46
  const queryState = queryClient.getQueryState(listQuery.queryKey);
50
47
  if (queryState?.error && hooks?.onLoadError) {
@@ -73,10 +70,10 @@ function createConversationLoader(id, config) {
73
70
  };
74
71
  try {
75
72
  if (hooks?.beforeLoadConversation) {
76
- const canLoad = await hooks.beforeLoadConversation(id, context);
77
- if (!canLoad) {
78
- throw new Error("Load prevented by beforeLoadConversation hook");
79
- }
73
+ await client.runClientHookWithShim(
74
+ () => hooks.beforeLoadConversation(id, context),
75
+ "Load prevented by beforeLoadConversation hook"
76
+ );
80
77
  }
81
78
  const client$1 = client.createApiClient({
82
79
  baseURL: apiBaseURL,
@@ -91,14 +88,10 @@ function createConversationLoader(id, config) {
91
88
  ]);
92
89
  if (hooks?.afterLoadConversation) {
93
90
  const conversation = queryClient.getQueryData(conversationQuery.queryKey) || null;
94
- const canContinue = await hooks.afterLoadConversation(
95
- conversation,
96
- id,
97
- context
91
+ await client.runClientHookWithShim(
92
+ () => hooks.afterLoadConversation(conversation, id, context),
93
+ "Load prevented by afterLoadConversation hook"
98
94
  );
99
- if (canContinue === false) {
100
- throw new Error("Load prevented by afterLoadConversation hook");
101
- }
102
95
  }
103
96
  const queryState = queryClient.getQueryState(
104
97
  conversationQuery.queryKey
@@ -1,5 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { defineClientPlugin, createApiClient } from '@btst/stack/plugins/client';
2
+ import { defineClientPlugin, runClientHookWithShim, createApiClient } from '@btst/stack/plugins/client';
3
3
  import { createRoute } from '@btst/yar';
4
4
  import { createAiChatQueryKeys } from '../../../../../../plugins/ai-chat/query-keys.mjs';
5
5
  import { ChatLayout } from './components/chat-layout.mjs';
@@ -20,10 +20,10 @@ function createConversationsLoader(config) {
20
20
  };
21
21
  try {
22
22
  if (hooks?.beforeLoadConversations) {
23
- const canLoad = await hooks.beforeLoadConversations(context);
24
- if (!canLoad) {
25
- throw new Error("Load prevented by beforeLoadConversations hook");
26
- }
23
+ await runClientHookWithShim(
24
+ () => hooks.beforeLoadConversations(context),
25
+ "Load prevented by beforeLoadConversations hook"
26
+ );
27
27
  }
28
28
  const client = createApiClient({
29
29
  baseURL: apiBaseURL,
@@ -36,13 +36,10 @@ function createConversationsLoader(config) {
36
36
  const conversations = queryClient.getQueryData(
37
37
  listQuery.queryKey
38
38
  ) || null;
39
- const canContinue = await hooks.afterLoadConversations(
40
- conversations,
41
- context
39
+ await runClientHookWithShim(
40
+ () => hooks.afterLoadConversations(conversations, context),
41
+ "Load prevented by afterLoadConversations hook"
42
42
  );
43
- if (canContinue === false) {
44
- throw new Error("Load prevented by afterLoadConversations hook");
45
- }
46
43
  }
47
44
  const queryState = queryClient.getQueryState(listQuery.queryKey);
48
45
  if (queryState?.error && hooks?.onLoadError) {
@@ -71,10 +68,10 @@ function createConversationLoader(id, config) {
71
68
  };
72
69
  try {
73
70
  if (hooks?.beforeLoadConversation) {
74
- const canLoad = await hooks.beforeLoadConversation(id, context);
75
- if (!canLoad) {
76
- throw new Error("Load prevented by beforeLoadConversation hook");
77
- }
71
+ await runClientHookWithShim(
72
+ () => hooks.beforeLoadConversation(id, context),
73
+ "Load prevented by beforeLoadConversation hook"
74
+ );
78
75
  }
79
76
  const client = createApiClient({
80
77
  baseURL: apiBaseURL,
@@ -89,14 +86,10 @@ function createConversationLoader(id, config) {
89
86
  ]);
90
87
  if (hooks?.afterLoadConversation) {
91
88
  const conversation = queryClient.getQueryData(conversationQuery.queryKey) || null;
92
- const canContinue = await hooks.afterLoadConversation(
93
- conversation,
94
- id,
95
- context
89
+ await runClientHookWithShim(
90
+ () => hooks.afterLoadConversation(conversation, id, context),
91
+ "Load prevented by afterLoadConversation hook"
96
92
  );
97
- if (canContinue === false) {
98
- throw new Error("Load prevented by afterLoadConversation hook");
99
- }
100
93
  }
101
94
  const queryState = queryClient.getQueryState(
102
95
  conversationQuery.queryKey
@@ -34,7 +34,23 @@ const chatRequestSchema = z.z.object({
34
34
  ])
35
35
  ),
36
36
  conversationId: z.z.string().optional(),
37
- model: z.z.string().optional()
37
+ model: z.z.string().optional(),
38
+ /**
39
+ * Description of the current page context, injected into the AI system prompt.
40
+ * Sent by ChatInterface when a page has registered context via useRegisterPageAIContext.
41
+ */
42
+ pageContext: z.z.string().max(16e3).optional(),
43
+ /**
44
+ * Names of client-side tools currently available on the page.
45
+ * The server includes matching tool schemas in the streamText call.
46
+ */
47
+ availableTools: z.z.array(z.z.string()).optional(),
48
+ /**
49
+ * The routeName registered by the page via useRegisterPageAIContext.
50
+ * Cross-validated server-side against each built-in tool's route allowlist
51
+ * to prevent a page from claiming tools intended for a different route.
52
+ */
53
+ routeName: z.z.string().optional()
38
54
  });
39
55
 
40
56
  exports.chatRequestSchema = chatRequestSchema;
@@ -32,7 +32,23 @@ const chatRequestSchema = z.object({
32
32
  ])
33
33
  ),
34
34
  conversationId: z.string().optional(),
35
- model: z.string().optional()
35
+ model: z.string().optional(),
36
+ /**
37
+ * Description of the current page context, injected into the AI system prompt.
38
+ * Sent by ChatInterface when a page has registered context via useRegisterPageAIContext.
39
+ */
40
+ pageContext: z.string().max(16e3).optional(),
41
+ /**
42
+ * Names of client-side tools currently available on the page.
43
+ * The server includes matching tool schemas in the streamText call.
44
+ */
45
+ availableTools: z.array(z.string()).optional(),
46
+ /**
47
+ * The routeName registered by the page via useRegisterPageAIContext.
48
+ * Cross-validated server-side against each built-in tool's route allowlist
49
+ * to prevent a page from claiming tools intended for a different route.
50
+ */
51
+ routeName: z.string().optional()
36
52
  });
37
53
 
38
54
  export { chatRequestSchema, createConversationSchema, updateConversationSchema };
@@ -3,11 +3,12 @@
3
3
  const api = require('@btst/stack/plugins/api');
4
4
  const z = require('zod');
5
5
  const db = require('../db.cjs');
6
- const utils = require('../utils.cjs');
6
+ const utils$1 = require('../utils.cjs');
7
7
  const schemas = require('../schemas.cjs');
8
8
  const getters = require('./getters.cjs');
9
9
  const queryKeyDefs = require('./query-key-defs.cjs');
10
10
  const serializers = require('./serializers.cjs');
11
+ const utils = require('../../utils.cjs');
11
12
 
12
13
  function createBlogPrefetchForRoute(adapter) {
13
14
  return async function prefetchForRoute(key, qc, params) {
@@ -113,7 +114,7 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
113
114
  for (const tag of allTags) {
114
115
  tagMapBySlug.set(tag.slug, tag);
115
116
  }
116
- const tagSlugs = tagsToFindOrCreate.map((tag) => utils.slugify(tag.name));
117
+ const tagSlugs = tagsToFindOrCreate.map((tag) => utils$1.slugify(tag.name));
117
118
  const foundTags = [];
118
119
  for (const slug of tagSlugs) {
119
120
  const tag = tagMapBySlug.get(slug);
@@ -126,7 +127,7 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
126
127
  ...foundTags.map((tag) => tag.slug)
127
128
  ]);
128
129
  const tagsToCreate = tagsToFindOrCreate.filter(
129
- (tag) => !existingSlugs.has(utils.slugify(tag.name))
130
+ (tag) => !existingSlugs.has(utils$1.slugify(tag.name))
130
131
  );
131
132
  const createdTags = [];
132
133
  for (const tag of tagsToCreate) {
@@ -135,7 +136,7 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
135
136
  model: "tag",
136
137
  data: {
137
138
  name: normalizedName,
138
- slug: utils.slugify(normalizedName),
139
+ slug: utils$1.slugify(normalizedName),
139
140
  createdAt: /* @__PURE__ */ new Date(),
140
141
  updatedAt: /* @__PURE__ */ new Date()
141
142
  }
@@ -155,12 +156,11 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
155
156
  const context = { query, headers };
156
157
  try {
157
158
  if (hooks?.onBeforeListPosts) {
158
- const canList = await hooks.onBeforeListPosts(query, context);
159
- if (!canList) {
160
- throw ctx.error(403, {
161
- message: "Unauthorized: Cannot list posts"
162
- });
163
- }
159
+ await utils.runHookWithShim(
160
+ () => hooks.onBeforeListPosts(query, context),
161
+ ctx.error,
162
+ "Unauthorized: Cannot list posts"
163
+ );
164
164
  }
165
165
  const result = await getters.getAllPosts(adapter, query);
166
166
  if (hooks?.onPostsRead) {
@@ -188,19 +188,15 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
188
188
  };
189
189
  try {
190
190
  if (hooks?.onBeforeCreatePost) {
191
- const canCreate = await hooks.onBeforeCreatePost(
192
- ctx.body,
193
- context
191
+ await utils.runHookWithShim(
192
+ () => hooks.onBeforeCreatePost(ctx.body, context),
193
+ ctx.error,
194
+ "Unauthorized: Cannot create post"
194
195
  );
195
- if (!canCreate) {
196
- throw ctx.error(403, {
197
- message: "Unauthorized: Cannot create post"
198
- });
199
- }
200
196
  }
201
197
  const { tags, ...postData } = ctx.body;
202
198
  const tagNames = tags || [];
203
- const slug = utils.slugify(postData.slug || postData.title);
199
+ const slug = utils$1.slugify(postData.slug || postData.title);
204
200
  if (!slug) {
205
201
  throw ctx.error(400, {
206
202
  message: "Invalid slug: must contain at least one alphanumeric character"
@@ -259,20 +255,15 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
259
255
  };
260
256
  try {
261
257
  if (hooks?.onBeforeUpdatePost) {
262
- const canUpdate = await hooks.onBeforeUpdatePost(
263
- ctx.params.id,
264
- ctx.body,
265
- context
258
+ await utils.runHookWithShim(
259
+ () => hooks.onBeforeUpdatePost(ctx.params.id, ctx.body, context),
260
+ ctx.error,
261
+ "Unauthorized: Cannot update post"
266
262
  );
267
- if (!canUpdate) {
268
- throw ctx.error(403, {
269
- message: "Unauthorized: Cannot update post"
270
- });
271
- }
272
263
  }
273
264
  const { tags, slug: rawSlug, ...restPostData } = ctx.body;
274
265
  const tagNames = tags || [];
275
- const slugified = rawSlug ? utils.slugify(rawSlug) : void 0;
266
+ const slugified = rawSlug ? utils$1.slugify(rawSlug) : void 0;
276
267
  if (rawSlug && !slugified) {
277
268
  throw ctx.error(400, {
278
269
  message: "Invalid slug: must contain at least one alphanumeric character"
@@ -364,15 +355,11 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
364
355
  };
365
356
  try {
366
357
  if (hooks?.onBeforeDeletePost) {
367
- const canDelete = await hooks.onBeforeDeletePost(
368
- ctx.params.id,
369
- context
358
+ await utils.runHookWithShim(
359
+ () => hooks.onBeforeDeletePost(ctx.params.id, context),
360
+ ctx.error,
361
+ "Unauthorized: Cannot delete post"
370
362
  );
371
- if (!canDelete) {
372
- throw ctx.error(403, {
373
- message: "Unauthorized: Cannot delete post"
374
- });
375
- }
376
363
  }
377
364
  await adapter.delete({
378
365
  model: "post",
@@ -401,15 +388,11 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
401
388
  const context = { query, headers };
402
389
  try {
403
390
  if (hooks?.onBeforeListPosts) {
404
- const canList = await hooks.onBeforeListPosts(
405
- { published: true },
406
- context
391
+ await utils.runHookWithShim(
392
+ () => hooks.onBeforeListPosts({ published: true }, context),
393
+ ctx.error,
394
+ "Unauthorized: Cannot list posts"
407
395
  );
408
- if (!canList) {
409
- throw ctx.error(403, {
410
- message: "Unauthorized: Cannot list posts"
411
- });
412
- }
413
396
  }
414
397
  const date = query.date;
415
398
  const previousPosts = await adapter.findMany({
@@ -6,6 +6,7 @@ import { createPostSchema, updatePostSchema } from '../schemas.mjs';
6
6
  import { getAllTags, getPostBySlug, getAllPosts } from './getters.mjs';
7
7
  import { BLOG_QUERY_KEYS } from './query-key-defs.mjs';
8
8
  import { serializePost, serializeTag } from './serializers.mjs';
9
+ import { runHookWithShim } from '../../utils.mjs';
9
10
 
10
11
  function createBlogPrefetchForRoute(adapter) {
11
12
  return async function prefetchForRoute(key, qc, params) {
@@ -153,12 +154,11 @@ const blogBackendPlugin = (hooks) => defineBackendPlugin({
153
154
  const context = { query, headers };
154
155
  try {
155
156
  if (hooks?.onBeforeListPosts) {
156
- const canList = await hooks.onBeforeListPosts(query, context);
157
- if (!canList) {
158
- throw ctx.error(403, {
159
- message: "Unauthorized: Cannot list posts"
160
- });
161
- }
157
+ await runHookWithShim(
158
+ () => hooks.onBeforeListPosts(query, context),
159
+ ctx.error,
160
+ "Unauthorized: Cannot list posts"
161
+ );
162
162
  }
163
163
  const result = await getAllPosts(adapter, query);
164
164
  if (hooks?.onPostsRead) {
@@ -186,15 +186,11 @@ const blogBackendPlugin = (hooks) => defineBackendPlugin({
186
186
  };
187
187
  try {
188
188
  if (hooks?.onBeforeCreatePost) {
189
- const canCreate = await hooks.onBeforeCreatePost(
190
- ctx.body,
191
- context
189
+ await runHookWithShim(
190
+ () => hooks.onBeforeCreatePost(ctx.body, context),
191
+ ctx.error,
192
+ "Unauthorized: Cannot create post"
192
193
  );
193
- if (!canCreate) {
194
- throw ctx.error(403, {
195
- message: "Unauthorized: Cannot create post"
196
- });
197
- }
198
194
  }
199
195
  const { tags, ...postData } = ctx.body;
200
196
  const tagNames = tags || [];
@@ -257,16 +253,11 @@ const blogBackendPlugin = (hooks) => defineBackendPlugin({
257
253
  };
258
254
  try {
259
255
  if (hooks?.onBeforeUpdatePost) {
260
- const canUpdate = await hooks.onBeforeUpdatePost(
261
- ctx.params.id,
262
- ctx.body,
263
- context
256
+ await runHookWithShim(
257
+ () => hooks.onBeforeUpdatePost(ctx.params.id, ctx.body, context),
258
+ ctx.error,
259
+ "Unauthorized: Cannot update post"
264
260
  );
265
- if (!canUpdate) {
266
- throw ctx.error(403, {
267
- message: "Unauthorized: Cannot update post"
268
- });
269
- }
270
261
  }
271
262
  const { tags, slug: rawSlug, ...restPostData } = ctx.body;
272
263
  const tagNames = tags || [];
@@ -362,15 +353,11 @@ const blogBackendPlugin = (hooks) => defineBackendPlugin({
362
353
  };
363
354
  try {
364
355
  if (hooks?.onBeforeDeletePost) {
365
- const canDelete = await hooks.onBeforeDeletePost(
366
- ctx.params.id,
367
- context
356
+ await runHookWithShim(
357
+ () => hooks.onBeforeDeletePost(ctx.params.id, context),
358
+ ctx.error,
359
+ "Unauthorized: Cannot delete post"
368
360
  );
369
- if (!canDelete) {
370
- throw ctx.error(403, {
371
- message: "Unauthorized: Cannot delete post"
372
- });
373
- }
374
361
  }
375
362
  await adapter.delete({
376
363
  model: "post",
@@ -399,15 +386,11 @@ const blogBackendPlugin = (hooks) => defineBackendPlugin({
399
386
  const context = { query, headers };
400
387
  try {
401
388
  if (hooks?.onBeforeListPosts) {
402
- const canList = await hooks.onBeforeListPosts(
403
- { published: true },
404
- context
389
+ await runHookWithShim(
390
+ () => hooks.onBeforeListPosts({ published: true }, context),
391
+ ctx.error,
392
+ "Unauthorized: Cannot list posts"
405
393
  );
406
- if (!canList) {
407
- throw ctx.error(403, {
408
- message: "Unauthorized: Cannot list posts"
409
- });
410
- }
411
394
  }
412
395
  const date = query.date;
413
396
  const previousPosts = await adapter.findMany({