@btst/stack 2.2.0 → 2.4.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 (237) 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 +54 -7
  6. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +54 -7
  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/schemas.cjs +17 -1
  16. package/dist/packages/stack/src/plugins/ai-chat/schemas.mjs +17 -1
  17. package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +52 -1
  18. package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +52 -1
  19. package/dist/packages/stack/src/plugins/blog/api/query-key-defs.cjs +18 -0
  20. package/dist/packages/stack/src/plugins/blog/api/query-key-defs.mjs +15 -0
  21. package/dist/packages/stack/src/plugins/blog/api/serializers.cjs +21 -0
  22. package/dist/packages/stack/src/plugins/blog/api/serializers.mjs +18 -0
  23. package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.cjs +15 -2
  24. package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.mjs +16 -3
  25. package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.cjs +24 -1
  26. package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.mjs +24 -1
  27. package/dist/packages/stack/src/plugins/blog/client/components/pages/fill-blog-form-handler.cjs +26 -0
  28. package/dist/packages/stack/src/plugins/blog/client/components/pages/fill-blog-form-handler.mjs +24 -0
  29. package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.cjs +30 -1
  30. package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.mjs +30 -1
  31. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +18 -0
  32. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +18 -0
  33. package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +15 -0
  34. package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +16 -1
  35. package/dist/packages/stack/src/plugins/cms/api/getters.cjs +10 -0
  36. package/dist/packages/stack/src/plugins/cms/api/getters.mjs +10 -1
  37. package/dist/packages/stack/src/plugins/cms/api/mutations.cjs +48 -0
  38. package/dist/packages/stack/src/plugins/cms/api/mutations.mjs +46 -0
  39. package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +75 -0
  40. package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +76 -1
  41. package/dist/packages/stack/src/plugins/cms/api/query-key-defs.cjs +29 -0
  42. package/dist/packages/stack/src/plugins/cms/api/query-key-defs.mjs +26 -0
  43. package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +15 -0
  44. package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +16 -1
  45. package/dist/packages/stack/src/plugins/form-builder/api/getters.cjs +9 -0
  46. package/dist/packages/stack/src/plugins/form-builder/api/getters.mjs +9 -1
  47. package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +62 -1
  48. package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +63 -2
  49. package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.cjs +37 -0
  50. package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.mjs +33 -0
  51. package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +15 -0
  52. package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +16 -1
  53. package/dist/packages/stack/src/plugins/kanban/api/mutations.cjs +91 -0
  54. package/dist/packages/stack/src/plugins/kanban/api/mutations.mjs +87 -0
  55. package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +34 -1
  56. package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +34 -1
  57. package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.cjs +26 -0
  58. package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.mjs +23 -0
  59. package/dist/packages/stack/src/plugins/kanban/api/serializers.cjs +30 -0
  60. package/dist/packages/stack/src/plugins/kanban/api/serializers.mjs +26 -0
  61. package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.cjs +7 -3
  62. package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.mjs +7 -3
  63. package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +10 -0
  64. package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +11 -1
  65. package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.cjs +89 -0
  66. package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.mjs +89 -0
  67. package/dist/packages/stack/src/plugins/utils.cjs +6 -0
  68. package/dist/packages/stack/src/plugins/utils.mjs +6 -1
  69. package/dist/plugins/ai-chat/api/index.d.cts +1 -1
  70. package/dist/plugins/ai-chat/api/index.d.mts +1 -1
  71. package/dist/plugins/ai-chat/api/index.d.ts +1 -1
  72. package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
  73. package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
  74. package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
  75. package/dist/plugins/ai-chat/client/context/page-ai-context.cjs +92 -0
  76. package/dist/plugins/ai-chat/client/context/page-ai-context.d.cts +84 -0
  77. package/dist/plugins/ai-chat/client/context/page-ai-context.d.mts +84 -0
  78. package/dist/plugins/ai-chat/client/context/page-ai-context.d.ts +84 -0
  79. package/dist/plugins/ai-chat/client/context/page-ai-context.mjs +88 -0
  80. package/dist/plugins/ai-chat/client/hooks/index.d.cts +1 -1
  81. package/dist/plugins/ai-chat/client/hooks/index.d.mts +1 -1
  82. package/dist/plugins/ai-chat/client/hooks/index.d.ts +1 -1
  83. package/dist/plugins/ai-chat/client/index.d.cts +2 -2
  84. package/dist/plugins/ai-chat/client/index.d.mts +2 -2
  85. package/dist/plugins/ai-chat/client/index.d.ts +2 -2
  86. package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
  87. package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
  88. package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
  89. package/dist/plugins/blog/api/index.cjs +5 -0
  90. package/dist/plugins/blog/api/index.d.cts +19 -4
  91. package/dist/plugins/blog/api/index.d.mts +19 -4
  92. package/dist/plugins/blog/api/index.d.ts +19 -4
  93. package/dist/plugins/blog/api/index.mjs +2 -0
  94. package/dist/plugins/blog/client/hooks/index.d.cts +3 -3
  95. package/dist/plugins/blog/client/hooks/index.d.mts +3 -3
  96. package/dist/plugins/blog/client/hooks/index.d.ts +3 -3
  97. package/dist/plugins/blog/client/index.d.cts +1 -1
  98. package/dist/plugins/blog/client/index.d.mts +1 -1
  99. package/dist/plugins/blog/client/index.d.ts +1 -1
  100. package/dist/plugins/blog/query-keys.cjs +6 -5
  101. package/dist/plugins/blog/query-keys.d.cts +8 -387
  102. package/dist/plugins/blog/query-keys.d.mts +8 -387
  103. package/dist/plugins/blog/query-keys.d.ts +8 -387
  104. package/dist/plugins/blog/query-keys.mjs +6 -5
  105. package/dist/plugins/client/index.cjs +1 -0
  106. package/dist/plugins/client/index.d.cts +8 -1
  107. package/dist/plugins/client/index.d.mts +8 -1
  108. package/dist/plugins/client/index.d.ts +8 -1
  109. package/dist/plugins/client/index.mjs +1 -1
  110. package/dist/plugins/cms/api/index.cjs +8 -0
  111. package/dist/plugins/cms/api/index.d.cts +7 -219
  112. package/dist/plugins/cms/api/index.d.mts +7 -219
  113. package/dist/plugins/cms/api/index.d.ts +7 -219
  114. package/dist/plugins/cms/api/index.mjs +3 -1
  115. package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
  116. package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
  117. package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
  118. package/dist/plugins/cms/query-keys.cjs +2 -1
  119. package/dist/plugins/cms/query-keys.d.cts +5 -9
  120. package/dist/plugins/cms/query-keys.d.mts +5 -9
  121. package/dist/plugins/cms/query-keys.d.ts +5 -9
  122. package/dist/plugins/cms/query-keys.mjs +2 -1
  123. package/dist/plugins/form-builder/api/index.cjs +6 -0
  124. package/dist/plugins/form-builder/api/index.d.cts +7 -211
  125. package/dist/plugins/form-builder/api/index.d.mts +7 -211
  126. package/dist/plugins/form-builder/api/index.d.ts +7 -211
  127. package/dist/plugins/form-builder/api/index.mjs +2 -1
  128. package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
  129. package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
  130. package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
  131. package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
  132. package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
  133. package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
  134. package/dist/plugins/form-builder/query-keys.cjs +3 -2
  135. package/dist/plugins/form-builder/query-keys.d.cts +6 -6
  136. package/dist/plugins/form-builder/query-keys.d.mts +6 -6
  137. package/dist/plugins/form-builder/query-keys.d.ts +6 -6
  138. package/dist/plugins/form-builder/query-keys.mjs +3 -2
  139. package/dist/plugins/kanban/api/index.cjs +10 -0
  140. package/dist/plugins/kanban/api/index.d.cts +17 -392
  141. package/dist/plugins/kanban/api/index.d.mts +17 -392
  142. package/dist/plugins/kanban/api/index.d.ts +17 -392
  143. package/dist/plugins/kanban/api/index.mjs +3 -0
  144. package/dist/plugins/kanban/client/components/index.d.cts +1 -1
  145. package/dist/plugins/kanban/client/components/index.d.mts +1 -1
  146. package/dist/plugins/kanban/client/components/index.d.ts +1 -1
  147. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  148. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  149. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  150. package/dist/plugins/kanban/client/index.d.cts +1 -1
  151. package/dist/plugins/kanban/client/index.d.mts +1 -1
  152. package/dist/plugins/kanban/client/index.d.ts +1 -1
  153. package/dist/plugins/kanban/query-keys.cjs +2 -9
  154. package/dist/plugins/kanban/query-keys.d.cts +4 -16
  155. package/dist/plugins/kanban/query-keys.d.mts +4 -16
  156. package/dist/plugins/kanban/query-keys.d.ts +4 -16
  157. package/dist/plugins/kanban/query-keys.mjs +2 -9
  158. package/dist/plugins/ui-builder/index.d.cts +1 -1
  159. package/dist/plugins/ui-builder/index.d.mts +1 -1
  160. package/dist/plugins/ui-builder/index.d.ts +1 -1
  161. package/dist/shared/stack.B7ONvlD_.d.mts +293 -0
  162. package/dist/shared/{stack.BeSm90va.d.ts → stack.BEn34wW6.d.ts} +60 -2
  163. package/dist/shared/stack.BUkC2EsZ.d.cts +327 -0
  164. package/dist/shared/{stack.DaOcgmrM.d.ts → stack.BV9hnvu4.d.cts} +31 -7
  165. package/dist/shared/{stack.DaOcgmrM.d.cts → stack.BV9hnvu4.d.mts} +31 -7
  166. package/dist/shared/{stack.DaOcgmrM.d.mts → stack.BV9hnvu4.d.ts} +31 -7
  167. package/dist/shared/stack.BepFXT3w.d.mts +500 -0
  168. package/dist/shared/stack.CL8ts1Mu.d.ts +419 -0
  169. package/dist/shared/{stack.CXjzTMsb.d.cts → stack.CVDTkMoO.d.cts} +7 -1
  170. package/dist/shared/{stack.CXjzTMsb.d.mts → stack.CVDTkMoO.d.mts} +7 -1
  171. package/dist/shared/{stack.CXjzTMsb.d.ts → stack.CVDTkMoO.d.ts} +7 -1
  172. package/dist/shared/stack.CczspVn2.d.mts +327 -0
  173. package/dist/shared/stack.CgWzG5jH.d.ts +500 -0
  174. package/dist/shared/stack.D3GB6wKv.d.cts +500 -0
  175. package/dist/shared/stack.DASmUVjX.d.ts +327 -0
  176. package/dist/shared/{stack.QD1y_7NY.d.cts → stack.DJaKVY7v.d.cts} +1 -1
  177. package/dist/shared/{stack.QD1y_7NY.d.mts → stack.DJaKVY7v.d.mts} +1 -1
  178. package/dist/shared/{stack.QD1y_7NY.d.ts → stack.DJaKVY7v.d.ts} +1 -1
  179. package/dist/shared/{stack.Dg09R0oB.d.mts → stack.DTDxgFj8.d.mts} +60 -2
  180. package/dist/shared/{stack.CMh_EdxW.d.cts → stack.DWoCZff7.d.cts} +60 -2
  181. package/dist/shared/{stack.CIrIsc-A.d.cts → stack.DdI5W6MB.d.cts} +7 -1
  182. package/dist/shared/{stack.CIrIsc-A.d.mts → stack.DdI5W6MB.d.mts} +7 -1
  183. package/dist/shared/{stack.CIrIsc-A.d.ts → stack.DdI5W6MB.d.ts} +7 -1
  184. package/dist/shared/stack.Dk5r4W1F.d.mts +419 -0
  185. package/dist/shared/stack.Kq2-QzOC.d.ts +293 -0
  186. package/dist/shared/stack.heOA9gzA.d.cts +419 -0
  187. package/dist/shared/stack.kcdnD4gA.d.cts +293 -0
  188. package/package.json +16 -3
  189. package/src/client/components/compose.tsx +7 -4
  190. package/src/plugins/ai-chat/api/page-tools.ts +111 -0
  191. package/src/plugins/ai-chat/api/plugin.ts +180 -9
  192. package/src/plugins/ai-chat/client/components/chat-input.tsx +2 -2
  193. package/src/plugins/ai-chat/client/components/chat-interface.tsx +154 -58
  194. package/src/plugins/ai-chat/client/components/chat-layout.tsx +166 -32
  195. package/src/plugins/ai-chat/client/components/chat-sidebar.tsx +1 -1
  196. package/src/plugins/ai-chat/client/context/page-ai-context.tsx +240 -0
  197. package/src/plugins/ai-chat/schemas.ts +16 -0
  198. package/src/plugins/blog/api/index.ts +2 -0
  199. package/src/plugins/blog/api/plugin.ts +85 -0
  200. package/src/plugins/blog/api/query-key-defs.ts +46 -0
  201. package/src/plugins/blog/api/serializers.ts +27 -0
  202. package/src/plugins/blog/client/components/forms/post-forms.tsx +29 -2
  203. package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +28 -0
  204. package/src/plugins/blog/client/components/pages/fill-blog-form-handler.ts +38 -0
  205. package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +33 -1
  206. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +20 -0
  207. package/src/plugins/blog/client/plugin.tsx +19 -0
  208. package/src/plugins/blog/query-keys.ts +5 -7
  209. package/src/plugins/client/index.ts +1 -1
  210. package/src/plugins/cms/api/getters.ts +24 -0
  211. package/src/plugins/cms/api/index.ts +14 -1
  212. package/src/plugins/cms/api/mutations.ts +84 -0
  213. package/src/plugins/cms/api/plugin.ts +114 -0
  214. package/src/plugins/cms/api/query-key-defs.ts +53 -0
  215. package/src/plugins/cms/api/serializers.ts +12 -0
  216. package/src/plugins/cms/client/plugin.tsx +19 -0
  217. package/src/plugins/cms/query-keys.ts +2 -1
  218. package/src/plugins/form-builder/api/getters.ts +23 -0
  219. package/src/plugins/form-builder/api/index.ts +15 -2
  220. package/src/plugins/form-builder/api/plugin.ts +91 -0
  221. package/src/plugins/form-builder/api/query-key-defs.ts +79 -0
  222. package/src/plugins/form-builder/api/serializers.ts +12 -0
  223. package/src/plugins/form-builder/client/plugin.tsx +19 -0
  224. package/src/plugins/form-builder/query-keys.ts +6 -2
  225. package/src/plugins/kanban/api/index.ts +9 -0
  226. package/src/plugins/kanban/api/mutations.ts +169 -0
  227. package/src/plugins/kanban/api/plugin.ts +61 -0
  228. package/src/plugins/kanban/api/query-key-defs.ts +54 -0
  229. package/src/plugins/kanban/api/serializers.ts +49 -0
  230. package/src/plugins/kanban/client/hooks/kanban-hooks.tsx +4 -0
  231. package/src/plugins/kanban/client/plugin.tsx +13 -0
  232. package/src/plugins/kanban/query-keys.ts +2 -9
  233. package/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.tsx +132 -0
  234. package/src/plugins/utils.ts +19 -0
  235. package/dist/shared/{stack.BkYlUT_8.d.cts → stack.BQmuNl5p.d.cts} +6 -6
  236. package/dist/shared/{stack.BkYlUT_8.d.mts → stack.BQmuNl5p.d.mts} +6 -6
  237. package/dist/shared/{stack.BkYlUT_8.d.ts → stack.BQmuNl5p.d.ts} +6 -6
