@btst/stack 3.1.2 → 4.1.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 (129) hide show
  1. package/README.md +2 -2
  2. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +112 -103
  3. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +112 -103
  4. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +5 -2
  5. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +5 -2
  6. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +99 -88
  7. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +99 -88
  8. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.cjs +12 -3
  9. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.mjs +12 -3
  10. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.cjs +6 -2
  11. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.mjs +6 -2
  12. package/dist/packages/stack/src/plugins/ai-chat/client/hooks/chat-hooks.cjs +5 -5
  13. package/dist/packages/stack/src/plugins/ai-chat/client/hooks/chat-hooks.mjs +5 -5
  14. package/dist/packages/stack/src/plugins/blog/client/components/shared/page-wrapper.cjs +3 -2
  15. package/dist/packages/stack/src/plugins/blog/client/components/shared/page-wrapper.mjs +3 -2
  16. package/dist/packages/stack/src/plugins/blog/client/components/shared/post-card.cjs +81 -52
  17. package/dist/packages/stack/src/plugins/blog/client/components/shared/post-card.mjs +82 -53
  18. package/dist/packages/stack/src/plugins/blog/client/components/shared/posts-list.cjs +3 -2
  19. package/dist/packages/stack/src/plugins/blog/client/components/shared/posts-list.mjs +3 -2
  20. package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.internal.cjs +95 -75
  21. package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.internal.mjs +95 -75
  22. package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.internal.cjs +16 -3
  23. package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.internal.mjs +16 -3
  24. package/dist/packages/stack/src/plugins/cms/client/components/shared/page-wrapper.cjs +3 -2
  25. package/dist/packages/stack/src/plugins/cms/client/components/shared/page-wrapper.mjs +3 -2
  26. package/dist/packages/stack/src/plugins/cms/utils.cjs +6 -2
  27. package/dist/packages/stack/src/plugins/cms/utils.mjs +6 -3
  28. package/dist/packages/stack/src/plugins/form-builder/client/components/forms/form-renderer.cjs +14 -8
  29. package/dist/packages/stack/src/plugins/form-builder/client/components/forms/form-renderer.mjs +14 -8
  30. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.cjs +94 -87
  31. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.mjs +94 -87
  32. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +81 -74
  33. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +81 -74
  34. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/page-wrapper.cjs +3 -2
  35. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/page-wrapper.mjs +3 -2
  36. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/pagination.cjs +25 -16
  37. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/pagination.mjs +25 -16
  38. package/dist/packages/stack/src/plugins/form-builder/utils.cjs +6 -2
  39. package/dist/packages/stack/src/plugins/form-builder/utils.mjs +6 -3
  40. package/dist/packages/stack/src/plugins/kanban/api/getters.cjs +7 -5
  41. package/dist/packages/stack/src/plugins/kanban/api/getters.mjs +7 -5
  42. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.cjs +10 -8
  43. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.mjs +10 -8
  44. package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
  45. package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
  46. package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
  47. package/dist/plugins/ai-chat/client/index.d.cts +2 -2
  48. package/dist/plugins/ai-chat/client/index.d.mts +2 -2
  49. package/dist/plugins/ai-chat/client/index.d.ts +2 -2
  50. package/dist/plugins/blog/api/index.d.cts +2 -2
  51. package/dist/plugins/blog/api/index.d.mts +2 -2
  52. package/dist/plugins/blog/api/index.d.ts +2 -2
  53. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  54. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  55. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  56. package/dist/plugins/blog/client/index.d.cts +20 -2
  57. package/dist/plugins/blog/client/index.d.mts +20 -2
  58. package/dist/plugins/blog/client/index.d.ts +20 -2
  59. package/dist/plugins/blog/query-keys.d.cts +2 -2
  60. package/dist/plugins/blog/query-keys.d.mts +2 -2
  61. package/dist/plugins/blog/query-keys.d.ts +2 -2
  62. package/dist/plugins/cms/api/index.d.cts +1 -1
  63. package/dist/plugins/cms/api/index.d.mts +1 -1
  64. package/dist/plugins/cms/api/index.d.ts +1 -1
  65. package/dist/plugins/cms/client/index.d.cts +18 -0
  66. package/dist/plugins/cms/client/index.d.mts +18 -0
  67. package/dist/plugins/cms/client/index.d.ts +18 -0
  68. package/dist/plugins/cms/query-keys.d.cts +1 -1
  69. package/dist/plugins/cms/query-keys.d.mts +1 -1
  70. package/dist/plugins/cms/query-keys.d.ts +1 -1
  71. package/dist/plugins/form-builder/client/index.d.cts +16 -0
  72. package/dist/plugins/form-builder/client/index.d.mts +16 -0
  73. package/dist/plugins/form-builder/client/index.d.ts +16 -0
  74. package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
  75. package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
  76. package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
  77. package/dist/plugins/ui-builder/client/index.d.cts +1 -1
  78. package/dist/plugins/ui-builder/client/index.d.mts +1 -1
  79. package/dist/plugins/ui-builder/client/index.d.ts +1 -1
  80. package/dist/plugins/ui-builder/query-keys.d.cts +1 -1
  81. package/dist/plugins/ui-builder/query-keys.d.mts +1 -1
  82. package/dist/plugins/ui-builder/query-keys.d.ts +1 -1
  83. package/dist/shared/{stack.BTStHVTy.d.cts → stack.BGzgFx3R.d.cts} +5 -5
  84. package/dist/shared/{stack.CtrnoQgH.d.cts → stack.BOZRaVpu.d.cts} +35 -35
  85. package/dist/shared/{stack.94kN3pEK.d.mts → stack.CjCSDfWy.d.mts} +5 -5
  86. package/dist/shared/{stack.lPKwd_v0.d.ts → stack.DlBoNqYu.d.ts} +35 -35
  87. package/dist/shared/{stack.DfrRYOIl.d.ts → stack.Dpg_JjIo.d.ts} +5 -5
  88. package/dist/shared/{stack.BggTHKlo.d.mts → stack.Dvy7FmaA.d.mts} +35 -35
  89. package/dist/shared/{stack.5_zzCejP.d.cts → stack.rVF0f509.d.cts} +20 -0
  90. package/dist/shared/{stack.5_zzCejP.d.mts → stack.rVF0f509.d.mts} +20 -0
  91. package/dist/shared/{stack.5_zzCejP.d.ts → stack.rVF0f509.d.ts} +20 -0
  92. package/package.json +11 -7
  93. package/src/__tests__/package-metadata.test.ts +4 -4
  94. package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +248 -0
  95. package/src/plugins/ai-chat/__tests__/hooks.test.tsx +70 -54
  96. package/src/plugins/ai-chat/client/components/chat-input.tsx +6 -1
  97. package/src/plugins/ai-chat/client/components/chat-interface.tsx +3 -0
  98. package/src/plugins/ai-chat/client/components/chat-layout.tsx +9 -2
  99. package/src/plugins/ai-chat/client/components/chat-message.tsx +13 -5
  100. package/src/plugins/ai-chat/client/components/chat-sidebar.tsx +6 -2
  101. package/src/plugins/ai-chat/client/hooks/chat-hooks.tsx +6 -9
  102. package/src/plugins/ai-chat/client/overrides.ts +21 -0
  103. package/src/plugins/blog/__tests__/classnames-overrides.test.tsx +206 -0
  104. package/src/plugins/blog/client/components/shared/page-wrapper.tsx +3 -2
  105. package/src/plugins/blog/client/components/shared/post-card.tsx +32 -12
  106. package/src/plugins/blog/client/components/shared/posts-list.tsx +3 -2
  107. package/src/plugins/blog/client/overrides.ts +18 -0
  108. package/src/plugins/cms/__tests__/classnames-overrides.test.tsx +200 -0
  109. package/src/plugins/cms/client/components/pages/content-list-page.internal.tsx +17 -4
  110. package/src/plugins/cms/client/components/pages/dashboard-page.internal.tsx +14 -3
  111. package/src/plugins/cms/client/components/shared/page-wrapper.tsx +3 -2
  112. package/src/plugins/cms/client/overrides.ts +19 -0
  113. package/src/plugins/form-builder/__tests__/classnames-overrides.test.tsx +312 -0
  114. package/src/plugins/form-builder/client/components/forms/form-renderer.tsx +14 -9
  115. package/src/plugins/form-builder/client/components/pages/form-list-page.internal.tsx +9 -5
  116. package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +9 -5
  117. package/src/plugins/form-builder/client/components/shared/page-wrapper.tsx +3 -2
  118. package/src/plugins/form-builder/client/components/shared/pagination.tsx +7 -1
  119. package/src/plugins/form-builder/client/overrides.ts +17 -0
  120. package/src/plugins/kanban/__tests__/getters.test.ts +14 -1
  121. package/src/plugins/kanban/api/getters.ts +9 -7
  122. package/src/plugins/kanban/types.ts +4 -6
  123. package/src/plugins/media/client/hooks/use-media.tsx +7 -8
  124. package/dist/shared/{stack.lsbdKRj7.d.mts → stack.B8PG21zX.d.mts} +16 -16
  125. package/dist/shared/{stack.CKc_Bais.d.cts → stack.Csi-4Mgz.d.cts} +7 -7
  126. package/dist/shared/{stack.CKc_Bais.d.mts → stack.Csi-4Mgz.d.mts} +7 -7
  127. package/dist/shared/{stack.CKc_Bais.d.ts → stack.Csi-4Mgz.d.ts} +7 -7
  128. package/dist/shared/{stack.BMlTZnZp.d.ts → stack.CyalJ0j6.d.ts} +16 -16
  129. package/dist/shared/{stack.BV0uNV4f.d.cts → stack.D6Pg-ufX.d.cts} +16 -16
