@prenta/admin 1.17.1 → 1.17.2

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 (268) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/__tests__/views/ai-settings.render.test.js +67 -1
  3. package/dist/__tests__/views/ai-settings.render.test.js.map +1 -1
  4. package/dist/components/CoauthorResultPopover.d.ts.map +1 -1
  5. package/dist/components/CoauthorResultPopover.js +4 -1
  6. package/dist/components/CoauthorResultPopover.js.map +1 -1
  7. package/dist/components/CommentSidePanel.d.ts +0 -7
  8. package/dist/components/CommentSidePanel.d.ts.map +1 -1
  9. package/dist/components/CommentSidePanel.js +0 -2
  10. package/dist/components/CommentSidePanel.js.map +1 -1
  11. package/dist/components/EditorRail.d.ts +2 -1
  12. package/dist/components/EditorRail.d.ts.map +1 -1
  13. package/dist/components/MediaPickerModal.d.ts +2 -1
  14. package/dist/components/MediaPickerModal.d.ts.map +1 -1
  15. package/dist/components/TipTapEditor.d.ts +3 -2
  16. package/dist/components/TipTapEditor.d.ts.map +1 -1
  17. package/dist/components/forms/EntriesTable.d.ts +0 -1
  18. package/dist/components/forms/EntriesTable.d.ts.map +1 -1
  19. package/dist/components/forms/EntriesTable.js +1 -1
  20. package/dist/components/forms/EntriesTable.js.map +1 -1
  21. package/dist/components/pages/tag-badges.d.ts +2 -2
  22. package/dist/components/pages/tag-badges.d.ts.map +1 -1
  23. package/dist/components/pages/tag-badges.js.map +1 -1
  24. package/dist/components/seo/FaqProposalPreview.d.ts +2 -3
  25. package/dist/components/seo/FaqProposalPreview.d.ts.map +1 -1
  26. package/dist/components/seo/FaqProposalPreview.js +1 -1
  27. package/dist/components/seo/FaqProposalPreview.js.map +1 -1
  28. package/dist/components/seo/SeoCopilotDrawer.js +1 -1
  29. package/dist/components/seo/SeoCopilotDrawer.js.map +1 -1
  30. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
  31. package/dist/components/seo/SeoEditorPane.js +4 -1
  32. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  33. package/dist/components/ui/Badge.d.ts +3 -9
  34. package/dist/components/ui/Badge.d.ts.map +1 -1
  35. package/dist/components/ui/Badge.js +2 -3
  36. package/dist/components/ui/Badge.js.map +1 -1
  37. package/dist/components/ui/Button.d.ts +2 -2
  38. package/dist/components/ui/Button.d.ts.map +1 -1
  39. package/dist/components/ui/Button.js +0 -1
  40. package/dist/components/ui/Button.js.map +1 -1
  41. package/dist/components/ui/Card.d.ts +2 -3
  42. package/dist/components/ui/Card.d.ts.map +1 -1
  43. package/dist/components/ui/Card.js +0 -4
  44. package/dist/components/ui/Card.js.map +1 -1
  45. package/dist/components/ui/Input.d.ts +6 -8
  46. package/dist/components/ui/Input.d.ts.map +1 -1
  47. package/dist/components/ui/Input.js +6 -6
  48. package/dist/components/ui/Input.js.map +1 -1
  49. package/dist/components/ui/Modal.d.ts +1 -1
  50. package/dist/components/ui/Modal.d.ts.map +1 -1
  51. package/dist/components/ui/ResizableAside.d.ts +0 -13
  52. package/dist/components/ui/ResizableAside.d.ts.map +1 -1
  53. package/dist/components/ui/ResizableAside.js +5 -5
  54. package/dist/components/ui/ResizableAside.js.map +1 -1
  55. package/dist/components/ui/StatusBadge.d.ts +2 -2
  56. package/dist/components/ui/StatusBadge.d.ts.map +1 -1
  57. package/dist/components/ui/StatusBadge.js +0 -1
  58. package/dist/components/ui/StatusBadge.js.map +1 -1
  59. package/dist/components/ui/Toggle.d.ts +2 -8
  60. package/dist/components/ui/Toggle.d.ts.map +1 -1
  61. package/dist/components/ui/Toggle.js +0 -1
  62. package/dist/components/ui/Toggle.js.map +1 -1
  63. package/dist/fields/FieldRenderer.d.ts +2 -1
  64. package/dist/fields/FieldRenderer.d.ts.map +1 -1
  65. package/dist/fields/index.d.ts +0 -17
  66. package/dist/fields/index.d.ts.map +1 -1
  67. package/dist/fields/index.js +0 -15
  68. package/dist/fields/index.js.map +1 -1
  69. package/dist/layout/primitives/tokens.d.ts +0 -2
  70. package/dist/layout/primitives/tokens.d.ts.map +1 -1
  71. package/dist/layout/primitives/tokens.js +0 -6
  72. package/dist/layout/primitives/tokens.js.map +1 -1
  73. package/dist/lib/brand-voice-client.d.ts +14 -1
  74. package/dist/lib/brand-voice-client.d.ts.map +1 -1
  75. package/dist/lib/brand-voice-client.js +7 -0
  76. package/dist/lib/brand-voice-client.js.map +1 -1
  77. package/dist/lib/coauthor-client.d.ts +2 -1
  78. package/dist/lib/coauthor-client.d.ts.map +1 -1
  79. package/dist/lib/collection-schema.d.ts +0 -9
  80. package/dist/lib/collection-schema.d.ts.map +1 -1
  81. package/dist/lib/collection-schema.js +2 -2
  82. package/dist/lib/collection-schema.js.map +1 -1
  83. package/dist/lib/editor-seo-content.d.ts +0 -10
  84. package/dist/lib/editor-seo-content.d.ts.map +1 -1
  85. package/dist/lib/editor-seo-content.js +0 -13
  86. package/dist/lib/editor-seo-content.js.map +1 -1
  87. package/dist/lib/forms-service.d.ts +3 -21
  88. package/dist/lib/forms-service.d.ts.map +1 -1
  89. package/dist/lib/forms-service.js +0 -25
  90. package/dist/lib/forms-service.js.map +1 -1
  91. package/dist/lib/notifications-client.d.ts +4 -3
  92. package/dist/lib/notifications-client.d.ts.map +1 -1
  93. package/dist/lib/page-audit-input.d.ts +0 -10
  94. package/dist/lib/page-audit-input.d.ts.map +1 -1
  95. package/dist/lib/page-audit-input.js +1 -1
  96. package/dist/lib/page-audit-input.js.map +1 -1
  97. package/dist/lib/page-editor-service.d.ts +2 -4
  98. package/dist/lib/page-editor-service.d.ts.map +1 -1
  99. package/dist/lib/page-editor-service.js +3 -3
  100. package/dist/lib/page-editor-service.js.map +1 -1
  101. package/dist/lib/pages-service.d.ts +2 -13
  102. package/dist/lib/pages-service.d.ts.map +1 -1
  103. package/dist/lib/pages-service.js +3 -24
  104. package/dist/lib/pages-service.js.map +1 -1
  105. package/dist/lib/plan.d.ts +2 -1
  106. package/dist/lib/plan.d.ts.map +1 -1
  107. package/dist/lib/post-editor-service.d.ts +3 -13
  108. package/dist/lib/post-editor-service.d.ts.map +1 -1
  109. package/dist/lib/post-editor-service.js +5 -6
  110. package/dist/lib/post-editor-service.js.map +1 -1
  111. package/dist/lib/post-types-service.d.ts +2 -3
  112. package/dist/lib/post-types-service.d.ts.map +1 -1
  113. package/dist/lib/post-types-service.js +1 -1
  114. package/dist/lib/post-types-service.js.map +1 -1
  115. package/dist/lib/posts-service.d.ts +2 -1
  116. package/dist/lib/posts-service.d.ts.map +1 -1
  117. package/dist/lib/preview-link.d.ts +0 -9
  118. package/dist/lib/preview-link.d.ts.map +1 -1
  119. package/dist/lib/preview-link.js +0 -15
  120. package/dist/lib/preview-link.js.map +1 -1
  121. package/dist/lib/rail-width.d.ts +0 -1
  122. package/dist/lib/rail-width.d.ts.map +1 -1
  123. package/dist/lib/rail-width.js +1 -1
  124. package/dist/lib/rail-width.js.map +1 -1
  125. package/dist/lib/reflow-probe.d.ts +0 -67
  126. package/dist/lib/reflow-probe.d.ts.map +1 -1
  127. package/dist/lib/reflow-probe.js +4 -4
  128. package/dist/lib/reflow-probe.js.map +1 -1
  129. package/dist/lib/seo-service.d.ts +14 -32
  130. package/dist/lib/seo-service.d.ts.map +1 -1
  131. package/dist/lib/seo-service.js +7 -26
  132. package/dist/lib/seo-service.js.map +1 -1
  133. package/dist/lib/theme-chrome.d.ts +0 -1
  134. package/dist/lib/theme-chrome.d.ts.map +1 -1
  135. package/dist/lib/theme-chrome.js +1 -1
  136. package/dist/lib/theme-chrome.js.map +1 -1
  137. package/dist/lib/version-history.d.ts +2 -2
  138. package/dist/lib/version-history.d.ts.map +1 -1
  139. package/dist/lib/workflow-chrome.d.ts +0 -3
  140. package/dist/lib/workflow-chrome.d.ts.map +1 -1
  141. package/dist/lib/workflow-chrome.js +3 -3
  142. package/dist/lib/workflow-chrome.js.map +1 -1
  143. package/dist/views/Login.d.ts +2 -1
  144. package/dist/views/Login.d.ts.map +1 -1
  145. package/dist/views/MediaBrowser.d.ts.map +1 -1
  146. package/dist/views/MediaBrowser.js +3 -2
  147. package/dist/views/MediaBrowser.js.map +1 -1
  148. package/dist/views/PageEditor.d.ts +2 -1
  149. package/dist/views/PageEditor.d.ts.map +1 -1
  150. package/dist/views/Pages/PagesListView.d.ts +0 -1
  151. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  152. package/dist/views/Pages/PagesListView.js +1 -1
  153. package/dist/views/Pages/PagesListView.js.map +1 -1
  154. package/dist/views/Pages.d.ts +2 -1
  155. package/dist/views/Pages.d.ts.map +1 -1
  156. package/dist/views/Redirects.d.ts +2 -1
  157. package/dist/views/Redirects.d.ts.map +1 -1
  158. package/dist/views/page-editor/section-types.d.ts +2 -2
  159. package/dist/views/page-editor/section-types.d.ts.map +1 -1
  160. package/dist/views/page-editor/section-types.js +1 -1
  161. package/dist/views/page-editor/section-types.js.map +1 -1
  162. package/dist/views/page-editor/sections/admin-renderers.d.ts +2 -1
  163. package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -1
  164. package/dist/views/page-editor/theme-chrome/index.d.ts +1 -6
  165. package/dist/views/page-editor/theme-chrome/index.d.ts.map +1 -1
  166. package/dist/views/page-editor/theme-chrome/index.js +1 -3
  167. package/dist/views/page-editor/theme-chrome/index.js.map +1 -1
  168. package/dist/views/page-editor/viewports.d.ts +1 -1
  169. package/dist/views/page-editor/viewports.d.ts.map +1 -1
  170. package/dist/views/page-editor/viewports.js +1 -1
  171. package/dist/views/page-editor/viewports.js.map +1 -1
  172. package/dist/views/settings/AISettingsTab.d.ts.map +1 -1
  173. package/dist/views/settings/AISettingsTab.js +13 -3
  174. package/dist/views/settings/AISettingsTab.js.map +1 -1
  175. package/dist/views/settings/BrandVoiceCard.d.ts +3 -0
  176. package/dist/views/settings/BrandVoiceCard.d.ts.map +1 -1
  177. package/dist/views/settings/BrandVoiceCard.js +2 -2
  178. package/dist/views/settings/BrandVoiceCard.js.map +1 -1
  179. package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.d.ts.map +1 -1
  180. package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.js +4 -1
  181. package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.js.map +1 -1
  182. package/dist/views/settings/featureModel.d.ts +0 -3
  183. package/dist/views/settings/featureModel.d.ts.map +1 -1
  184. package/dist/views/settings/featureModel.js +2 -2
  185. package/dist/views/settings/featureModel.js.map +1 -1
  186. package/dist/views/settings/useAiSettings.d.ts +2 -2
  187. package/dist/views/settings/useAiSettings.d.ts.map +1 -1
  188. package/dist/views/settings/useAiSettings.js +6 -2
  189. package/dist/views/settings/useAiSettings.js.map +1 -1
  190. package/dist/views/settings/useAppearanceSettings.d.ts +5 -6
  191. package/dist/views/settings/useAppearanceSettings.d.ts.map +1 -1
  192. package/dist/views/settings/useAppearanceSettings.js +1 -1
  193. package/dist/views/settings/useAppearanceSettings.js.map +1 -1
  194. package/dist/views/settings/useEmailSettings.d.ts +3 -2
  195. package/dist/views/settings/useEmailSettings.d.ts.map +1 -1
  196. package/dist/views/settings/useGeneralSettings.d.ts +2 -11
  197. package/dist/views/settings/useGeneralSettings.d.ts.map +1 -1
  198. package/dist/views/settings/useGeneralSettings.js +3 -3
  199. package/dist/views/settings/useGeneralSettings.js.map +1 -1
  200. package/dist/views/settings/useSeoSettings.d.ts +1 -1
  201. package/dist/views/settings/useSeoSettings.d.ts.map +1 -1
  202. package/dist/views/settings/useUpdates.d.ts +1 -3
  203. package/dist/views/settings/useUpdates.d.ts.map +1 -1
  204. package/dist/views/settings/useUpdates.js +1 -1
  205. package/dist/views/settings/useUpdates.js.map +1 -1
  206. package/package.json +4 -4
  207. package/src/__tests__/views/ai-settings.render.test.tsx +86 -2
  208. package/src/components/CoauthorResultPopover.tsx +3 -1
  209. package/src/components/CommentSidePanel.tsx +0 -1
  210. package/src/components/EditorRail.tsx +1 -1
  211. package/src/components/MediaPickerModal.tsx +1 -1
  212. package/src/components/TipTapEditor.tsx +2 -2
  213. package/src/components/forms/EntriesTable.tsx +1 -1
  214. package/src/components/pages/tag-badges.tsx +1 -1
  215. package/src/components/seo/FaqProposalPreview.tsx +2 -2
  216. package/src/components/seo/SeoCopilotDrawer.tsx +1 -1
  217. package/src/components/seo/SeoEditorPane.tsx +3 -1
  218. package/src/components/ui/Badge.tsx +4 -6
  219. package/src/components/ui/Button.tsx +1 -3
  220. package/src/components/ui/Card.tsx +1 -14
  221. package/src/components/ui/Input.tsx +7 -15
  222. package/src/components/ui/Modal.tsx +1 -1
  223. package/src/components/ui/ResizableAside.tsx +5 -5
  224. package/src/components/ui/StatusBadge.tsx +1 -3
  225. package/src/components/ui/Toggle.tsx +1 -3
  226. package/src/fields/FieldRenderer.tsx +1 -1
  227. package/src/fields/index.ts +0 -17
  228. package/src/layout/primitives/tokens.ts +0 -7
  229. package/src/lib/brand-voice-client.ts +18 -1
  230. package/src/lib/coauthor-client.ts +1 -1
  231. package/src/lib/collection-schema.ts +2 -2
  232. package/src/lib/editor-seo-content.ts +0 -22
  233. package/src/lib/forms-service.ts +2 -42
  234. package/src/lib/notifications-client.ts +3 -3
  235. package/src/lib/page-audit-input.ts +1 -1
  236. package/src/lib/page-editor-service.ts +1 -3
  237. package/src/lib/pages-service.ts +4 -29
  238. package/src/lib/plan.ts +1 -1
  239. package/src/lib/post-editor-service.ts +4 -10
  240. package/src/lib/post-types-service.ts +2 -2
  241. package/src/lib/posts-service.ts +1 -1
  242. package/src/lib/preview-link.ts +0 -14
  243. package/src/lib/rail-width.ts +1 -1
  244. package/src/lib/reflow-probe.ts +4 -4
  245. package/src/lib/seo-service.ts +20 -60
  246. package/src/lib/theme-chrome.ts +1 -1
  247. package/src/lib/version-history.ts +2 -2
  248. package/src/lib/workflow-chrome.ts +3 -3
  249. package/src/views/Login.tsx +1 -1
  250. package/src/views/MediaBrowser.tsx +3 -2
  251. package/src/views/PageEditor.tsx +1 -1
  252. package/src/views/Pages/PagesListView.tsx +1 -1
  253. package/src/views/Pages.tsx +1 -1
  254. package/src/views/Redirects.tsx +1 -1
  255. package/src/views/page-editor/section-types.ts +0 -9
  256. package/src/views/page-editor/sections/admin-renderers.tsx +1 -1
  257. package/src/views/page-editor/theme-chrome/index.ts +2 -10
  258. package/src/views/page-editor/viewports.ts +0 -3
  259. package/src/views/settings/AISettingsTab.tsx +15 -3
  260. package/src/views/settings/BrandVoiceCard.tsx +11 -1
  261. package/src/views/settings/brand-voice/BrandVoiceAlignmentPanel.tsx +3 -1
  262. package/src/views/settings/featureModel.ts +2 -2
  263. package/src/views/settings/useAiSettings.ts +8 -3
  264. package/src/views/settings/useAppearanceSettings.ts +4 -6
  265. package/src/views/settings/useEmailSettings.ts +2 -2
  266. package/src/views/settings/useGeneralSettings.ts +5 -10
  267. package/src/views/settings/useSeoSettings.ts +1 -1
  268. package/src/views/settings/useUpdates.ts +2 -2
