@actuate-media/cms-admin 0.56.0 → 0.57.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 (271) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/__tests__/components/posts-badges.test.js +3 -3
  3. package/dist/__tests__/components/posts-badges.test.js.map +1 -1
  4. package/dist/__tests__/views/ai-settings.render.test.js +34 -0
  5. package/dist/__tests__/views/ai-settings.render.test.js.map +1 -1
  6. package/dist/actuate-admin.css +1 -1
  7. package/dist/components/AICoauthorBubbleMenu.d.ts.map +1 -1
  8. package/dist/components/AICoauthorBubbleMenu.js +7 -3
  9. package/dist/components/AICoauthorBubbleMenu.js.map +1 -1
  10. package/dist/components/BrandVoiceAlignmentBadge.d.ts +6 -0
  11. package/dist/components/BrandVoiceAlignmentBadge.d.ts.map +1 -0
  12. package/dist/components/BrandVoiceAlignmentBadge.js +11 -0
  13. package/dist/components/BrandVoiceAlignmentBadge.js.map +1 -0
  14. package/dist/components/Breadcrumbs.js +2 -2
  15. package/dist/components/Breadcrumbs.js.map +1 -1
  16. package/dist/components/CoauthorResultPopover.d.ts +5 -1
  17. package/dist/components/CoauthorResultPopover.d.ts.map +1 -1
  18. package/dist/components/CoauthorResultPopover.js +35 -6
  19. package/dist/components/CoauthorResultPopover.js.map +1 -1
  20. package/dist/components/CommandPalette.d.ts.map +1 -1
  21. package/dist/components/CommandPalette.js +16 -12
  22. package/dist/components/CommandPalette.js.map +1 -1
  23. package/dist/components/CommentSidePanel.js +11 -11
  24. package/dist/components/CommentSidePanel.js.map +1 -1
  25. package/dist/components/ContentOverviewChart.js +2 -2
  26. package/dist/components/ContentOverviewChart.js.map +1 -1
  27. package/dist/components/FocalPointPicker.js +1 -1
  28. package/dist/components/FocalPointPicker.js.map +1 -1
  29. package/dist/components/LivePreview.d.ts.map +1 -1
  30. package/dist/components/LivePreview.js +5 -5
  31. package/dist/components/LivePreview.js.map +1 -1
  32. package/dist/components/LogoPlaceholder.js +1 -1
  33. package/dist/components/LogoPlaceholder.js.map +1 -1
  34. package/dist/components/MentionableTextarea.js +3 -3
  35. package/dist/components/MentionableTextarea.js.map +1 -1
  36. package/dist/components/NotificationBell.js +1 -1
  37. package/dist/components/NotificationBell.js.map +1 -1
  38. package/dist/components/OfflineStatus.js +8 -8
  39. package/dist/components/OfflineStatus.js.map +1 -1
  40. package/dist/components/PresenceChips.d.ts.map +1 -1
  41. package/dist/components/PresenceChips.js +10 -6
  42. package/dist/components/PresenceChips.js.map +1 -1
  43. package/dist/components/SEOConfigPanel.d.ts.map +1 -1
  44. package/dist/components/SEOConfigPanel.js +10 -10
  45. package/dist/components/SEOConfigPanel.js.map +1 -1
  46. package/dist/components/SEOPanel.js +12 -12
  47. package/dist/components/SEOPanel.js.map +1 -1
  48. package/dist/components/SEOPerformance.d.ts.map +1 -1
  49. package/dist/components/SEOPerformance.js +3 -3
  50. package/dist/components/SEOPerformance.js.map +1 -1
  51. package/dist/components/TipTapEditor.js +6 -6
  52. package/dist/components/TipTapEditor.js.map +1 -1
  53. package/dist/components/pages/tag-badges.js +6 -6
  54. package/dist/components/pages/tag-badges.js.map +1 -1
  55. package/dist/components/posts/badges.d.ts +0 -4
  56. package/dist/components/posts/badges.d.ts.map +1 -1
  57. package/dist/components/posts/badges.js +28 -42
  58. package/dist/components/posts/badges.js.map +1 -1
  59. package/dist/components/seo/SeoEditorDrawer.d.ts.map +1 -1
  60. package/dist/components/seo/SeoEditorDrawer.js +43 -2
  61. package/dist/components/seo/SeoEditorDrawer.js.map +1 -1
  62. package/dist/components/ui/Badge.d.ts +1 -1
  63. package/dist/components/ui/Badge.js +5 -5
  64. package/dist/components/ui/Badge.js.map +1 -1
  65. package/dist/components/ui/Toast.d.ts.map +1 -1
  66. package/dist/components/ui/Toast.js +4 -4
  67. package/dist/components/ui/Toast.js.map +1 -1
  68. package/dist/fields/ComponentBlockField.js +2 -2
  69. package/dist/fields/ComponentBlockField.js.map +1 -1
  70. package/dist/fields/FieldRenderer.js +1 -1
  71. package/dist/fields/FieldRenderer.js.map +1 -1
  72. package/dist/fields/PropInput.js +1 -1
  73. package/dist/fields/PropInput.js.map +1 -1
  74. package/dist/fields/RelationshipField.js +5 -5
  75. package/dist/fields/RelationshipField.js.map +1 -1
  76. package/dist/fields/ToggleField.js +1 -1
  77. package/dist/fields/ToggleField.js.map +1 -1
  78. package/dist/lib/brand-voice-client.d.ts +21 -0
  79. package/dist/lib/brand-voice-client.d.ts.map +1 -0
  80. package/dist/lib/brand-voice-client.js +27 -0
  81. package/dist/lib/brand-voice-client.js.map +1 -0
  82. package/dist/lib/coauthor-client.d.ts +2 -0
  83. package/dist/lib/coauthor-client.d.ts.map +1 -1
  84. package/dist/lib/coauthor-client.js +1 -0
  85. package/dist/lib/coauthor-client.js.map +1 -1
  86. package/dist/lib/seo-service.d.ts +12 -0
  87. package/dist/lib/seo-service.d.ts.map +1 -1
  88. package/dist/lib/seo-service.js +0 -10
  89. package/dist/lib/seo-service.js.map +1 -1
  90. package/dist/views/Dashboard.js +5 -5
  91. package/dist/views/Dashboard.js.map +1 -1
  92. package/dist/views/DocumentEdit.d.ts.map +1 -1
  93. package/dist/views/DocumentEdit.js +5 -5
  94. package/dist/views/DocumentEdit.js.map +1 -1
  95. package/dist/views/ForgotPassword.d.ts.map +1 -1
  96. package/dist/views/ForgotPassword.js +3 -2
  97. package/dist/views/ForgotPassword.js.map +1 -1
  98. package/dist/views/FormEditor.d.ts.map +1 -1
  99. package/dist/views/FormEditor.js +5 -5
  100. package/dist/views/FormEditor.js.map +1 -1
  101. package/dist/views/Login.d.ts.map +1 -1
  102. package/dist/views/Login.js +17 -17
  103. package/dist/views/Login.js.map +1 -1
  104. package/dist/views/PageEditor.d.ts.map +1 -1
  105. package/dist/views/PageEditor.js +2 -2
  106. package/dist/views/PageEditor.js.map +1 -1
  107. package/dist/views/Pages/PagesListView.js +6 -6
  108. package/dist/views/Pages/PagesListView.js.map +1 -1
  109. package/dist/views/Pages.d.ts.map +1 -1
  110. package/dist/views/Pages.js +16 -19
  111. package/dist/views/Pages.js.map +1 -1
  112. package/dist/views/ResetPassword.d.ts.map +1 -1
  113. package/dist/views/ResetPassword.js +3 -2
  114. package/dist/views/ResetPassword.js.map +1 -1
  115. package/dist/views/Settings.js +2 -2
  116. package/dist/views/Settings.js.map +1 -1
  117. package/dist/views/SetupWizard.d.ts.map +1 -1
  118. package/dist/views/SetupWizard.js +6 -5
  119. package/dist/views/SetupWizard.js.map +1 -1
  120. package/dist/views/page-builder/DesignScore.js +12 -12
  121. package/dist/views/page-builder/DesignScore.js.map +1 -1
  122. package/dist/views/page-builder/SEOPanel.js +9 -9
  123. package/dist/views/page-builder/SEOPanel.js.map +1 -1
  124. package/dist/views/page-editor/EditorCanvas.js +1 -1
  125. package/dist/views/page-editor/EditorCanvas.js.map +1 -1
  126. package/dist/views/page-editor/EditorTopBar.js +2 -2
  127. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  128. package/dist/views/page-editor/PagePreview.js +1 -1
  129. package/dist/views/page-editor/PagePreview.js.map +1 -1
  130. package/dist/views/page-editor/SectionInspector.js +1 -1
  131. package/dist/views/page-editor/SectionInspector.js.map +1 -1
  132. package/dist/views/page-editor/SitePreviewFrame.js +1 -1
  133. package/dist/views/page-editor/SitePreviewFrame.js.map +1 -1
  134. package/dist/views/page-editor/sections/ArticleBodySection.js +1 -1
  135. package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
  136. package/dist/views/page-editor/sections/AuthorBioSection.js +2 -2
  137. package/dist/views/page-editor/sections/AuthorBioSection.js.map +1 -1
  138. package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
  139. package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
  140. package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
  141. package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
  142. package/dist/views/page-editor/sections/EmbedSection.d.ts.map +1 -1
  143. package/dist/views/page-editor/sections/EmbedSection.js +1 -1
  144. package/dist/views/page-editor/sections/EmbedSection.js.map +1 -1
  145. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -1
  146. package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
  147. package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
  148. package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -1
  149. package/dist/views/page-editor/sections/GallerySection.js +1 -1
  150. package/dist/views/page-editor/sections/GallerySection.js.map +1 -1
  151. package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
  152. package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
  153. package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -1
  154. package/dist/views/page-editor/sections/MissionSection.js +1 -1
  155. package/dist/views/page-editor/sections/MissionSection.js.map +1 -1
  156. package/dist/views/page-editor/sections/PullQuoteSection.js +1 -1
  157. package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -1
  158. package/dist/views/page-editor/sections/RelatedPostsSection.js +1 -1
  159. package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -1
  160. package/dist/views/page-editor/sections/SectionRenderer.js +1 -1
  161. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
  162. package/dist/views/page-editor/sections/parts.js +6 -6
  163. package/dist/views/page-editor/sections/parts.js.map +1 -1
  164. package/dist/views/post-editor/PostEditorCanvas.js +2 -2
  165. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  166. package/dist/views/post-editor/PostHeader.js +2 -2
  167. package/dist/views/post-editor/PostHeader.js.map +1 -1
  168. package/dist/views/post-editor/PostHeaderPanel.js +1 -1
  169. package/dist/views/post-editor/PostHeaderPanel.js.map +1 -1
  170. package/dist/views/post-editor/PostPreview.js +1 -1
  171. package/dist/views/post-editor/PostPreview.js.map +1 -1
  172. package/dist/views/profile/AccountSecurity.js +1 -1
  173. package/dist/views/profile/AccountSecurity.js.map +1 -1
  174. package/dist/views/settings/AISettingsTab.d.ts +3 -1
  175. package/dist/views/settings/AISettingsTab.d.ts.map +1 -1
  176. package/dist/views/settings/AISettingsTab.js +2 -2
  177. package/dist/views/settings/AISettingsTab.js.map +1 -1
  178. package/dist/views/settings/BrandVoiceCard.d.ts +39 -2
  179. package/dist/views/settings/BrandVoiceCard.d.ts.map +1 -1
  180. package/dist/views/settings/BrandVoiceCard.js +215 -3
  181. package/dist/views/settings/BrandVoiceCard.js.map +1 -1
  182. package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.d.ts +13 -0
  183. package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.d.ts.map +1 -0
  184. package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.js +74 -0
  185. package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.js.map +1 -0
  186. package/dist/views/settings/brand-voice/BrandVoiceRulesSections.d.ts +7 -0
  187. package/dist/views/settings/brand-voice/BrandVoiceRulesSections.d.ts.map +1 -0
  188. package/dist/views/settings/brand-voice/BrandVoiceRulesSections.js +54 -0
  189. package/dist/views/settings/brand-voice/BrandVoiceRulesSections.js.map +1 -0
  190. package/dist/views/settings/components.js +1 -1
  191. package/dist/views/settings/components.js.map +1 -1
  192. package/dist/views/settings/useAiSettings.d.ts +32 -0
  193. package/dist/views/settings/useAiSettings.d.ts.map +1 -1
  194. package/dist/views/settings/useAiSettings.js +85 -3
  195. package/dist/views/settings/useAiSettings.js.map +1 -1
  196. package/package.json +2 -2
  197. package/src/__tests__/components/posts-badges.test.tsx +3 -3
  198. package/src/__tests__/views/ai-settings.render.test.tsx +50 -0
  199. package/src/components/AICoauthorBubbleMenu.tsx +16 -4
  200. package/src/components/BrandVoiceAlignmentBadge.tsx +39 -0
  201. package/src/components/Breadcrumbs.tsx +5 -5
  202. package/src/components/CoauthorResultPopover.tsx +88 -27
  203. package/src/components/CommandPalette.tsx +53 -39
  204. package/src/components/CommentSidePanel.tsx +20 -20
  205. package/src/components/ContentOverviewChart.tsx +4 -4
  206. package/src/components/FocalPointPicker.tsx +2 -2
  207. package/src/components/LivePreview.tsx +20 -17
  208. package/src/components/LogoPlaceholder.tsx +1 -1
  209. package/src/components/MentionableTextarea.tsx +5 -5
  210. package/src/components/NotificationBell.tsx +10 -10
  211. package/src/components/OfflineStatus.tsx +8 -8
  212. package/src/components/PresenceChips.tsx +12 -8
  213. package/src/components/SEOConfigPanel.tsx +40 -38
  214. package/src/components/SEOPanel.tsx +15 -15
  215. package/src/components/SEOPerformance.tsx +25 -23
  216. package/src/components/TipTapEditor.tsx +7 -7
  217. package/src/components/pages/tag-badges.tsx +6 -6
  218. package/src/components/posts/badges.tsx +28 -46
  219. package/src/components/seo/SeoEditorDrawer.tsx +65 -1
  220. package/src/components/ui/Badge.tsx +5 -5
  221. package/src/components/ui/Toast.tsx +4 -5
  222. package/src/fields/ComponentBlockField.tsx +2 -2
  223. package/src/fields/FieldRenderer.tsx +1 -1
  224. package/src/fields/PropInput.tsx +1 -1
  225. package/src/fields/RelationshipField.tsx +5 -5
  226. package/src/fields/ToggleField.tsx +1 -1
  227. package/src/lib/brand-voice-client.ts +38 -0
  228. package/src/lib/coauthor-client.ts +4 -0
  229. package/src/lib/seo-service.ts +2 -0
  230. package/src/views/Dashboard.tsx +5 -5
  231. package/src/views/DocumentEdit.tsx +13 -11
  232. package/src/views/ForgotPassword.tsx +21 -18
  233. package/src/views/FormEditor.tsx +74 -69
  234. package/src/views/Login.tsx +45 -39
  235. package/src/views/PageEditor.tsx +26 -22
  236. package/src/views/Pages/PagesListView.tsx +6 -6
  237. package/src/views/Pages.tsx +64 -65
  238. package/src/views/ResetPassword.tsx +32 -29
  239. package/src/views/Settings.tsx +3 -3
  240. package/src/views/SetupWizard.tsx +42 -33
  241. package/src/views/page-builder/DesignScore.tsx +12 -12
  242. package/src/views/page-builder/SEOPanel.tsx +12 -12
  243. package/src/views/page-editor/EditorCanvas.tsx +1 -1
  244. package/src/views/page-editor/EditorTopBar.tsx +2 -2
  245. package/src/views/page-editor/PagePreview.tsx +1 -1
  246. package/src/views/page-editor/SectionInspector.tsx +1 -1
  247. package/src/views/page-editor/SitePreviewFrame.tsx +1 -1
  248. package/src/views/page-editor/sections/ArticleBodySection.tsx +5 -5
  249. package/src/views/page-editor/sections/AuthorBioSection.tsx +6 -6
  250. package/src/views/page-editor/sections/ByTheNumbersSection.tsx +5 -5
  251. package/src/views/page-editor/sections/CtaBannerSection.tsx +1 -1
  252. package/src/views/page-editor/sections/EmbedSection.tsx +6 -4
  253. package/src/views/page-editor/sections/FeatureHighlightSection.tsx +7 -5
  254. package/src/views/page-editor/sections/GallerySection.tsx +6 -4
  255. package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
  256. package/src/views/page-editor/sections/MissionSection.tsx +4 -2
  257. package/src/views/page-editor/sections/PullQuoteSection.tsx +4 -4
  258. package/src/views/page-editor/sections/RelatedPostsSection.tsx +5 -5
  259. package/src/views/page-editor/sections/SectionRenderer.tsx +2 -2
  260. package/src/views/page-editor/sections/parts.tsx +6 -6
  261. package/src/views/post-editor/PostEditorCanvas.tsx +4 -4
  262. package/src/views/post-editor/PostHeader.tsx +5 -5
  263. package/src/views/post-editor/PostHeaderPanel.tsx +1 -1
  264. package/src/views/post-editor/PostPreview.tsx +1 -1
  265. package/src/views/profile/AccountSecurity.tsx +1 -1
  266. package/src/views/settings/AISettingsTab.tsx +13 -2
  267. package/src/views/settings/BrandVoiceCard.tsx +592 -76
  268. package/src/views/settings/brand-voice/BrandVoiceAlignmentPanel.tsx +205 -0
  269. package/src/views/settings/brand-voice/BrandVoiceRulesSections.tsx +321 -0
  270. package/src/views/settings/components.tsx +1 -1
  271. package/src/views/settings/useAiSettings.ts +134 -3