@@ -2,7 +2,7 @@ import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
2
  import { createApiClient } from '@btst/stack/plugins/client';
3
3
  import * as zod from 'zod';
4
4
  import { z } from 'zod';
5
- import { c as createPostSchema, u as updatePostSchema } from './stack.CKc_Bais.mjs';
5
+ import { c as createPostSchema, u as updatePostSchema } from './stack.Csi-4Mgz.mjs';
6
6
  import { P as Post, T as Tag, S as SerializedPost, a as SerializedTag } from './stack.Dsatjh0B.mjs';
7
7
  import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
8
8
  import { DeepReadonly, OperationContext, OperationErrorContext } from '@btst/stack/plugins/api';
@@ -89,12 +89,6 @@ declare const NextPreviousPostsQuerySchema: z.ZodObject<{
89
89
  date: z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>;
90
90
  }, z.core.$strip>;
91
91
  declare const CreatePostOperationInputSchema: z.ZodObject<{
92
- slug: z.ZodOptional<z.ZodString>;
93
- title: z.ZodString;
94
- content: z.ZodString;
95
- excerpt: z.ZodString;
96
- image: z.ZodOptional<z.ZodString>;
97
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
98
92
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
99
93
  name: z.ZodString;
100
94
  }, z.core.$strip>, z.ZodObject<{
@@ -102,6 +96,12 @@ declare const CreatePostOperationInputSchema: z.ZodObject<{
102
96
  name: z.ZodString;
103
97
  slug: z.ZodString;
104
98
  }, z.core.$strip>]>>>>;