@@ -24,7 +24,7 @@ import { FolderTree, type FolderSelection } from '../components/FolderTree.js'
24
24
 
25
25
  type PageSortKey = 'title' | 'author' | 'template' | 'status' | 'date'
26
26
 
27
- export interface PagesProps {
27
+ interface PagesProps {
28
28
  onNavigate?: (path: string) => void
29
29
  }
30
30
 
@@ -22,7 +22,7 @@ import { cmsApi } from '../lib/api.js'
22
22
 
23
23
  type RedirectSortKey = 'from' | 'to' | 'type' | 'hits' | 'active'
24
24
 
25
- export interface RedirectsProps {
25
+ interface RedirectsProps {
26
26
  onNavigate?: (path: string) => void
27
27
  }
28
28
 
@@ -10,27 +10,18 @@ export {
10
10
  SECTION_TYPES,
11
11
  SECTION_TYPE_LIST,
12
12
  getSectionType,
13
- getSectionTypesForScope,
14
13
  getAddableSectionTypesForScope,
15
- isSectionTypeAddable,
16
14
  isSectionTypeHideable,
17
15
  isSectionEffectivelyVisible,
18
- sectionTypeLabel,
19
- isKnownSectionType,
20
16
  resolveRepeaterField,
21
- isRepeaterField,
22
17
  blankRepeaterRow,
23
18
  } from '@prenta/core/page-sections'
24
19
 
25
20
  export type {
26
21
  SectionTypeId,
27
22
  SectionScope,
28
- SectionFieldType,
29
23
  SectionFieldDef,
30
24
  SectionSettings,
31
- SectionTypeDef,
32
25
  ResolvedRepeater,
33
- RepeaterRow,
34
26
  PostHeaderConfig,
35
- PostTemplate,
36
27
  } from '@prenta/core/page-sections'
@@ -62,7 +62,7 @@ export interface AdminSectionPostContext {
62
62
  * Paint budget to protect, but sections-react renderers call `claim()` during
63
63
  * render, so the shape must exist or a real renderer throws on its first image.
64
64
  */
65
- export interface AdminLcpController {
65
+ interface AdminLcpController {
66
66
  claim(): boolean
67
67
  readonly claimed: boolean
68
68
  }
@@ -1,12 +1,4 @@
1
- export {
2
- ThemeChromeProvider,
3
- useThemeChromeSpec,
4
- useThemeChromeContext,
5
- } from './ThemeChromeProvider.js'
1
+ export { ThemeChromeProvider, useThemeChromeContext } from './ThemeChromeProvider.js'
6
2
  export { ThemeChromeFrame } from './ThemeChromeFrame.js'
7
- export type { ThemeChromeFrameProps, ThemeChromeHealthState } from './ThemeChromeFrame.js'
3
+
8
4
  export { ThemeChromeHealth } from './ThemeChromeHealth.js'
9
- export type { ThemeChromeHealthProps } from './ThemeChromeHealth.js'
10
- export { ThemeChromeModeToggle } from './ThemeChromeModeToggle.js'
11
- export type { ThemeChromeModeToggleProps } from './ThemeChromeModeToggle.js'
12
- export { useThemeChromeMode } from './useThemeChromeMode.js'
@@ -5,9 +5,6 @@
5
5
  * mid-migration; import from `../../lib/breakpoints.js` in new code.
6
6
  */
7
7
  export {
8
- BREAKPOINTS as VIEWPORTS,
9
8
  DEFAULT_BREAKPOINT as DEFAULT_VIEWPORT,
10
- getBreakpoint as getViewport,
11
- type Breakpoint as Viewport,
12
9
  type BreakpointId as ViewportId,
13
10
  } from '../../lib/breakpoints.js'
@@ -54,7 +54,14 @@ export function AISettingsTab({ role, config }: AISettingsTabProps) {
54
54
  'Connect an AI provider under Provider connections before generating a brand voice profile.',
55
55
  }
56
56
  }
57
- if (!ai.form.defaultModelId) {
57
+ // The runtime resolves the provider's curated default when no default
58
+ // model is set. Only built-in providers have a curated catalog — a
59
+ // `custom` endpoint cannot supply that default, so a custom-only site (or
60
+ // one with no keyed built-in connection) still needs an explicit model.
61
+ const hasKeyedProvider = ai.providers.some(
62
+ (p) => p.enabled && p.hasKey && p.provider !== 'custom',
63
+ )
64
+ if (!ai.form.defaultModelId && !hasKeyedProvider) {
58
65
  return {
59
66
  ready: false,
60
67
  message:
@@ -67,8 +74,13 @@ export function AISettingsTab({ role, config }: AISettingsTabProps) {
67
74
  message: 'Save your AI settings before generating a brand voice profile.',
68
75
  }
69
76
  }
70
- return { ready: true }
71
- }, [ai.dirty, ai.form.defaultModelId, ai.form.features, ai.providers.length])
77
+ return {
78
+ ready: true,
79
+ ...(ai.form.defaultModelId
80
+ ? {}
81
+ : { note: 'No default model selected — using the provider’s recommended default.' }),
82
+ }
83
+ }, [ai.dirty, ai.form.defaultModelId, ai.form.features, ai.providers])
72
84
 
73
85
  if (ai.loading) {
74
86
  return (
@@ -9,7 +9,7 @@ import {
9
9
  type BrandAlignmentScoreResult,
10
10
  type BrandVoiceStructuredProfile,
11
11
  } from '@prenta/core/brand-voice'
12
- import { AlertTriangle, Download, FileUp, Link2, Loader2, Sparkles } from 'lucide-react'
12
+ import { AlertTriangle, Download, FileUp, Info, Link2, Loader2, Sparkles } from 'lucide-react'
13
13
  import { useCallback, useId, useMemo, useRef, useState } from 'react'
14
14
  import { toast } from 'sonner'
15
15
  import { BrandVoiceAlignmentPanel } from './brand-voice/BrandVoiceAlignmentPanel.js'
@@ -95,7 +95,10 @@ function downloadTextFile(filename: string, contents: string) {
95
95
  */
96
96
  export interface BrandVoiceAiReadiness {
97
97
  ready: boolean
98
+ /** Why AI generation is blocked (rendered as a warning when `ready` is false). */
98
99
  message?: string
100
+ /** Informational context when ready — e.g. running on the provider's default model. */
101
+ note?: string
99
102
  }
100
103
 
101
104
  export function BrandVoiceCard({
@@ -348,6 +351,13 @@ export function BrandVoiceCard({
348
351
  </div>
349
352
  )}
350
353
 
354
+ {aiReadiness?.ready && aiReadiness.note && (
355
+ <p className="text-muted-foreground flex items-start gap-2 text-sm">
356
+ <Info size={16} className="mt-0.5 shrink-0" aria-hidden="true" />
357
+ <span>{aiReadiness.note}</span>
358
+ </p>
359
+ )}
360
+
351
361
  <div className="flex flex-wrap items-center gap-2">
352
362
  <button
353
363
  type="button"
@@ -5,7 +5,7 @@ import { BRAND_VOICE_CHANNEL_PRESETS } from '@prenta/core/brand-voice'
5
5
  import { Loader2, Wand2 } from 'lucide-react'
6
6
  import { useId, useState } from 'react'
7
7
  import { toast } from 'sonner'
8
- import { fixBrandVoiceContent } from '../../../lib/brand-voice-client.js'
8
+ import { brandVoiceFixAiNotice, fixBrandVoiceContent } from '../../../lib/brand-voice-client.js'
9
9
 
10
10
  const INPUT_CLASS =
11
11
  'w-full rounded-md border border-border bg-input-background px-3 py-2 text-base text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-60'
@@ -78,6 +78,8 @@ export function BrandVoiceAlignmentPanel({
78
78
  ? 'Copy revised to match brand voice.'
79
79
  : 'Applied brand voice rule fixes.',
80
80
  )
81
+ const notice = brandVoiceFixAiNotice(res.result)
82
+ if (notice) toast.info(notice)
81
83
  } finally {
82
84
  setFixing(false)
83
85
  }
@@ -21,13 +21,13 @@ const CAPABILITY_LABELS: Record<string, string> = {
21
21
  longContext: 'long context',
22
22
  }
23
23
 
24
- export function findModel(models: AiModelView[], id: string | undefined): AiModelView | undefined {
24
+ function findModel(models: AiModelView[], id: string | undefined): AiModelView | undefined {
25
25
  if (!id) return undefined
26
26
  return models.find((m) => m.id === id || m.providerModelId === id)
27
27
  }
28
28
 
29
29
  /** Required capabilities a model is missing. Unknown capability strings are ignored. */
30
- export function missingCapabilities(model: AiModelView | undefined, required: string[]): string[] {
30
+ function missingCapabilities(model: AiModelView | undefined, required: string[]): string[] {
31
31
  const wanted = required.filter((c) => c in CAPABILITY_LABELS)
32
32
  if (!model) return wanted
33
33
  return wanted.filter((c) => !model.capabilities?.[c])
@@ -31,7 +31,7 @@ export type AiConnectionStatus =
31
31
  | 'provider_down'
32
32
  | 'needs_setup'
33
33
  | 'unknown'
34
- export type AiModelStatus = 'available' | 'unavailable' | 'deprecated' | 'unknown'
34
+ type AiModelStatus = 'available' | 'unavailable' | 'deprecated' | 'unknown'
35
35
 
36
36
  export interface AiModelView {
37
37
  id: string
@@ -147,7 +147,7 @@ const EMPTY_BRAND_VOICE: BrandVoiceForm = {
147
147
  }
148
148
 
149
149
  /** The buffered (Save-bar) portion of AI settings. */
150
- export interface AiSettingsForm {
150
+ interface AiSettingsForm {
151
151
  defaultProviderId: string
152
152
  defaultModelId: string
153
153
  features: AiFeatureView[]
@@ -453,7 +453,12 @@ export function useAiSettings(canEdit: boolean): UseAiSettings {
453
453
  method: 'POST',
454
454
  body: JSON.stringify(input),
455
455
  })
456
- if (res.error) return { ok: false, error: res.error }
456
+ if (res.error) {
457
+ // 503 "not configured" bodies carry the runtime's setup `hint`
458
+ // separately from `error` (same contract as every other AI route).
459
+ const hint = (res.errorBody as { hint?: unknown } | undefined)?.hint
460
+ return { ok: false, error: typeof hint === 'string' ? `${res.error} ${hint}` : res.error }
461
+ }
457
462
  if (!res.data?.draft) return { ok: false, error: 'No draft profile was returned.' }
458
463
  const structured = res.data.draft.document?.trim()
459
464
  ? parseStructuredProfile(res.data.draft.document)
@@ -30,10 +30,10 @@ import { cmsApi } from '../../lib/api.js'
30
30
  import { BRAND_UPDATED_EVENT } from '../../lib/favicon.js'
31
31
  import type { SettingsSourceMetadata, ValidationIssue } from './useGeneralSettings.js'
32
32
 
33
- export type SaveState = 'idle' | 'saving' | 'success' | 'error'
33
+ type SaveState = 'idle' | 'saving' | 'success' | 'error'
34
34
 
35
35
  /** Theme + typography + motion form (the `appearance` settings blob). */
36
- export interface AppearanceForm {
36
+ interface AppearanceForm {
37
37
  colorMode: ColorMode
38
38
  accent: string
39
39
  density: SidebarDensity
@@ -42,7 +42,7 @@ export interface AppearanceForm {
42
42
  }
43
43
 
44
44
  /** Brand asset references + admin branding mode (the `brand` settings blob). */
45
- export interface BrandForm {
45
+ interface BrandForm {
46
46
  mode: AdminBrandingMode
47
47
  publicPrimaryLogoAssetId?: string
48
48
  publicLogoMarkAssetId?: string
@@ -64,7 +64,7 @@ export type BrandAssetField =
64
64
  | 'adminDarkLogoAssetId'
65
65
  | 'faviconSourceAssetId'
66
66
 
67
- export const FIELD_ROLE: Record<BrandAssetField, BrandAssetRole> = {
67
+ const FIELD_ROLE: Record<BrandAssetField, BrandAssetRole> = {
68
68
  publicPrimaryLogoAssetId: 'publicPrimaryLogo',
69
69
  publicLogoMarkAssetId: 'publicLogoMark',
70
70
  publicDarkLogoAssetId: 'publicDarkLogo',
@@ -74,8 +74,6 @@ export const FIELD_ROLE: Record<BrandAssetField, BrandAssetRole> = {
74
74
  faviconSourceAssetId: 'faviconSource',
75
75
  }
76
76
 
77
- export type { AssetPreview } from '../../lib/appearance-api.js'
78
-
79
77
  /** An existing media-library asset chosen for a brand field via the picker. */
80
78
  export interface SelectedBrandAsset {
81
79
  id: string
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
5
5
  import { cmsApi } from '../../lib/api.js'
6
6
 
7
7
  export type EmailProviderKey = 'resend' | 'smtpcom' | 'ses' | 'smtp'
8
- export type SaveState = 'idle' | 'saving' | 'success' | 'error'
8
+ type SaveState = 'idle' | 'saving' | 'success' | 'error'
9
9
 
10
10
  export const EMAIL_PROVIDER_OPTIONS: Array<{ value: EmailProviderKey; label: string }> = [
11
11
  { value: 'resend', label: 'Resend' },
@@ -15,7 +15,7 @@ export const EMAIL_PROVIDER_OPTIONS: Array<{ value: EmailProviderKey; label: str
15
15
  ]
16
16
 
17
17
  /** Form state. Secrets are write-only: inputs start empty and never echo stored values. */
18
- export interface EmailSettingsForm {
18
+ interface EmailSettingsForm {
19
19
  /** Empty string = email delivery not configured here. */
20
20
  provider: EmailProviderKey | ''
21
21
  from: string
@@ -16,7 +16,7 @@ export interface GeneralSettingsForm {
16
16
  noIndexNonProduction: boolean
17
17
  }
18
18
 
19
- export type SettingsSource = 'config' | 'environment' | 'database' | 'plugin' | 'computed'
19
+ type SettingsSource = 'config' | 'environment' | 'database' | 'plugin' | 'computed'
20
20
 
21
21
  export interface SettingsSourceMetadata {
22
22
  source: SettingsSource
@@ -24,12 +24,7 @@ export interface SettingsSourceMetadata {
24
24
  reason?: string
25
25
  }
26
26
 
27
- export type IndexStatusLevel =
28
- | 'indexable'
29
- | 'protected'
30
- | 'blocked'
31
- | 'partially_blocked'
32
- | 'unknown'
27
+ type IndexStatusLevel = 'indexable' | 'protected' | 'blocked' | 'partially_blocked' | 'unknown'
33
28
 
34
29
  export interface SiteIndexStatus {
35
30
  status: IndexStatusLevel
@@ -69,7 +64,7 @@ const EMPTY_FORM: GeneralSettingsForm = {
69
64
  }
70
65
 
71
66
  /** Validate a site URL: absolute http(s), no trailing slash, parseable. */
72
- export function validateSiteUrl(value: string): { valid: boolean; error?: string } {
67
+ function validateSiteUrl(value: string): { valid: boolean; error?: string } {
73
68
  const trimmed = value.trim()
74
69
  if (!trimmed) return { valid: true }
75
70
  let url: URL
@@ -88,7 +83,7 @@ export function validateSiteUrl(value: string): { valid: boolean; error?: string
88
83
  }
89
84
 
90
85
  /** Best-effort IANA timezone check using the Intl API. */
91
- export function isValidTimeZone(tz: string): boolean {
86
+ function isValidTimeZone(tz: string): boolean {
92
87
  if (!tz) return false
93
88
  try {
94
89
  new Intl.DateTimeFormat('en-US', { timeZone: tz })
@@ -99,7 +94,7 @@ export function isValidTimeZone(tz: string): boolean {
99
94
  }
100
95
 
101
96
  /** Best-effort BCP 47 locale check. */
102
- export function isValidLocale(locale: string): boolean {
97
+ function isValidLocale(locale: string): boolean {
103
98
  if (!locale) return false
104
99
  try {
105
100
  // Throws RangeError on structurally invalid tags.
@@ -49,7 +49,7 @@ export interface SeoSettingsForm {
49
49
  organizationSameAs: string
50
50
  }
51
51
 
52
- export interface SitemapSummary {
52
+ interface SitemapSummary {
53
53
  sitemapUrl: string
54
54
  lastGeneratedAt: string | null
55
55
  pagesIncluded: number
@@ -20,7 +20,7 @@ export interface UpdateChangelogItem {
20
20
  type?: UpdateChangeType
21
21
  }
22
22
 
23
- export interface UpdatePluginCompatibility {
23
+ interface UpdatePluginCompatibility {
24
24
  name: string
25
25
  compatible: boolean
26
26
  minVersion?: string
@@ -52,7 +52,7 @@ interface GithubForm {
52
52
  const REPO_PATTERN = /^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/
53
53
 
54
54
  /** Admin/Owner may manage updates (matches the server's `requireAdminScope`). */
55
- export function canManageUpdates(role: string | null | undefined): boolean {
55
+ function canManageUpdates(role: string | null | undefined): boolean {
56
56
  const r = (role ?? '').toUpperCase()
57
57
  return r === 'ADMIN' || r === 'OWNER'
58
58
  }