@@ -24,6 +24,59 @@ import {
24
24
  updateTaskSchema,
25
25
  } from "../schemas";
26
26
  import { getAllBoards, getBoardById } from "./getters";
27
+ import {
28
+ createKanbanTask,
29
+ findOrCreateKanbanBoard,
30
+ getKanbanColumnsByBoardId,
31
+ } from "./mutations";
32
+ import { KANBAN_QUERY_KEYS } from "./query-key-defs";
33
+ import { serializeBoard } from "./serializers";
34
+ import type { QueryClient } from "@tanstack/react-query";
35
+
36
+ /**
37
+ * Route keys for the Kanban plugin — matches the keys returned by
38
+ * `stackClient.router.getRoute(path).routeKey`.
39
+ */
40
+ export type KanbanRouteKey = "boards" | "newBoard" | "board";
41
+
42
+ interface KanbanPrefetchForRoute {
43
+ (key: "boards" | "newBoard", qc: QueryClient): Promise<void>;
44
+ (key: "board", qc: QueryClient, params: { boardId: string }): Promise<void>;
45
+ }
46
+
47
+ function createKanbanPrefetchForRoute(
48
+ adapter: Adapter,
49
+ ): KanbanPrefetchForRoute {
50
+ return async function prefetchForRoute(
51
+ key: KanbanRouteKey,
52
+ qc: QueryClient,
53
+ params?: Record<string, string>,
54
+ ): Promise<void> {
55
+ switch (key) {
56
+ case "boards": {
57
+ const result = await getAllBoards(adapter, { limit: 50, offset: 0 });
58
+ qc.setQueryData(
59
+ KANBAN_QUERY_KEYS.boardsList({}),
60
+ result.items.map(serializeBoard),
61
+ );
62
+ break;
63
+ }
64
+ case "board": {
65
+ const boardId = params?.boardId ?? "";
66
+ if (boardId) {
67
+ const board = await getBoardById(adapter, boardId);
68
+ qc.setQueryData(
69
+ KANBAN_QUERY_KEYS.boardDetail(boardId),
70
+ board ? serializeBoard(board) : null,
71
+ );
72
+ }
73
+ break;
74
+ }
75
+ default:
76
+ break;
77
+ }
78
+ } as KanbanPrefetchForRoute;
79
+ }
27
80
 