99
+ slug: z.ZodOptional<z.ZodString>;
100
+ title: z.ZodString;
101
+ content: z.ZodString;
102
+ excerpt: z.ZodString;
103
+ image: z.ZodOptional<z.ZodString>;
104
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
105
105
  publishedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
106
106
  createdAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
107
107
  updatedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
@@ -109,12 +109,6 @@ declare const CreatePostOperationInputSchema: z.ZodObject<{
109
109
  declare const UpdatePostOperationInputSchema: z.ZodObject<{
110
110
  id: z.ZodString;
111
111
  data: z.ZodObject<{
112
- slug: z.ZodString;
113
- title: z.ZodString;
114
- content: z.ZodString;
115
- excerpt: z.ZodString;
116
- image: z.ZodOptional<z.ZodString>;
117
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
118
112
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
119
113
  name: z.ZodString;
120
114
  }, z.core.$strip>, z.ZodObject<{
@@ -122,6 +116,12 @@ declare const UpdatePostOperationInputSchema: z.ZodObject<{
122
116
  name: z.ZodString;
123
117
  slug: z.ZodString;
124
118
  }, z.core.$strip>]>>>>;
119
+ slug: z.ZodString;
120
+ title: z.ZodString;
121
+ content: z.ZodString;
122
+ excerpt: z.ZodString;
123
+ image: z.ZodOptional<z.ZodString>;
124
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
125
125
  publishedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
126
126
  createdAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
127
127
  updatedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
@@ -386,9 +386,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
386
386
  title: string;
387
387
  content: string;
388
388
  excerpt: string;
389
- slug?: string | undefined;
390
- image?: string | undefined;
391
- published?: boolean | undefined;
392
389
  tags?: ({
393
390
  name: string;
394
391
  } | {
@@ -396,6 +393,9 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
396
393
  name: string;
397
394
  slug: string;
398
395
  })[] | undefined;
396
+ slug?: string | undefined;
397
+ image?: string | undefined;
398
+ published?: boolean | undefined;
399
399
  publishedAt?: unknown;
400
400
  createdAt?: unknown;
401
401
  updatedAt?: unknown;
@@ -403,9 +403,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
403
403
  title: string;
404
404
  content: string;
405
405
  excerpt: string;
406
- slug?: string | undefined;
407
- image?: string | undefined;
408
- published?: boolean | undefined;
409
406
  tags?: ({
410
407
  name: string;
411
408
  } | {
@@ -413,6 +410,9 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
413
410
  name: string;
414
411
  slug: string;
415
412
  })[] | undefined;
413
+ slug?: string | undefined;
414
+ image?: string | undefined;
415
+ published?: boolean | undefined;
416
416
  publishedAt?: unknown;
417
417
  createdAt?: unknown;
418
418
  updatedAt?: unknown;
@@ -448,8 +448,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
448
448
  title: string;
449
449
  content: string;
450
450
  excerpt: string;
451
- image?: string | undefined;
452
- published?: boolean | undefined;
453
451
  tags?: ({
454
452
  name: string;
455
453
  } | {
@@ -457,6 +455,8 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
457
455
  name: string;
458
456
  slug: string;
459
457
  })[] | undefined;
458
+ image?: string | undefined;
459
+ published?: boolean | undefined;
460
460
  publishedAt?: unknown;
461
461
  createdAt?: unknown;
462
462
  updatedAt?: unknown;
@@ -465,8 +465,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
465
465
  title: string;
466
466
  content: string;
467
467
  excerpt: string;
468
- image?: string | undefined;
469
- published?: boolean | undefined;
470
468
  tags?: ({
471
469
  name: string;
472
470
  } | {
@@ -474,6 +472,8 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
474
472
  name: string;
475
473
  slug: string;
476
474
  })[] | undefined;
475
+ image?: string | undefined;
476
+ published?: boolean | undefined;
477
477
  publishedAt?: unknown;
478
478
  createdAt?: unknown;
479
479
  updatedAt?: unknown;
@@ -627,12 +627,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
627
627
  total: number;
628
628
  }, false>;
629
629
  readonly createPost: _btst_stack_plugins_api.Operation<zod.ZodObject<{
630
- slug: zod.ZodOptional<zod.ZodString>;
631
- title: zod.ZodString;
632
- content: zod.ZodString;
633
- excerpt: zod.ZodString;
634
- image: zod.ZodOptional<zod.ZodString>;
635
- published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
636
630
  tags: zod.ZodDefault<zod.ZodOptional<zod.ZodArray<zod.ZodUnion<readonly [zod.ZodObject<{
637
631
  name: zod.ZodString;
638
632
  }, zod_v4_core.$strip>, zod.ZodObject<{
@@ -640,6 +634,12 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
640
634
  name: zod.ZodString;
641
635
  slug: zod.ZodString;
642
636
  }, zod_v4_core.$strip>]>>>>;
637
+ slug: zod.ZodOptional<zod.ZodString>;
638
+ title: zod.ZodString;
639
+ content: zod.ZodString;
640
+ excerpt: zod.ZodString;
641
+ image: zod.ZodOptional<zod.ZodString>;
642
+ published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
643
643
  publishedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
644
644
  createdAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
645
645
  updatedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
@@ -673,12 +673,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
673
673
  readonly updatePost: _btst_stack_plugins_api.Operation<zod.ZodObject<{
674
674
  id: zod.ZodString;
675
675
  data: zod.ZodObject<{
676
- slug: zod.ZodString;
677
- title: zod.ZodString;
678
- content: zod.ZodString;
679
- excerpt: zod.ZodString;
680
- image: zod.ZodOptional<zod.ZodString>;
681
- published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
682
676
  tags: zod.ZodDefault<zod.ZodOptional<zod.ZodArray<zod.ZodUnion<readonly [zod.ZodObject<{
683
677
  name: zod.ZodString;
684
678
  }, zod_v4_core.$strip>, zod.ZodObject<{
@@ -686,6 +680,12 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
686
680
  name: zod.ZodString;
687
681
  slug: zod.ZodString;
688
682
  }, zod_v4_core.$strip>]>>>>;
683
+ slug: zod.ZodString;
684
+ title: zod.ZodString;
685
+ content: zod.ZodString;
686
+ excerpt: zod.ZodString;
687
+ image: zod.ZodOptional<zod.ZodString>;
688
+ published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
689
689
  publishedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
690
690
  createdAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
691
691
  updatedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
@@ -204,6 +204,26 @@ interface AiChatPluginOverrides {
204
204
  * ```
205
205
  */
206
206
  toolRenderers?: Record<string, ToolCallRenderer>;
207
+ /**
208
+ * Optional class names merged onto specific UI elements.
209
+ * Use these for targeted styling without replacing whole components.
210
+ */
211
+ classNames?: {
212
+ /** Root of `ChatLayout` (full-page or widget mode) */
213
+ container?: string;
214
+ /** Conversation sidebar root */
215
+ sidebar?: string;
216
+ /** Wrapper around the list of messages */
217
+ messageList?: string;
218
+ /** Each message row (user and assistant) */
219
+ message?: string;
220
+ /** The bubble of a user message */
221
+ userMessage?: string;
222
+ /** The bubble of an assistant message */
223
+ assistantMessage?: string;
224
+ /** The composer form at the bottom of the chat */
225
+ input?: string;
226
+ };
207
227
  /**
208
228
  * Called when a route is rendered
209
229
  * @param routeName - Name of the route (e.g., 'chat', 'chatConversation')
@@ -204,6 +204,26 @@ interface AiChatPluginOverrides {
204
204
  * ```
205
205
  */
206
206
  toolRenderers?: Record<string, ToolCallRenderer>;
207
+ /**
208
+ * Optional class names merged onto specific UI elements.
209
+ * Use these for targeted styling without replacing whole components.
210
+ */
211
+ classNames?: {
212
+ /** Root of `ChatLayout` (full-page or widget mode) */
213
+ container?: string;
214
+ /** Conversation sidebar root */
215
+ sidebar?: string;
216
+ /** Wrapper around the list of messages */
217
+ messageList?: string;
218
+ /** Each message row (user and assistant) */
219
+ message?: string;
220
+ /** The bubble of a user message */
221
+ userMessage?: string;
222
+ /** The bubble of an assistant message */
223
+ assistantMessage?: string;
224
+ /** The composer form at the bottom of the chat */
225
+ input?: string;
226
+ };
207
227
  /**
208
228
  * Called when a route is rendered
209
229
  * @param routeName - Name of the route (e.g., 'chat', 'chatConversation')
@@ -204,6 +204,26 @@ interface AiChatPluginOverrides {
204
204
  * ```
205
205
  */
206
206
  toolRenderers?: Record<string, ToolCallRenderer>;
207
+ /**
208
+ * Optional class names merged onto specific UI elements.
209
+ * Use these for targeted styling without replacing whole components.
210
+ */
211
+ classNames?: {
212
+ /** Root of `ChatLayout` (full-page or widget mode) */
213
+ container?: string;
214
+ /** Conversation sidebar root */
215
+ sidebar?: string;
216
+ /** Wrapper around the list of messages */
217
+ messageList?: string;
218
+ /** Each message row (user and assistant) */
219
+ message?: string;
220
+ /** The bubble of a user message */
221
+ userMessage?: string;
222
+ /** The bubble of an assistant message */
223
+ assistantMessage?: string;
224
+ /** The composer form at the bottom of the chat */
225
+ input?: string;
226
+ };
207
227
  /**
208
228
  * Called when a route is rendered
209
229
  * @param routeName - Name of the route (e.g., 'chat', 'chatConversation')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btst/stack",
3
- "version": "3.1.2",
3
+ "version": "4.1.0",
4
4
  "description": "A composable, plugin-based library for building full-stack applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1067,7 +1067,7 @@
1067
1067
  }
1068
1068
  },
1069
1069
  "dependencies": {
1070
- "@btst/db": "2.2.3",
1070
+ "@btst/db": "3.0.0",
1071
1071
  "@milkdown/crepe": "^7.17.1",
1072
1072
  "@milkdown/kit": "^7.17.1",
1073
1073
  "remend": "^1.0.1",
@@ -1088,7 +1088,7 @@
1088
1088
  "@tanstack/react-router": ">=1.0.0",
1089
1089
  "@vercel/blob": ">=0.14.0",
1090
1090
  "ai": ">=5.0.0",
1091
- "better-call": "1.3.6",
1091
+ "better-call": "1.4.0",
1092
1092
  "class-variance-authority": ">=0.7.0",
1093
1093
  "clsx": ">=2.1.0",
1094
1094
  "cmdk": ">=1.1.0",
@@ -1097,8 +1097,8 @@
1097
1097
  "katex": ">=0.16.0",
1098
1098
  "lucide-react": ">=0.469.0",
1099
1099
  "next": ">=15.0.0",
1100
- "react": "^18.0.0 || ^19.0.0",
1101
- "react-dom": "^18.0.0 || ^19.0.0",
1100
+ "react": ">=19.2.6",
1101
+ "react-dom": ">=19.2.6",
1102
1102
  "react-error-boundary": ">=4.0.0",
1103
1103
  "react-hook-form": ">=7.55.0",
1104
1104
  "react-markdown": ">=9.1.0",
@@ -1110,7 +1110,7 @@
1110
1110
  "remark-math": ">=6.0.0",
1111
1111
  "sonner": ">=2.0.0",
1112
1112
  "tailwind-merge": ">=2.6.0",
1113
- "tailwindcss": ">=3.0.0",
1113
+ "tailwindcss": ">=4.3.2",
1114
1114
  "zod": ">=4.2.0"
1115
1115
  },
1116
1116
  "peerDependenciesMeta": {
@@ -1137,7 +1137,10 @@
1137
1137
  "@ai-sdk/react": "^2.0.94",
1138
1138
  "@aws-sdk/client-s3": "^3.1011.0",
1139
1139
  "@aws-sdk/s3-request-presigner": "^3.1011.0",
1140
- "@btst/adapter-memory": "2.2.3",
1140
+ "@better-auth/core": "1.7.6",
1141
+ "@better-auth/utils": "0.4.2",
1142
+ "@better-fetch/fetch": "1.3.2",
1143
+ "@btst/adapter-memory": "3.0.0",
1141
1144
  "@btst/yar": "1.3.2",
1142
1145
  "@tanstack/react-router": "1.168.10",
1143
1146
  "@types/react": "^19.0.0",
@@ -1146,6 +1149,7 @@
1146
1149
  "@vercel/blob": "^0.27.3",
1147
1150
  "@workspace/ui": "workspace:*",
1148
1151
  "ai": "^5.0.94",
1152
+ "better-auth": "1.7.6",
1149
1153
  "better-call": "catalog:",
1150
1154
  "jsdom": "^28.1.0",
1151
1155
  "knip": "^5.61.2",
@@ -11,7 +11,7 @@ import { z } from "zod";
11
11
  const execFileAsync = promisify(execFile);
12
12
 
13
13
  describe("published dependency alignment", () => {
14
- it("uses the Better DB release that shares the Better Auth 1.6.16 cohort", async () => {
14
+ it("uses the Better DB release that shares the Better Auth 1.7.6 cohort", async () => {
15
15
  const manifest = JSON.parse(
16
16
  await readFile(resolve("package.json"), "utf8"),
17
17
  ) as {
@@ -20,11 +20,11 @@ describe("published dependency alignment", () => {
20
20
  peerDependencies?: Record<string, string>;
21
21
  };
22
22
 
23
- expect(manifest.dependencies?.["@btst/db"]).toBe("2.2.3");
24
- expect(manifest.devDependencies?.["@btst/adapter-memory"]).toBe("2.2.3");
23
+ expect(manifest.dependencies?.["@btst/db"]).toBe("3.0.0");
24
+ expect(manifest.devDependencies?.["@btst/adapter-memory"]).toBe("3.0.0");
25
25
  expect(manifest.devDependencies?.["@btst/yar"]).toBe("1.3.2");
26
26
  expect(manifest.peerDependencies?.["@btst/yar"]).toBe(">=1.3.2");
27
- expect(manifest.peerDependencies?.["better-call"]).toBe("1.3.6");
27
+ expect(manifest.peerDependencies?.["better-call"]).toBe("1.4.0");
28
28
  });
29
29
  });
30
30
 
@@ -0,0 +1,248 @@
1
+ // @vitest-environment jsdom
2
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3
+ import { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+ // Core primitives MUST be imported from the package entry (not relative src
7
+ // paths) so they share module identity — and React context — with the plugin
8
+ // components, which resolve `@btst/stack/*` via package self-reference.
9
+ import { StackProvider } from "@btst/stack/context";
10
+ import type { UIMessage } from "ai";
11
+ import { ChatInput } from "../client/components/chat-input";
12
+ import { ChatLayout } from "../client/components/chat-layout";
13
+ import { ChatMessage } from "../client/components/chat-message";
14
+ import { aiChatClientPlugin } from "../client/plugin";
15
+ import type { SerializedConversation } from "../types";
16
+ import { createTestClientStack } from "../../../__tests__/client-stack-test-utils";
17
+
18
+ (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
19
+ (globalThis as any).ResizeObserver ??= class {
20
+ observe() {}
21
+ unobserve() {}
22
+ disconnect() {}
23
+ };
24
+ Element.prototype.scrollIntoView ??= () => {};
25
+
26
+ const mocks = vi.hoisted(() => ({
27
+ useChat: vi.fn(),
28
+ useConversation: vi.fn(),
29
+ useConversations: vi.fn(),
30
+ useRenameConversationForm: vi.fn(),
31
+ useDeleteConversation: vi.fn(),
32
+ useAiChatIdentityPartition: vi.fn(),
33
+ }));
34
+
35
+ vi.mock("@ai-sdk/react", () => ({ useChat: mocks.useChat }));
36
+ vi.mock("../client/hooks/chat-hooks", () => ({
37
+ useAiChatIdentityPartition: mocks.useAiChatIdentityPartition,
38
+ useConversation: mocks.useConversation,
39
+ useConversations: mocks.useConversations,
40
+ useRenameConversationForm: mocks.useRenameConversationForm,
41
+ useDeleteConversation: mocks.useDeleteConversation,
42
+ }));
43
+ vi.mock("../client/context/page-ai-context", () => ({
44
+ usePageAIContext: () => undefined,
45
+ }));
46
+ // Markdown rendering is unrelated to class wiring and drags in highlight/katex.
47
+ vi.mock("@workspace/ui/components/markdown-content", () => ({
48
+ MarkdownContent: ({ content }: { content: string }) => <div>{content}</div>,
49
+ }));
50
+
51
+ const CLASS_NAMES = {
52
+ container: "bg-red-100",
53
+ sidebar: "bg-blue-100",
54
+ messageList: "bg-yellow-50",
55
+ message: "text-purple-700",
56
+ userMessage: "border-4 border-orange-500",
57
+ assistantMessage: "border-4 border-green-500",
58
+ input: "bg-pink-100",
59
+ } as const;
60
+
61
+ const conversation: SerializedConversation = {
62
+ id: "conv-1",
63
+ userId: "owner-1",
64
+ title: "First conversation",
65
+ createdAt: new Date("2024-01-01").toISOString(),
66
+ updatedAt: new Date("2024-01-02").toISOString(),
67
+ };
68
+
69
+ const userMessage: UIMessage = {
70
+ id: "user-1",
71
+ role: "user",
72
+ parts: [{ type: "text", text: "Hi there" }],
73
+ };
74
+ const assistantMessage: UIMessage = {
75
+ id: "assistant-1",
76
+ role: "assistant",
77
+ parts: [{ type: "text", text: "Hello back" }],
78
+ };
79
+
80
+ let container: HTMLDivElement;
81
+ let root: Root;
82
+ let queryClient: QueryClient;
83
+
84
+ beforeEach(() => {
85
+ container = document.createElement("div");
86
+ document.body.appendChild(container);
87
+ root = createRoot(container);
88
+ queryClient = new QueryClient({
89
+ defaultOptions: { queries: { retry: false } },
90
+ });
91
+
92
+ mocks.useAiChatIdentityPartition.mockReturnValue("anonymous");
93
+ mocks.useChat.mockReturnValue({
94
+ messages: [userMessage, assistantMessage],
95
+ sendMessage: vi.fn(),
96
+ status: "ready",
97
+ error: null,
98
+ setMessages: vi.fn(),
99
+ regenerate: vi.fn(),
100
+ addToolOutput: vi.fn(),
101
+ stop: vi.fn(),
102
+ });
103
+ mocks.useConversation.mockReturnValue({
104
+ conversation: null,
105
+ isLoading: false,
106
+ error: null,
107
+ refetch: vi.fn(),
108
+ });
109
+ mocks.useConversations.mockReturnValue({
110
+ conversations: [conversation],
111
+ isLoading: false,
112
+ error: null,
113
+ refetch: vi.fn(),
114
+ });
115
+ mocks.useRenameConversationForm.mockReturnValue({
116
+ submit: vi.fn(),
117
+ isSubmitting: false,
118
+ error: null,
119
+ fieldErrors: {},
120
+ clearErrors: vi.fn(),
121
+ });
122
+ mocks.useDeleteConversation.mockReturnValue({
123
+ mutateAsync: vi.fn(),
124
+ isPending: false,
125
+ });
126
+ });
127
+
128
+ afterEach(async () => {
129
+ await act(async () => {
130
+ root.unmount();
131
+ });
132
+ container.remove();
133
+ vi.clearAllMocks();
134
+ });
135
+
136
+ async function render(ui: React.ReactElement) {
137
+ await act(async () => {
138
+ root.render(
139
+ <QueryClientProvider client={queryClient}>
140
+ <StackProvider
141
+ stack={createTestClientStack(
142
+ { aiChat: aiChatClientPlugin() },
143
+ queryClient,
144
+ )}
145
+ overrides={{ aiChat: { classNames: CLASS_NAMES } }}
146
+ >
147
+ {ui}
148
+ </StackProvider>
149
+ </QueryClientProvider>,
150
+ );
151
+ });
152
+ }
153
+
154
+ function query(selector: string): HTMLElement {
155
+ const el = container.querySelector<HTMLElement>(selector);
156
+ expect(el, `expected to find ${selector}`).toBeTruthy();
157
+ return el!;
158
+ }
159
+
160
+ describe("ai-chat classNames overrides (issue #36)", () => {
161
+ it("applies message, userMessage and assistantMessage to a user message", async () => {
162
+ await render(<ChatMessage message={userMessage} />);
163
+
164
+ const row = query('[data-testid="chat-message"][data-role="user"]');
165
+ expect(row.className).toContain(CLASS_NAMES.message);
166
+ expect(row.className).toContain("justify-end");
167
+
168
+ const bubble = query('[data-testid="chat-message-bubble"]');
169
+ expect(bubble.className).toContain("border-orange-500");
170
+ expect(bubble.className).not.toContain("border-green-500");
171
+ // The role-specific base styling is preserved.
172
+ expect(bubble.className).toContain("bg-primary");
173
+ });
174
+
175
+ it("applies assistantMessage (not userMessage) to an assistant message", async () => {
176
+ await render(<ChatMessage message={assistantMessage} />);
177
+
178
+ const row = query('[data-testid="chat-message"][data-role="assistant"]');
179
+ expect(row.className).toContain(CLASS_NAMES.message);
180
+ expect(row.className).toContain("justify-start");
181
+
182
+ const bubble = query('[data-testid="chat-message-bubble"]');
183
+ expect(bubble.className).toContain("border-green-500");
184
+ expect(bubble.className).not.toContain("border-orange-500");
185
+ expect(bubble.className).toContain("bg-muted");
186
+ });
187
+
188
+ it("applies classNames.input to the composer form", async () => {
189
+ await render(
190
+ <ChatInput
191
+ handleInputChange={() => {}}
192
+ handleSubmit={() => {}}
193
+ isLoading={false}
194
+ />,
195
+ );
196
+
197
+ const form = query('[data-testid="chat-input-form"]');
198
+ expect(form.tagName).toBe("FORM");
199
+ expect(form.className).toContain(CLASS_NAMES.input);
200
+ expect(form.className).toContain("space-y-2");
201
+ });
202
+
203
+ it("applies container, sidebar and messageList in the full layout", async () => {
204
+ await render(<ChatLayout conversationId={conversation.id} />);
205
+
206
+ const layout = query('[data-testid="chat-layout"]');
207
+ expect(layout.className).toContain(CLASS_NAMES.container);
208
+ expect(layout.className).toContain("overflow-hidden");
209
+
210
+ const sidebar = query('[data-testid="chat-sidebar"]');
211
+ expect(sidebar.className).toContain(CLASS_NAMES.sidebar);
212
+ expect(sidebar.className).toContain("border-r");
213
+
214
+ const list = query('[data-testid="chat-message-list"]');
215
+ expect(list.className).toContain(CLASS_NAMES.messageList);
216
+ expect(list.className).toContain("flex-col");
217
+ });
218
+
219
+ it("applies classNames.container to the widget root", async () => {
220
+ await render(<ChatLayout layout="widget" />);
221
+
222
+ const trigger = query('[data-testid="widget-trigger"]');
223
+ const widgetRoot = trigger.parentElement!;
224
+ expect(widgetRoot.className).toContain(CLASS_NAMES.container);
225
+ expect(widgetRoot.className).toContain("items-end");
226
+ });
227
+
228
+ it("leaves base classes untouched when no classNames override is supplied", async () => {
229
+ await act(async () => {
230
+ root.render(
231
+ <QueryClientProvider client={queryClient}>
232
+ <StackProvider
233
+ stack={createTestClientStack(
234
+ { aiChat: aiChatClientPlugin() },
235
+ queryClient,
236
+ )}
237
+ >
238
+ <ChatMessage message={userMessage} />
239
+ </StackProvider>
240
+ </QueryClientProvider>,
241
+ );
242
+ });
243
+
244
+ const bubble = query('[data-testid="chat-message-bubble"]');
245
+ expect(bubble.className).toContain("bg-primary");
246
+ expect(bubble.className).not.toContain("border-orange-500");
247
+ });
248
+ });