@@ -1,9 +1,13 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useRef } from 'react'
4
- import { Check, Copy, Loader2, Sparkles, X } from 'lucide-react'
3
+ import { useEffect, useRef, useState } from 'react'
4
+ import { Check, Copy, Loader2, Sparkles, Wand2, X } from 'lucide-react'
5
+ import type { BrandAlignmentScoreResult } from '@actuate-media/cms-core/brand-voice'
5
6
  import type { CoauthorAction, ProofreadChange } from '../lib/coauthor-client.js'
6
7
  import { coauthorActionLabel } from '../lib/coauthor-client.js'
8
+ import { fixBrandVoiceContent } from '../lib/brand-voice-client.js'
9
+ import { BrandVoiceAlignmentBadge } from './BrandVoiceAlignmentBadge.js'
10
+ import { toast } from 'sonner'
7
11
 
8
12
  export interface CoauthorResultPopoverProps {
9
13
  open: boolean
@@ -13,6 +17,9 @@ export interface CoauthorResultPopoverProps {
13
17
  after: string
14
18
  error?: string
15
19
  changes?: ProofreadChange[]
20
+ brandAlignment?: BrandAlignmentScoreResult
21
+ /** Called when fix-to-brand updates the after text. */
22
+ onAfterChange?: (text: string, alignment?: BrandAlignmentScoreResult) => void
16
23
  /** Label for the apply target, e.g. "Apply to editor" or "Replace selection". */
17
24
  applyLabel?: string
18
25
  onApply: () => void
@@ -39,12 +46,15 @@ export function CoauthorResultPopover({
39
46
  after,
40
47
  error,
41
48
  changes,
49
+ brandAlignment,
50
+ onAfterChange,
42
51
  applyLabel,
43
52
  onApply,
44
53
  onCopy,
45
54
  onDiscard,
46
55
  }: CoauthorResultPopoverProps) {
47
56
  const dialogRef = useRef<HTMLDivElement>(null)
57
+ const [fixing, setFixing] = useState(false)
48
58
 
49
59
  useEffect(() => {
50
60
  if (!open) return
@@ -65,8 +75,35 @@ export function CoauthorResultPopover({
65
75
 
66
76
  if (!open) return null
67
77
 
68
- const applyDisabled = loading || !!error || after.length === 0
69
- const copyDisabled = loading || !!error || after.length === 0
78
+ const applyDisabled = loading || fixing || !!error || after.length === 0
79
+ const copyDisabled = loading || fixing || !!error || after.length === 0
80
+ const showFix =
81
+ !loading &&
82
+ !error &&
83
+ after.length > 0 &&
84
+ brandAlignment &&
85
+ brandAlignment.score < 85 &&
86
+ onAfterChange
87
+
88
+ async function handleFix() {
89
+ if (!onAfterChange || !after.trim()) return
90
+ setFixing(true)
91
+ try {
92
+ const res = await fixBrandVoiceContent({ content: after, channel: 'blog' })
93
+ if (!res.ok || !res.result) {
94
+ toast.error(res.error ?? 'Could not fix copy for brand voice.')
95
+ return
96
+ }
97
+ onAfterChange(res.result.text, res.result.afterScore)
98
+ toast.success(
99
+ res.result.usedAi
100
+ ? 'Copy revised to match brand voice.'
101
+ : 'Applied brand voice rule fixes.',
102
+ )
103
+ } finally {
104
+ setFixing(false)
105
+ }
106
+ }
70
107
 
71
108
  return (
72
109
  <div
@@ -81,20 +118,23 @@ export function CoauthorResultPopover({
81
118
  aria-label={`AI ${coauthorActionLabel(action)} result`}
82
119
  tabIndex={-1}
83
120
  onClick={(e) => e.stopPropagation()}
84
- className="w-full max-w-2xl rounded-lg bg-white shadow-2xl outline-none"
121
+ className="bg-card w-full max-w-2xl rounded-lg shadow-2xl outline-none"
85
122
  >
86
- <div className="flex items-center justify-between border-b border-gray-200 px-4 py-3">
87
- <div className="flex items-center gap-2 text-sm font-medium text-gray-900">
88
- <Sparkles className="h-4 w-4 text-blue-600" aria-hidden />
123
+ <div className="border-border flex items-center justify-between border-b px-4 py-3">
124
+ <div className="text-foreground flex items-center gap-2 text-sm font-medium">
125
+ <Sparkles className="text-primary h-4 w-4" aria-hidden />
89
126
  <span>AI {coauthorActionLabel(action)}</span>
90
127
  {loading && (
91
- <Loader2 className="ml-1 h-4 w-4 animate-spin text-gray-400" aria-label="Loading" />
128
+ <Loader2
129
+ className="text-muted-foreground ml-1 h-4 w-4 animate-spin"
130
+ aria-label="Loading"
131
+ />
92
132
  )}
93
133
  </div>
94
134
  <button
95
135
  type="button"
96
136
  onClick={onDiscard}
97
- className="rounded p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
137
+ className="text-muted-foreground hover:bg-accent hover:text-muted-foreground rounded p-1"
98
138
  aria-label="Close"
99
139
  >
100
140
  <X className="h-4 w-4" />
@@ -105,34 +145,34 @@ export function CoauthorResultPopover({
105
145
  <section aria-labelledby="coauthor-before">
106
146
  <h3
107
147
  id="coauthor-before"
108
- className="mb-2 text-xs font-medium tracking-wide text-gray-500 uppercase"
148
+ className="text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase"
109
149
  >
110
150
  Before
111
151
  </h3>
112
- <pre className="max-h-72 overflow-auto rounded border border-gray-200 bg-gray-50 p-3 text-sm whitespace-pre-wrap text-gray-700">
152
+ <pre className="border-border bg-muted text-foreground max-h-72 overflow-auto rounded border p-3 text-sm whitespace-pre-wrap">
113
153
  {before}
114
154
  </pre>
115
155
  </section>
116
156
  <section aria-labelledby="coauthor-after">
117
157
  <h3
118
158
  id="coauthor-after"
119
- className="mb-2 text-xs font-medium tracking-wide text-gray-500 uppercase"
159
+ className="text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase"
120
160
  >
121
161
  After
122
162
  </h3>
123
163
  {loading ? (
124
164
  <div
125
- className="flex h-72 items-center justify-center rounded border border-dashed border-gray-200 bg-gray-50 text-sm text-gray-400"
165
+ className="border-border bg-muted text-muted-foreground flex h-72 items-center justify-center rounded border border-dashed text-sm"
126
166
  role="status"
127
167
  >
128
168
  Thinking…
129
169
  </div>
130
170
  ) : error ? (
131
- <div className="flex h-72 items-center justify-center rounded border border-dashed border-red-200 bg-red-50 p-3 text-sm text-red-600">
171
+ <div className="border-destructive/30 bg-destructive/10 text-destructive flex h-72 items-center justify-center rounded border border-dashed p-3 text-sm">
132
172
  {error}
133
173
  </div>
134
174
  ) : (
135
- <pre className="max-h-72 overflow-auto rounded border border-blue-200 bg-blue-50 p-3 text-sm whitespace-pre-wrap text-gray-900">
175
+ <pre className="border-info/30 bg-info/10 text-foreground max-h-72 overflow-auto rounded border p-3 text-sm whitespace-pre-wrap">
136
176
  {after}
137
177
  </pre>
138
178
  )}
@@ -140,18 +180,18 @@ export function CoauthorResultPopover({
140
180
  </div>
141
181
 
142
182
  {changes && changes.length > 0 && !loading && !error && (
143
- <div className="border-t border-gray-200 px-4 py-3">
144
- <h3 className="mb-2 text-xs font-medium tracking-wide text-gray-500 uppercase">
183
+ <div className="border-border border-t px-4 py-3">
184
+ <h3 className="text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase">
145
185
  Changes ({changes.length})
146
186
  </h3>
147
187
  <ul className="max-h-32 space-y-1 overflow-auto text-xs">
148
188
  {changes.map((change, i) => (
149
- <li key={i} className="flex items-start gap-2 rounded bg-gray-50 px-2 py-1">
150
- <span className="text-red-600 line-through">{change.original}</span>
151
- <span className="text-gray-400">→</span>
152
- <span className="text-green-700">{change.corrected}</span>
189
+ <li key={i} className="bg-muted flex items-start gap-2 rounded px-2 py-1">
190
+ <span className="text-destructive line-through">{change.original}</span>
191
+ <span className="text-muted-foreground">→</span>
192
+ <span className="text-success">{change.corrected}</span>
153
193
  {change.reason && (
154
- <span className="ml-auto text-gray-500 italic">{change.reason}</span>
194
+ <span className="text-muted-foreground ml-auto italic">{change.reason}</span>
155
195
  )}
156
196
  </li>
157
197
  ))}
@@ -159,11 +199,32 @@ export function CoauthorResultPopover({
159
199
  </div>
160
200
  )}
161
201
 
162
- <div className="flex items-center justify-end gap-2 border-t border-gray-200 bg-gray-50 px-4 py-3">
202
+ {brandAlignment && !loading && !error && (
203
+ <div className="border-border flex flex-wrap items-center justify-between gap-3 border-t px-4 py-3">
204
+ <BrandVoiceAlignmentBadge alignment={brandAlignment} />
205
+ {showFix && (
206
+ <button
207
+ type="button"
208
+ onClick={handleFix}
209
+ disabled={fixing}
210
+ className="border-border bg-card text-foreground hover:bg-accent inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm disabled:cursor-not-allowed disabled:opacity-50"
211
+ >
212
+ {fixing ? (
213
+ <Loader2 className="h-3.5 w-3.5 animate-spin" aria-hidden />
214
+ ) : (
215
+ <Wand2 className="h-3.5 w-3.5" aria-hidden />
216
+ )}
217
+ Fix to match brand voice
218
+ </button>
219
+ )}
220
+ </div>
221
+ )}
222
+
223
+ <div className="border-border bg-muted flex items-center justify-end gap-2 border-t px-4 py-3">
163
224
  <button
164
225
  type="button"
165
226
  onClick={onDiscard}
166
- className="rounded-md border border-gray-200 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50"
227
+ className="border-border bg-card text-foreground hover:bg-accent rounded-md border px-3 py-1.5 text-sm"
167
228
  >
168
229
  <X className="mr-1.5 inline h-3.5 w-3.5" aria-hidden />
169
230
  Discard
@@ -172,7 +233,7 @@ export function CoauthorResultPopover({
172
233
  type="button"
173
234
  onClick={onCopy}
174
235
  disabled={copyDisabled}
175
- className="rounded-md border border-gray-200 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50"
236
+ className="border-border bg-card text-foreground hover:bg-accent rounded-md border px-3 py-1.5 text-sm disabled:cursor-not-allowed disabled:opacity-50"
176
237
  >
177
238
  <Copy className="mr-1.5 inline h-3.5 w-3.5" aria-hidden />
178
239
  Copy
@@ -181,7 +242,7 @@ export function CoauthorResultPopover({
181
242
  type="button"
182
243
  onClick={onApply}
183
244
  disabled={applyDisabled}
184
- className="rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
245
+ className="bg-primary hover:bg-primary/90 rounded-md px-3 py-1.5 text-sm font-medium text-white disabled:cursor-not-allowed disabled:opacity-50"
185
246
  >
186
247
  <Check className="mr-1.5 inline h-3.5 w-3.5" aria-hidden />
187
248
  {applyLabel ?? 'Apply'}
@@ -31,6 +31,7 @@ import {
31
31
  type CoauthorAction,
32
32
  type ProofreadChange,
33
33
  } from '../lib/coauthor-client.js'
34
+ import type { BrandAlignmentScoreResult } from '@actuate-media/cms-core/brand-voice'
34
35
  import {
35
36
  isAiSlashCommandInput,
36
37
  orderedAiActions,
@@ -116,6 +117,7 @@ interface AiResultState {
116
117
  after: string
117
118
  error?: string
118
119
  changes?: ProofreadChange[]
120
+ brandAlignment?: BrandAlignmentScoreResult
119
121
  /** Editor captured at submission time; null means clipboard-only. */
120
122
  target: Editor | null
121
123
  }
@@ -258,6 +260,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
258
260
  loading: false,
259
261
  after: outcome.result.text,
260
262
  changes: outcome.result.changes,
263
+ brandAlignment: outcome.result.brandAlignment,
261
264
  error: undefined,
262
265
  }))
263
266
  },
@@ -294,6 +297,13 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
294
297
  }
295
298
  }, [aiResult.after])
296
299
 
300
+ const handleAiAfterChange = useCallback(
301
+ (after: string, brandAlignment?: BrandAlignmentScoreResult) => {
302
+ setAiResult((prev) => ({ ...prev, after, brandAlignment }))
303
+ },
304
+ [],
305
+ )
306
+
297
307
  const handleAiDiscard = useCallback(() => {
298
308
  setAiResult(INITIAL_AI_RESULT)
299
309
  }, [])
@@ -314,6 +324,8 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
314
324
  after={aiResult.after}
315
325
  error={aiResult.error}
316
326
  changes={aiResult.changes}
327
+ brandAlignment={aiResult.brandAlignment}
328
+ onAfterChange={handleAiAfterChange}
317
329
  applyLabel={aiResult.target ? 'Apply to editor' : 'Copy to clipboard'}
318
330
  onApply={handleAiApply}
319
331
  onCopy={handleAiCopy}
@@ -340,7 +352,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
340
352
  onClick={(e) => e.stopPropagation()}
341
353
  >
342
354
  <Command
343
- className="w-full overflow-hidden rounded-lg bg-white shadow-2xl"
355
+ className="bg-card w-full overflow-hidden rounded-lg shadow-2xl"
344
356
  onKeyDown={(e) => {
345
357
  if (e.key === 'Escape') {
346
358
  onOpenChange(false)
@@ -349,30 +361,30 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
349
361
  }}
350
362
  shouldFilter={!hasQuery}
351
363
  >
352
- <div className="flex items-center border-b border-gray-200 px-4">
353
- <Search className="mr-3 h-5 w-5 shrink-0 text-gray-400" />
364
+ <div className="border-border flex items-center border-b px-4">
365
+ <Search className="text-muted-foreground mr-3 h-5 w-5 shrink-0" />
354
366
  <Command.Input
355
367
  value={search}
356
368
  onValueChange={setSearch}
357
369
  placeholder="Search, jump to, or type /ai to use the AI co-author…"
358
- className="w-full bg-transparent py-4 text-base placeholder:text-gray-400 focus:outline-none"
370
+ className="placeholder:text-muted-foreground w-full bg-transparent py-4 text-base focus:outline-none"
359
371
  />
360
372
  {loading && (
361
- <div className="mr-2 h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-gray-300 border-t-gray-600" />
373
+ <div className="border-border mr-2 h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-t-gray-600" />
362
374
  )}
363
- <kbd className="hidden shrink-0 rounded bg-gray-100 px-2 py-1 font-mono text-xs text-gray-600 sm:inline-block">
375
+ <kbd className="bg-muted text-muted-foreground hidden shrink-0 rounded px-2 py-1 font-mono text-xs sm:inline-block">
364
376
  ESC
365
377
  </kbd>
366
378
  </div>
367
379
 
368
380
  <Command.List className="max-h-[400px] overflow-y-auto p-2">
369
- <Command.Empty className="py-6 text-center text-sm text-gray-500">
381
+ <Command.Empty className="text-muted-foreground py-6 text-center text-sm">
370
382
  {loading ? 'Searching...' : 'No results found.'}
371
383
  </Command.Empty>
372
384
 
373
385
  {isAiMode && (
374
386
  <Command.Group heading="AI co-author" className="px-2 py-2">
375
- <div className="mb-2 flex items-center gap-1 text-xs font-medium text-gray-500">
387
+ <div className="text-muted-foreground mb-2 flex items-center gap-1 text-xs font-medium">
376
388
  <Sparkles className="h-3 w-3" aria-hidden /> AI CO-AUTHOR
377
389
  </div>
378
390
  {aiActions.map((action) => {
@@ -384,23 +396,23 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
384
396
  key={`ai-${action}`}
385
397
  value={`ai ${action}`}
386
398
  onSelect={() => runSlashAction(action, aiPayload)}
387
- className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
399
+ className="hover:bg-accent data-[selected=true]:bg-muted flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2"
388
400
  >
389
- <Icon className="h-4 w-4 text-blue-600" />
401
+ <Icon className="text-primary h-4 w-4" />
390
402
  <div className="flex min-w-0 flex-col">
391
403
  <span className="font-medium">
392
404
  {coauthorActionLabel(action)}
393
405
  {previewable && (
394
- <span className="ml-1 font-normal text-gray-500">
406
+ <span className="text-muted-foreground ml-1 font-normal">
395
407
  “{truncatePreview(aiPayload)}”
396
408
  </span>
397
409
  )}
398
410
  </span>
399
- <span className="text-xs text-gray-400">
411
+ <span className="text-muted-foreground text-xs">
400
412
  {coauthorActionDescription(action)}
401
413
  </span>
402
414
  </div>
403
- <kbd className="ml-auto rounded bg-gray-100 px-1.5 py-0.5 font-mono text-[10px] text-gray-500">
415
+ <kbd className="bg-muted text-muted-foreground ml-auto rounded px-1.5 py-0.5 font-mono text-[10px]">
404
416
  /ai {action}
405
417
  </kbd>
406
418
  </Command.Item>
@@ -411,17 +423,19 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
411
423
 
412
424
  {!isAiMode && !hasQuery && recentItems.length > 0 && (
413
425
  <Command.Group heading="Recent" className="px-2 py-2">
414
- <div className="mb-2 text-xs font-medium text-gray-500">RECENT</div>
426
+ <div className="text-muted-foreground mb-2 text-xs font-medium">RECENT</div>
415
427
  {recentItems.map((item) => (
416
428
  <Command.Item
417
429
  key={`recent-${item.id}`}
418
430
  value={`recent ${item.label}`}
419
431
  onSelect={() => handleSelect(item.path, item)}
420
- className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
432
+ className="hover:bg-accent data-[selected=true]:bg-muted flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2"
421
433
  >
422
- <Clock className="h-4 w-4 text-gray-400" />
434
+ <Clock className="text-muted-foreground h-4 w-4" />
423
435
  <span>{item.label}</span>
424
- <span className="ml-auto text-xs text-gray-400 capitalize">{item.type}</span>
436
+ <span className="text-muted-foreground ml-auto text-xs capitalize">
437
+ {item.type}
438
+ </span>
425
439
  </Command.Item>
426
440
  ))}
427
441
  </Command.Group>
@@ -429,7 +443,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
429
443
 
430
444
  {!isAiMode && hasQuery && documents.length > 0 && (
431
445
  <Command.Group heading="Documents" className="px-2 py-2">
432
- <div className="mb-2 text-xs font-medium text-gray-500">DOCUMENTS</div>
446
+ <div className="text-muted-foreground mb-2 text-xs font-medium">DOCUMENTS</div>
433
447
  {documents.map((doc) => (
434
448
  <Command.Item
435
449
  key={`doc-${doc.id}`}
@@ -442,12 +456,12 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
442
456
  type: 'document',
443
457
  })
444
458
  }
445
- className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
459
+ className="hover:bg-accent data-[selected=true]:bg-muted flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2"
446
460
  >
447
- <FileText className="h-4 w-4 text-gray-500" />
461
+ <FileText className="text-muted-foreground h-4 w-4" />
448
462
  <div className="flex min-w-0 flex-col">
449
463
  <span className="truncate">{doc.title ?? doc.slug ?? doc.id}</span>
450
- <span className="text-xs text-gray-400">
464
+ <span className="text-muted-foreground text-xs">
451
465
  {doc.collection} &middot; {doc.status}
452
466
  </span>
453
467
  </div>
@@ -458,7 +472,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
458
472
 
459
473
  {!isAiMode && hasQuery && media.length > 0 && (
460
474
  <Command.Group heading="Media" className="mt-1 px-2 py-2">
461
- <div className="mb-2 text-xs font-medium text-gray-500">MEDIA</div>
475
+ <div className="text-muted-foreground mb-2 text-xs font-medium">MEDIA</div>
462
476
  {media.map((m) => (
463
477
  <Command.Item
464
478
  key={`media-${m.id}`}
@@ -471,12 +485,12 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
471
485
  type: 'media',
472
486
  })
473
487
  }
474
- className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
488
+ className="hover:bg-accent data-[selected=true]:bg-muted flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2"
475
489
  >
476
- <Image className="h-4 w-4 text-gray-500" />
490
+ <Image className="text-muted-foreground h-4 w-4" />
477
491
  <div className="flex min-w-0 flex-col">
478
492
  <span className="truncate">{m.filename}</span>
479
- <span className="text-xs text-gray-400">{m.mimeType}</span>
493
+ <span className="text-muted-foreground text-xs">{m.mimeType}</span>
480
494
  </div>
481
495
  </Command.Item>
482
496
  ))}
@@ -485,7 +499,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
485
499
 
486
500
  {!isAiMode && hasQuery && users.length > 0 && (
487
501
  <Command.Group heading="Users" className="mt-1 px-2 py-2">
488
- <div className="mb-2 text-xs font-medium text-gray-500">USERS</div>
502
+ <div className="text-muted-foreground mb-2 text-xs font-medium">USERS</div>
489
503
  {users.map((u) => (
490
504
  <Command.Item
491
505
  key={`user-${u.id}`}
@@ -498,12 +512,12 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
498
512
  type: 'user',
499
513
  })
500
514
  }
501
- className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
515
+ className="hover:bg-accent data-[selected=true]:bg-muted flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2"
502
516
  >
503
- <Users className="h-4 w-4 text-gray-500" />
517
+ <Users className="text-muted-foreground h-4 w-4" />
504
518
  <div className="flex min-w-0 flex-col">
505
519
  <span className="truncate">{u.name}</span>
506
- <span className="text-xs text-gray-400">
520
+ <span className="text-muted-foreground text-xs">
507
521
  {u.email} &middot; {u.role}
508
522
  </span>
509
523
  </div>
@@ -514,7 +528,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
514
528
 
515
529
  {!isAiMode && (!hasQuery || !hasResults) && (
516
530
  <Command.Group heading="Navigation" className="px-2 py-2">
517
- <div className="mb-2 text-xs font-medium text-gray-500">NAVIGATION</div>
531
+ <div className="text-muted-foreground mb-2 text-xs font-medium">NAVIGATION</div>
518
532
  {navigationCommands.map((cmd) => {
519
533
  const Icon = cmd.icon
520
534
  return (
@@ -522,9 +536,9 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
522
536
  key={cmd.id}
523
537
  value={cmd.label}
524
538
  onSelect={() => handleSelect(cmd.action)}
525
- className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
539
+ className="hover:bg-accent data-[selected=true]:bg-muted flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2"
526
540
  >
527
- <Icon className="h-4 w-4 text-gray-500" />
541
+ <Icon className="text-muted-foreground h-4 w-4" />
528
542
  <span>{cmd.label}</span>
529
543
  </Command.Item>
530
544
  )
@@ -534,7 +548,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
534
548
 
535
549
  {!isAiMode && (
536
550
  <Command.Group heading="Actions" className="mt-2 px-2 py-2">
537
- <div className="mb-2 text-xs font-medium text-gray-500">ACTIONS</div>
551
+ <div className="text-muted-foreground mb-2 text-xs font-medium">ACTIONS</div>
538
552
  {staticActions.map((cmd) => {
539
553
  const Icon = cmd.icon
540
554
  return (
@@ -549,9 +563,9 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
549
563
  type: 'action',
550
564
  })
551
565
  }
552
- className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
566
+ className="hover:bg-accent data-[selected=true]:bg-muted flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2"
553
567
  >
554
- <Icon className="h-4 w-4 text-gray-500" />
568
+ <Icon className="text-muted-foreground h-4 w-4" />
555
569
  <span>{cmd.label}</span>
556
570
  </Command.Item>
557
571
  )
@@ -560,19 +574,19 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
560
574
  )}
561
575
  </Command.List>
562
576
 
563
- <div className="flex items-center justify-between border-t border-gray-200 bg-gray-50 px-4 py-3 text-xs text-gray-500">
577
+ <div className="border-border bg-muted text-muted-foreground flex items-center justify-between border-t px-4 py-3 text-xs">
564
578
  <div className="flex items-center gap-4">
565
579
  <span className="flex items-center gap-1">
566
- <kbd className="rounded border border-gray-300 bg-white px-1.5 py-0.5 text-[10px]">
580
+ <kbd className="border-border bg-card rounded border px-1.5 py-0.5 text-[10px]">
567
581
 
568
582
  </kbd>
569
- <kbd className="rounded border border-gray-300 bg-white px-1.5 py-0.5 text-[10px]">
583
+ <kbd className="border-border bg-card rounded border px-1.5 py-0.5 text-[10px]">
570
584
 
571
585
  </kbd>
572
586
  to navigate
573
587
  </span>
574
588
  <span className="flex items-center gap-1">
575
- <kbd className="rounded border border-gray-300 bg-white px-1.5 py-0.5 text-[10px]">
589
+ <kbd className="border-border bg-card rounded border px-1.5 py-0.5 text-[10px]">
576
590
 
577
591
  </kbd>
578
592
  to select