28
81
  /**
29
82
  * Context passed to kanban API hooks
@@ -268,6 +321,14 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
268
321
  getAllBoards: (params?: Parameters<typeof getAllBoards>[1]) =>
269
322
  getAllBoards(adapter, params),
270
323
  getBoardById: (id: string) => getBoardById(adapter, id),
324
+ prefetchForRoute: createKanbanPrefetchForRoute(adapter),
325
+ // Mutations
326
+ createTask: (input: Parameters<typeof createKanbanTask>[1]) =>
327
+ createKanbanTask(adapter, input),
328
+ findOrCreateBoard: (slug: string, name: string, columnTitles: string[]) =>
329
+ findOrCreateKanbanBoard(adapter, slug, name, columnTitles),
330
+ getColumnsByBoardId: (boardId: string) =>
331
+ getKanbanColumnsByBoardId(adapter, boardId),
271
332
  }),
272
333
 
273
334
  routes: (adapter: Adapter) => {
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Internal query key constants for the Kanban plugin.
3
+ * Shared between query-keys.ts (HTTP path) and prefetchForRoute (DB path)
4
+ * to prevent key drift between SSR loaders and SSG prefetching.
5
+ */
6
+
7
+ export interface BoardsListDiscriminator {
8
+ slug: string | undefined;
9
+ ownerId: string | undefined;
10
+ organizationId: string | undefined;
11
+ limit: number;
12
+ offset: number;
13
+ }
14
+
15
+ /**
16
+ * Builds the discriminator object for the boards list query key.
17
+ * Mirrors the inline object used in createBoardsQueries.list.
18
+ */
19
+ export function boardsListDiscriminator(params?: {
20
+ slug?: string;
21
+ ownerId?: string;
22
+ organizationId?: string;
23
+ limit?: number;
24
+ offset?: number;
25
+ }): BoardsListDiscriminator {
26
+ return {
27
+ slug: params?.slug,
28
+ ownerId: params?.ownerId,
29
+ organizationId: params?.organizationId,
30
+ limit: params?.limit ?? 50,
31
+ offset: params?.offset ?? 0,
32
+ };
33
+ }
34
+
35
+ /** Full query key builders — use these with queryClient.setQueryData() */
36
+ export const KANBAN_QUERY_KEYS = {
37
+ /**
38
+ * Key for boards.list(params) query.
39
+ * Full key: ["boards", "list", { slug, ownerId, organizationId, limit, offset }]
40
+ */
41
+ boardsList: (params?: {
42
+ slug?: string;
43
+ ownerId?: string;
44
+ organizationId?: string;
45
+ limit?: number;
46
+ offset?: number;
47
+ }) => ["boards", "list", boardsListDiscriminator(params)] as const,
48
+
49
+ /**
50
+ * Key for boards.detail(boardId) query.
51
+ * Full key: ["boards", "detail", boardId]
52
+ */
53
+ boardDetail: (boardId: string) => ["boards", "detail", boardId] as const,
54
+ };
@@ -0,0 +1,49 @@
1
+ import type {
2
+ Task,
3
+ ColumnWithTasks,
4
+ BoardWithColumns,
5
+ SerializedTask,
6
+ SerializedColumn,
7
+ SerializedBoardWithColumns,
8
+ } from "../types";
9
+
10
+ /**
11
+ * Serialize a Task for SSR/SSG use (convert dates to strings).
12
+ * Pure function — no DB access, no hooks.
13
+ */
14
+ export function serializeTask(task: Task): SerializedTask {
15
+ return {
16
+ ...task,
17
+ completedAt: task.completedAt?.toISOString(),
18
+ createdAt: task.createdAt.toISOString(),
19
+ updatedAt: task.updatedAt.toISOString(),
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Serialize a Column (with its tasks) for SSR/SSG use (convert dates to strings).
25
+ * Pure function — no DB access, no hooks.
26
+ */
27
+ export function serializeColumn(col: ColumnWithTasks): SerializedColumn {
28
+ return {
29
+ ...col,
30
+ createdAt: col.createdAt.toISOString(),
31
+ updatedAt: col.updatedAt.toISOString(),
32
+ tasks: col.tasks.map(serializeTask),
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Serialize a Board (with columns and tasks) for SSR/SSG use (convert dates to strings).
38
+ * Pure function — no DB access, no hooks.
39
+ */
40
+ export function serializeBoard(
41
+ board: BoardWithColumns,
42
+ ): SerializedBoardWithColumns {
43
+ return {
44
+ ...board,
45
+ createdAt: board.createdAt.toISOString(),
46
+ updatedAt: board.updatedAt.toISOString(),
47
+ columns: board.columns.map(serializeColumn),
48
+ };
49
+ }
@@ -85,6 +85,7 @@ export function useBoards(params?: {
85
85
  return useQuery({
86
86
  ...queries.boards.list(params),
87
87
  staleTime: 30_000,
88
+ refetchOnWindowFocus: true,
88
89
  });
89
90
  }
90
91
 
@@ -102,6 +103,7 @@ export function useSuspenseBoards(params?: {
102
103
  const result = useSuspenseQuery({
103
104
  ...queries.boards.list(params),
104
105
  staleTime: 30_000,
106
+ refetchOnWindowFocus: true,
105
107
  });
106
108
 
107
109
  if (result.error && !result.isFetching) {
@@ -121,6 +123,7 @@ export function useBoard(boardId: string) {
121
123
  return useQuery({
122
124
  ...queries.boards.detail(boardId),
123
125
  staleTime: 30_000,
126
+ refetchOnWindowFocus: true,
124
127
  enabled: !!boardId,
125
128
  });
126
129
  }
@@ -135,6 +138,7 @@ export function useSuspenseBoard(boardId: string) {
135
138
  const result = useSuspenseQuery({
136
139
  ...queries.boards.detail(boardId),
137
140
  staleTime: 30_000,
141
+ refetchOnWindowFocus: true,
138
142
  });
139
143
 
140
144
  if (result.error && !result.isFetching) {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  defineClientPlugin,
3
3
  createApiClient,
4
+ isConnectionError,
4
5
  } from "@btst/stack/plugins/client";
5
6
  import { createRoute } from "@btst/yar";
6
7
  import type { QueryClient } from "@tanstack/react-query";
@@ -178,6 +179,12 @@ function createBoardsLoader(config: KanbanClientConfig) {
178
179
  await hooks.onLoadError(error, context);
179
180
  }
180
181
  } catch (error) {
182
+ if (isConnectionError(error)) {
183
+ console.warn(
184
+ "[btst/kanban] route.loader() failed — no server running at build time. " +
185
+ "Use myStack.api.kanban.prefetchForRoute() for SSG data prefetching.",
186
+ );
187
+ }
181
188
  if (hooks?.onLoadError) {
182
189
  await hooks.onLoadError(error as Error, context);
183
190
  }
@@ -241,6 +248,12 @@ function createBoardLoader(boardId: string, config: KanbanClientConfig) {
241
248
  await hooks.onLoadError(error, context);
242
249
  }
243
250
  } catch (error) {
251
+ if (isConnectionError(error)) {
252
+ console.warn(
253
+ "[btst/kanban] route.loader() failed — no server running at build time. " +
254
+ "Use myStack.api.kanban.prefetchForRoute() for SSG data prefetching.",
255
+ );
256
+ }
244
257
  if (hooks?.onLoadError) {
245
258
  await hooks.onLoadError(error as Error, context);
246
259
  }
@@ -5,6 +5,7 @@ import {
5
5
  import type { KanbanApiRouter } from "./api";
6
6
  import { createApiClient } from "@btst/stack/plugins/client";
7
7
  import type { SerializedBoardWithColumns } from "./types";
8
+ import { boardsListDiscriminator } from "./api/query-key-defs";
8
9
 
9
10
  interface BoardsListParams {
10
11
  slug?: string;
@@ -63,15 +64,7 @@ function createBoardsQueries(
63
64
  ) {
64
65
  return createQueryKeys("boards", {
65
66
  list: (params?: BoardsListParams) => ({
66
- queryKey: [
67
- {
68
- slug: params?.slug,
69
- ownerId: params?.ownerId,
70
- organizationId: params?.organizationId,
71
- limit: params?.limit ?? 50,
72
- offset: params?.offset ?? 0,
73
- },
74
- ],
67
+ queryKey: [boardsListDiscriminator(params)],
75
68
  queryFn: async () => {
76
69
  try {
77
70
  const response = await client("/boards", {
@@ -22,9 +22,12 @@ import { toast } from "sonner";
22
22
  import UIBuilder from "@workspace/ui/components/ui-builder";
23
23
  import type {
24
24
  ComponentLayer,
25
+ ComponentRegistry,
25
26
  Variable,
26
27
  } from "@workspace/ui/components/ui-builder/types";
27
28
 
29
+ import { useLayerStore } from "@workspace/ui/lib/ui-builder/store/layer-store";
30
+ import { useRegisterPageAIContext } from "@btst/stack/plugins/ai-chat/client/context";
28
31
  import {
29
32
  useSuspenseUIBuilderPage,
30
33
  useCreateUIBuilderPage,
@@ -39,6 +42,104 @@ export interface PageBuilderPageProps {
39
42
  id?: string;
40
43
  }
41
44
 
45
+ /**
46
+ * Generate a concise AI-readable description of the available components
47
+ * in the component registry, including their prop names.
48
+ */
49
+ function buildRegistryDescription(registry: ComponentRegistry): string {
50
+ const lines: string[] = [];
51
+ for (const [name, entry] of Object.entries(registry) as [
52
+ string,
53
+ { schema?: unknown },
54
+ ][]) {
55
+ let propsLine = "";
56
+ try {
57
+ const shape = (entry.schema as any)?.shape as
58
+ | Record<string, unknown>
59
+ | undefined;
60
+ if (shape) {
61
+ const fields = Object.keys(shape).join(", ");
62
+ propsLine = ` — props: ${fields}`;
63
+ }
64
+ } catch {
65
+ // ignore schema introspection errors
66
+ }
67
+ lines.push(`- ${name}${propsLine}`);
68
+ }
69
+ return lines.join("\n");
70
+ }
71
+
72
+ /**
73
+ * Build the full page description string for the AI context.
74
+ * Stays within the 8,000-character pageContext limit.
75
+ */
76
+ function buildPageDescription(
77
+ id: string | undefined,
78
+ slug: string,
79
+ layers: ComponentLayer[],
80
+ registry: ComponentRegistry,
81
+ ): string {
82
+ const header = id
83
+ ? `UI Builder — editing page (slug: "${slug}")`
84
+ : "UI Builder — creating new page";
85
+
86
+ const layersJson = JSON.stringify(layers, null, 2);
87
+
88
+ const registryDesc = buildRegistryDescription(registry);
89
+
90
+ const layerFormat = `Each layer: { id: string, type: string, name: string, props: Record<string,any>, children?: ComponentLayer[] | string }`;
91
+
92
+ const full = [
93
+ header,
94
+ "",
95
+ `## Current Layers (${layers.length})`,
96
+ layersJson,
97
+ "",
98
+ `## Available Component Types`,
99
+ registryDesc,
100
+ "",
101
+ `## ComponentLayer format`,
102
+ layerFormat,
103
+ ].join("\n");
104
+
105
+ // Trim to fit the 16,000-char server-side limit, cutting the layers JSON if needed
106
+ if (full.length <= 16000) return full;
107
+
108
+ // Re-build with truncated layers JSON
109
+ const overhead =
110
+ [
111
+ header,
112
+ "",
113
+ `## Current Layers (${layers.length})`,
114
+ "",
115
+ "",
116
+ `## Available Component Types`,
117
+ registryDesc,
118
+ "",
119
+ `## ComponentLayer format`,
120
+ layerFormat,
121
+ ].join("\n").length + 30; // 30-char buffer for "...(truncated)"
122
+
123
+ const budget = Math.max(0, 16000 - overhead);
124
+ const truncatedLayers =
125
+ layersJson.length > budget
126
+ ? layersJson.slice(0, budget) + "\n...(truncated)"
127
+ : layersJson;
128
+
129
+ return [
130
+ header,
131
+ "",
132
+ `## Current Layers (${layers.length})`,
133
+ truncatedLayers,
134
+ "",
135
+ `## Available Component Types`,
136
+ registryDesc,
137
+ "",
138
+ `## ComponentLayer format`,
139
+ layerFormat,
140
+ ].join("\n");
141
+ }
142
+
42
143
  /**
43
144
  * Slugify a string for URL-friendly slugs
44
145
  */
@@ -139,6 +240,37 @@ function PageBuilderPageContent({
139
240
  // Auto-generate slug from first page name
140
241
  const [autoSlug, setAutoSlug] = useState(!id);
141
242
 
243
+ // Register AI context so the chat can update the page layout
244
+ useRegisterPageAIContext({
245
+ routeName: id ? "ui-builder-edit-page" : "ui-builder-new-page",
246
+ pageDescription: buildPageDescription(id, slug, layers, componentRegistry),
247
+ suggestions: [
248
+ "Add a hero section",
249
+ "Add a 3-column feature grid",
250
+ "Make the layout full-width",
251
+ "Add a card with a title, description, and button",
252
+ "Replace the layout with a centered single-column design",
253
+ ],
254
+ clientTools: {
255
+ updatePageLayers: async ({ layers: newLayers }) => {
256
+ // Drive the UIBuilder's Zustand store directly so the editor
257
+ // and layers panel update immediately. The store's onChange
258
+ // callback will propagate back to the parent's `layers` state.
259
+ const store = useLayerStore.getState();
260
+ store.initialize(
261
+ newLayers,
262
+ store.selectedPageId || newLayers[0]?.id,
263
+ undefined,
264
+ store.variables,
265
+ );
266
+ return {
267
+ success: true,
268
+ message: `Applied ${newLayers.length} layer(s) to the page`,
269
+ };
270
+ },
271
+ },
272
+ });
273
+
142
274
  // Handle layers change from UIBuilder
143
275
  const handleLayersChange = useCallback(
144
276
  (newLayers: ComponentLayer[]) => {
@@ -1,4 +1,23 @@
1
1
  import { createClient } from "better-call/client";
2
+
3
+ /**
4
+ * Returns true when a fetch error is a connection-refused / no-server error.
5
+ * Used in SSR loaders to emit an actionable build-time warning when
6
+ * `route.loader()` is called during `next build` with no HTTP server running.
7
+ */
8
+ export function isConnectionError(err: unknown): boolean {
9
+ if (!(err instanceof Error)) return false;
10
+ const code =
11
+ (err as unknown as { cause?: { code?: string } }).cause?.code ??
12
+ (err as unknown as { code?: string }).code;
13
+ return (
14
+ err.message.includes("ECONNREFUSED") ||
15
+ err.message.includes("fetch failed") ||
16
+ err.message.includes("ERR_CONNECTION_REFUSED") ||
17
+ code === "ECONNREFUSED" ||
18
+ code === "ERR_CONNECTION_REFUSED"
19
+ );
20
+ }
2
21
  import type { Router, Endpoint } from "better-call";
3
22
 
4
23
  interface CreateApiClientOptions {
@@ -35,12 +35,6 @@ interface SerializedTag extends Omit<Tag, "createdAt" | "updatedAt"> {
35
35
  }
36
36
 
37
37
  declare const createPostSchema: z.ZodObject<{
38
- title: z.ZodString;
39
- slug: z.ZodOptional<z.ZodString>;
40
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
41
- createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
- updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
43
- publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
44
38
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
45
39
  name: z.ZodString;
46
40
  }, z.core.$strip>, z.ZodObject<{
@@ -48,9 +42,15 @@ declare const createPostSchema: z.ZodObject<{
48
42
  name: z.ZodString;
49
43
  slug: z.ZodString;
50
44
  }, z.core.$strip>]>>>>;
45
+ title: z.ZodString;
46
+ slug: z.ZodOptional<z.ZodString>;
47
+ createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
48
+ updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
49
+ publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
51
50
  content: z.ZodString;
52
51
  excerpt: z.ZodString;
53
52
  image: z.ZodOptional<z.ZodString>;
53
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
54
54
  }, z.core.$strip>;
55
55
  declare const updatePostSchema: z.ZodObject<{
56
56
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
@@ -35,12 +35,6 @@ interface SerializedTag extends Omit<Tag, "createdAt" | "updatedAt"> {
35
35
  }
36
36
 
37
37
  declare const createPostSchema: z.ZodObject<{
38
- title: z.ZodString;
39
- slug: z.ZodOptional<z.ZodString>;
40
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
41
- createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
- updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
43
- publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
44
38
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
45
39
  name: z.ZodString;
46
40
  }, z.core.$strip>, z.ZodObject<{
@@ -48,9 +42,15 @@ declare const createPostSchema: z.ZodObject<{
48
42
  name: z.ZodString;
49
43
  slug: z.ZodString;
50
44
  }, z.core.$strip>]>>>>;
45
+ title: z.ZodString;
46
+ slug: z.ZodOptional<z.ZodString>;
47
+ createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
48
+ updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
49
+ publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
51
50
  content: z.ZodString;
52
51
  excerpt: z.ZodString;
53
52
  image: z.ZodOptional<z.ZodString>;
53
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
54
54
  }, z.core.$strip>;
55
55
  declare const updatePostSchema: z.ZodObject<{
56
56
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
@@ -35,12 +35,6 @@ interface SerializedTag extends Omit<Tag, "createdAt" | "updatedAt"> {
35
35
  }
36
36
 
37
37
  declare const createPostSchema: z.ZodObject<{
38
- title: z.ZodString;
39
- slug: z.ZodOptional<z.ZodString>;
40
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
41
- createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
- updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
43
- publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
44
38
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
45
39
  name: z.ZodString;
46
40
  }, z.core.$strip>, z.ZodObject<{
@@ -48,9 +42,15 @@ declare const createPostSchema: z.ZodObject<{
48
42
  name: z.ZodString;
49
43
  slug: z.ZodString;
50
44
  }, z.core.$strip>]>>>>;
45
+ title: z.ZodString;
46
+ slug: z.ZodOptional<z.ZodString>;
47
+ createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
48
+ updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
49
+ publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
51
50
  content: z.ZodString;
52
51
  excerpt: z.ZodString;
53
52
  image: z.ZodOptional<z.ZodString>;
53
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
54
54
  }, z.core.$strip>;
55
55
  declare const updatePostSchema: z.ZodObject<{
56
56
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;