@actuate-media/cms-admin 0.73.0 → 0.75.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 (209) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +3 -2
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/LoginRoot.d.ts.map +1 -1
  6. package/dist/LoginRoot.js +2 -1
  7. package/dist/LoginRoot.js.map +1 -1
  8. package/dist/__tests__/components/theme-provider-accent.test.d.ts +2 -0
  9. package/dist/__tests__/components/theme-provider-accent.test.d.ts.map +1 -0
  10. package/dist/__tests__/components/theme-provider-accent.test.js +64 -0
  11. package/dist/__tests__/components/theme-provider-accent.test.js.map +1 -0
  12. package/dist/__tests__/views/portal-container.test.js +13 -0
  13. package/dist/__tests__/views/portal-container.test.js.map +1 -1
  14. package/dist/__tests__/views/post-fields-modal.render.test.js +3 -6
  15. package/dist/__tests__/views/post-fields-modal.render.test.js.map +1 -1
  16. package/dist/__tests__/views/redirects-pane.render.test.js +6 -0
  17. package/dist/__tests__/views/redirects-pane.render.test.js.map +1 -1
  18. package/dist/__tests__/views/seo-content-pane.render.test.js +15 -4
  19. package/dist/__tests__/views/seo-content-pane.render.test.js.map +1 -1
  20. package/dist/actuate-admin.css +1 -1
  21. package/dist/components/CoauthorResultPopover.js +1 -1
  22. package/dist/components/EditorSeoAside.d.ts +23 -0
  23. package/dist/components/EditorSeoAside.d.ts.map +1 -0
  24. package/dist/components/EditorSeoAside.js +21 -0
  25. package/dist/components/EditorSeoAside.js.map +1 -0
  26. package/dist/components/MediaPickerModal.js +1 -1
  27. package/dist/components/SEOConfigPanel.js +1 -1
  28. package/dist/components/SchedulePublishDialog.js +1 -1
  29. package/dist/components/SharePreviewLinkDialog.js +1 -1
  30. package/dist/components/ThemeProvider.d.ts.map +1 -1
  31. package/dist/components/ThemeProvider.js +2 -1
  32. package/dist/components/ThemeProvider.js.map +1 -1
  33. package/dist/components/VersionHistory.js +1 -1
  34. package/dist/components/seo/EditorSeoScoreBadge.d.ts +7 -0
  35. package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -0
  36. package/dist/components/seo/EditorSeoScoreBadge.js +9 -0
  37. package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -0
  38. package/dist/components/seo/SeoAccordion.d.ts +16 -0
  39. package/dist/components/seo/SeoAccordion.d.ts.map +1 -0
  40. package/dist/components/seo/SeoAccordion.js +21 -0
  41. package/dist/components/seo/SeoAccordion.js.map +1 -0
  42. package/dist/components/seo/SeoAnalysisList.d.ts +7 -0
  43. package/dist/components/seo/SeoAnalysisList.d.ts.map +1 -0
  44. package/dist/components/seo/SeoAnalysisList.js +32 -0
  45. package/dist/components/seo/SeoAnalysisList.js.map +1 -0
  46. package/dist/components/seo/SeoEditorPane.d.ts +15 -4
  47. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
  48. package/dist/components/seo/SeoEditorPane.js +155 -35
  49. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  50. package/dist/components/seo/SeoInsightsMetrics.d.ts +6 -0
  51. package/dist/components/seo/SeoInsightsMetrics.d.ts.map +1 -0
  52. package/dist/components/seo/SeoInsightsMetrics.js +16 -0
  53. package/dist/components/seo/SeoInsightsMetrics.js.map +1 -0
  54. package/dist/components/seo/SeoIssueFixPanel.d.ts.map +1 -1
  55. package/dist/components/seo/SeoIssueFixPanel.js +13 -3
  56. package/dist/components/seo/SeoIssueFixPanel.js.map +1 -1
  57. package/dist/components/seo/SeoLinkingSection.d.ts +9 -0
  58. package/dist/components/seo/SeoLinkingSection.d.ts.map +1 -0
  59. package/dist/components/seo/SeoLinkingSection.js +44 -0
  60. package/dist/components/seo/SeoLinkingSection.js.map +1 -0
  61. package/dist/components/seo/primitives.d.ts.map +1 -1
  62. package/dist/components/seo/primitives.js +3 -3
  63. package/dist/components/seo/primitives.js.map +1 -1
  64. package/dist/components/ui/Card.js +5 -5
  65. package/dist/components/ui/Card.js.map +1 -1
  66. package/dist/components/ui/InspectorPane.js +3 -3
  67. package/dist/components/ui/InspectorPane.js.map +1 -1
  68. package/dist/components/ui/PanelSection.d.ts +16 -0
  69. package/dist/components/ui/PanelSection.d.ts.map +1 -0
  70. package/dist/components/ui/PanelSection.js +15 -0
  71. package/dist/components/ui/PanelSection.js.map +1 -0
  72. package/dist/lib/admin-root-element.d.ts +9 -0
  73. package/dist/lib/admin-root-element.d.ts.map +1 -0
  74. package/dist/lib/admin-root-element.js +14 -0
  75. package/dist/lib/admin-root-element.js.map +1 -0
  76. package/dist/lib/editor-seo-content.d.ts +16 -0
  77. package/dist/lib/editor-seo-content.d.ts.map +1 -0
  78. package/dist/lib/editor-seo-content.js +139 -0
  79. package/dist/lib/editor-seo-content.js.map +1 -0
  80. package/dist/lib/portal-container.d.ts.map +1 -1
  81. package/dist/lib/portal-container.js +2 -3
  82. package/dist/lib/portal-container.js.map +1 -1
  83. package/dist/lib/seo-service.d.ts +91 -0
  84. package/dist/lib/seo-service.d.ts.map +1 -1
  85. package/dist/lib/seo-service.js +55 -2
  86. package/dist/lib/seo-service.js.map +1 -1
  87. package/dist/views/ApiKeys.js +2 -2
  88. package/dist/views/Dashboard.d.ts.map +1 -1
  89. package/dist/views/Dashboard.js +12 -14
  90. package/dist/views/Dashboard.js.map +1 -1
  91. package/dist/views/MediaBrowser.d.ts.map +1 -1
  92. package/dist/views/MediaBrowser.js +35 -6
  93. package/dist/views/MediaBrowser.js.map +1 -1
  94. package/dist/views/PageEditor.js +1 -1
  95. package/dist/views/Posts/PostTypeEditor.js +3 -3
  96. package/dist/views/SEO.d.ts.map +1 -1
  97. package/dist/views/SEO.js +4 -2
  98. package/dist/views/SEO.js.map +1 -1
  99. package/dist/views/ScriptTags.js +1 -1
  100. package/dist/views/ScriptTags.js.map +1 -1
  101. package/dist/views/Users.js +1 -1
  102. package/dist/views/page-editor/EditorTopBar.d.ts +6 -1
  103. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  104. package/dist/views/page-editor/EditorTopBar.js +5 -2
  105. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  106. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  107. package/dist/views/page-editor/PageSectionEditor.js +36 -1
  108. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  109. package/dist/views/page-editor/PageSettingsModal.d.ts +1 -1
  110. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -1
  111. package/dist/views/page-editor/PageSettingsModal.js +2 -13
  112. package/dist/views/page-editor/PageSettingsModal.js.map +1 -1
  113. package/dist/views/page-editor/SectionInspector.js +2 -2
  114. package/dist/views/page-editor/SectionsPanel.js +1 -1
  115. package/dist/views/post-editor/PostFieldsModal.d.ts +1 -1
  116. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
  117. package/dist/views/post-editor/PostFieldsModal.js +17 -79
  118. package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
  119. package/dist/views/post-editor/PostHeaderPanel.js +1 -1
  120. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  121. package/dist/views/post-editor/PostSectionEditor.js +37 -1
  122. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  123. package/dist/views/seo/ContentTab.d.ts.map +1 -1
  124. package/dist/views/seo/ContentTab.js +7 -1
  125. package/dist/views/seo/ContentTab.js.map +1 -1
  126. package/dist/views/seo/LinksTab.d.ts +4 -0
  127. package/dist/views/seo/LinksTab.d.ts.map +1 -0
  128. package/dist/views/seo/LinksTab.js +111 -0
  129. package/dist/views/seo/LinksTab.js.map +1 -0
  130. package/dist/views/seo/OverviewTab.js +1 -1
  131. package/dist/views/seo/OverviewTab.js.map +1 -1
  132. package/dist/views/seo/RedirectsTab.d.ts.map +1 -1
  133. package/dist/views/seo/RedirectsTab.js +39 -6
  134. package/dist/views/seo/RedirectsTab.js.map +1 -1
  135. package/dist/views/seo/TechnicalTab.js +2 -2
  136. package/dist/views/settings/AISettingsTab.js +1 -1
  137. package/dist/views/settings/AiFeaturesCard.js +1 -1
  138. package/dist/views/settings/AiFeaturesCard.js.map +1 -1
  139. package/dist/views/settings/AppearanceTab.js +1 -1
  140. package/dist/views/settings/BrandVoiceCard.js +1 -1
  141. package/dist/views/settings/EmailSettingsTab.js +2 -2
  142. package/dist/views/settings/GeneralSettingsTab.js +1 -1
  143. package/dist/views/settings/SecurityStatusCard.js +1 -1
  144. package/dist/views/settings/SeoSettingsTab.js +4 -4
  145. package/dist/views/settings/UpdatesTab.js +2 -2
  146. package/dist/views/settings/components.js +3 -3
  147. package/dist/views/settings/components.js.map +1 -1
  148. package/package.json +2 -2
  149. package/src/AdminRoot.tsx +3 -2
  150. package/src/LoginRoot.tsx +2 -1
  151. package/src/__tests__/components/theme-provider-accent.test.tsx +84 -0
  152. package/src/__tests__/views/portal-container.test.tsx +14 -0
  153. package/src/__tests__/views/post-fields-modal.render.test.tsx +3 -9
  154. package/src/__tests__/views/redirects-pane.render.test.tsx +6 -0
  155. package/src/__tests__/views/seo-content-pane.render.test.tsx +16 -4
  156. package/src/components/CoauthorResultPopover.tsx +1 -1
  157. package/src/components/EditorSeoAside.tsx +68 -0
  158. package/src/components/MediaPickerModal.tsx +1 -1
  159. package/src/components/SEOConfigPanel.tsx +2 -2
  160. package/src/components/SchedulePublishDialog.tsx +1 -1
  161. package/src/components/SharePreviewLinkDialog.tsx +1 -1
  162. package/src/components/ThemeProvider.tsx +3 -1
  163. package/src/components/VersionHistory.tsx +1 -1
  164. package/src/components/seo/EditorSeoScoreBadge.tsx +26 -0
  165. package/src/components/seo/SeoAccordion.tsx +76 -0
  166. package/src/components/seo/SeoAnalysisList.tsx +113 -0
  167. package/src/components/seo/SeoEditorPane.tsx +532 -250
  168. package/src/components/seo/SeoInsightsMetrics.tsx +109 -0
  169. package/src/components/seo/SeoIssueFixPanel.tsx +16 -4
  170. package/src/components/seo/SeoLinkingSection.tsx +151 -0
  171. package/src/components/seo/primitives.tsx +7 -3
  172. package/src/components/ui/Card.tsx +5 -5
  173. package/src/components/ui/InspectorPane.tsx +3 -3
  174. package/src/components/ui/PanelSection.tsx +53 -0
  175. package/src/lib/admin-root-element.ts +15 -0
  176. package/src/lib/editor-seo-content.ts +144 -0
  177. package/src/lib/portal-container.ts +3 -2
  178. package/src/lib/seo-service.ts +150 -2
  179. package/src/views/ApiKeys.tsx +2 -2
  180. package/src/views/Dashboard.tsx +40 -53
  181. package/src/views/MediaBrowser.tsx +62 -12
  182. package/src/views/PageEditor.tsx +1 -1
  183. package/src/views/Posts/PostTypeEditor.tsx +3 -3
  184. package/src/views/SEO.tsx +6 -0
  185. package/src/views/ScriptTags.tsx +2 -2
  186. package/src/views/Users.tsx +1 -1
  187. package/src/views/page-editor/EditorTopBar.tsx +27 -0
  188. package/src/views/page-editor/PageSectionEditor.tsx +58 -3
  189. package/src/views/page-editor/PageSettingsModal.tsx +1 -45
  190. package/src/views/page-editor/SectionInspector.tsx +2 -2
  191. package/src/views/page-editor/SectionsPanel.tsx +1 -1
  192. package/src/views/post-editor/PostFieldsModal.tsx +19 -138
  193. package/src/views/post-editor/PostHeaderPanel.tsx +1 -1
  194. package/src/views/post-editor/PostSectionEditor.tsx +60 -3
  195. package/src/views/seo/ContentTab.tsx +11 -5
  196. package/src/views/seo/LinksTab.tsx +462 -0
  197. package/src/views/seo/OverviewTab.tsx +1 -1
  198. package/src/views/seo/RedirectsTab.tsx +97 -4
  199. package/src/views/seo/TechnicalTab.tsx +2 -2
  200. package/src/views/settings/AISettingsTab.tsx +3 -3
  201. package/src/views/settings/AiFeaturesCard.tsx +1 -1
  202. package/src/views/settings/AppearanceTab.tsx +1 -1
  203. package/src/views/settings/BrandVoiceCard.tsx +2 -2
  204. package/src/views/settings/EmailSettingsTab.tsx +2 -2
  205. package/src/views/settings/GeneralSettingsTab.tsx +3 -3
  206. package/src/views/settings/SecurityStatusCard.tsx +1 -1
  207. package/src/views/settings/SeoSettingsTab.tsx +6 -6
  208. package/src/views/settings/UpdatesTab.tsx +3 -3
  209. package/src/views/settings/components.tsx +3 -3
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useRef, useState } from 'react'
3
+ import { useEffect, useRef, useState } from 'react'
4
4
  import {
5
5
  Plus,
6
6
  Trash2,
@@ -22,11 +22,14 @@ import {
22
22
  deleteRedirectRule,
23
23
  flattenRedirectChain,
24
24
  generate404RecoverySuggestions,
25
+ fetchRedirectRecoverySettings,
26
+ saveRedirectRecoverySettings,
25
27
  acceptRedirectSuggestion,
26
28
  dismissRedirectSuggestion,
27
29
  exportRedirectsCsv,
28
30
  importRedirectsCsv,
29
31
  type RedirectRule,
32
+ type RedirectRecoverySettings,
30
33
  } from '../../lib/seo-service.js'
31
34
  import {
32
35
  SectionCard,
@@ -66,6 +69,91 @@ function OverviewCards({ onRefetchKey }: { onRefetchKey: number }) {
66
69
  )
67
70
  }
68
71
 
72
+ function RecoverySettingsCard() {
73
+ const { data, loading, error, refetch } = useSeoResource(fetchRedirectRecoverySettings, [])
74
+ const [saving, setSaving] = useState(false)
75
+ const [local, setLocal] = useState<RedirectRecoverySettings | null>(null)
76
+
77
+ useEffect(() => {
78
+ if (data) setLocal(data)
79
+ }, [data])
80
+
81
+ async function handleSave() {
82
+ if (!local) return
83
+ setSaving(true)
84
+ const res = await saveRedirectRecoverySettings(local)
85
+ setSaving(false)
86
+ if (res.error) return toast.error(res.error)
87
+ toast.success('Recovery settings saved.')
88
+ refetch()
89
+ }
90
+
91
+ if (loading) return null
92
+ if (error) return null
93
+ if (!local) return null
94
+
95
+ return (
96
+ <SectionCard
97
+ title="404 recovery settings"
98
+ description="Control AI re-ranking and optional auto-redirect for high-confidence matches"
99
+ >
100
+ <div className="space-y-4">
101
+ <label className="flex items-start gap-3">
102
+ <input
103
+ type="checkbox"
104
+ className="border-border mt-1 h-4 w-4 rounded"
105
+ checked={local.aiRerank}
106
+ onChange={(e) => setLocal({ ...local, aiRerank: e.target.checked })}
107
+ />
108
+ <span>
109
+ <span className="text-foreground text-sm font-medium">AI re-ranking</span>
110
+ <span className="text-muted-foreground block text-sm">
111
+ Use semantic search to resolve ambiguous matches (40–84% heuristic confidence).
112
+ </span>
113
+ </span>
114
+ </label>
115
+ <label className="flex items-start gap-3">
116
+ <input
117
+ type="checkbox"
118
+ className="border-border mt-1 h-4 w-4 rounded"
119
+ checked={local.autoApply}
120
+ onChange={(e) => setLocal({ ...local, autoApply: e.target.checked })}
121
+ />
122
+ <span>
123
+ <span className="text-foreground text-sm font-medium">
124
+ Auto-apply high-confidence redirects
125
+ </span>
126
+ <span className="text-muted-foreground block text-sm">
127
+ Create 301 redirects automatically when confidence meets the threshold (off by
128
+ default).
129
+ </span>
130
+ </span>
131
+ </label>
132
+ <label className="block max-w-xs">
133
+ <span className="text-foreground text-sm font-medium">Auto-apply minimum confidence</span>
134
+ <input
135
+ type="number"
136
+ min={50}
137
+ max={100}
138
+ value={local.autoApplyMinConfidence}
139
+ disabled={!local.autoApply}
140
+ onChange={(e) =>
141
+ setLocal({
142
+ ...local,
143
+ autoApplyMinConfidence: Math.max(50, Math.min(100, Number(e.target.value) || 85)),
144
+ })
145
+ }
146
+ className="border-border bg-input-background text-foreground focus-visible:ring-ring mt-1 w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50"
147
+ />
148
+ </label>
149
+ <button className={btnSecondary} onClick={handleSave} disabled={saving}>
150
+ {saving ? 'Saving…' : 'Save settings'}
151
+ </button>
152
+ </div>
153
+ </SectionCard>
154
+ )
155
+ }
156
+
69
157
  function Ai404RecoveryCard({ onApplied }: { onApplied: () => void }) {
70
158
  const { data, loading, error, refetch } = useSeoResource(fetchRedirectSuggestions, [])
71
159
  const [generating, setGenerating] = useState(false)
@@ -78,7 +166,11 @@ function Ai404RecoveryCard({ onApplied }: { onApplied: () => void }) {
78
166
  toast.error(res.error)
79
167
  return
80
168
  }
81
- toast.success('Generated 404 recovery suggestions.')
169
+ const autoMsg =
170
+ res.autoApplied && res.autoApplied > 0
171
+ ? ` ${res.autoApplied} high-confidence redirect${res.autoApplied === 1 ? '' : 's'} applied automatically.`
172
+ : ''
173
+ toast.success(`Generated 404 recovery suggestions.${autoMsg}`)
82
174
  refetch()
83
175
  }
84
176
 
@@ -120,7 +212,7 @@ function Ai404RecoveryCard({ onApplied }: { onApplied: () => void }) {
120
212
  ) : (
121
213
  <ul className="divide-border divide-y">
122
214
  {data!.map((s) => (
123
- <li key={s.id} className="flex items-center justify-between gap-3 py-3 first:pt-0">
215
+ <li key={s.id} className="flex items-center justify-between gap-3 py-3">
124
216
  <div className="min-w-0">
125
217
  <p className="text-foreground flex items-center gap-2 text-sm">
126
218
  <span className="truncate font-medium">{s.fromPath}</span>
@@ -181,7 +273,7 @@ function ChainMapCard({ onFlattened }: { onFlattened: () => void }) {
181
273
  {data.map((chain) => (
182
274
  <li
183
275
  key={chain.id}
184
- className="border-border flex items-center justify-between gap-3 rounded-md border p-3"
276
+ className="border-border flex items-center justify-between gap-3 rounded-md border p-4"
185
277
  >
186
278
  <div className="min-w-0">
187
279
  <div className="mb-1 flex items-center gap-2">
@@ -272,6 +364,7 @@ export function RedirectsTab() {
272
364
  <OverviewCards onRefetchKey={refetchKey} />
273
365
 
274
366
  <div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
367
+ <RecoverySettingsCard />
275
368
  <Ai404RecoveryCard onApplied={refetchAll} />
276
369
  <ChainMapCard onFlattened={refetchAll} />
277
370
  </div>
@@ -73,7 +73,7 @@ function Toggle({
73
73
  const labelId = useId()
74
74
  const helpId = useId()
75
75
  return (
76
- <div className="flex items-center justify-between gap-3 py-2.5">
76
+ <div className="flex items-center justify-between gap-3 py-3.5">
77
77
  <span className="min-w-0">
78
78
  <span id={labelId} className="text-foreground block text-sm font-medium">
79
79
  {label}
@@ -259,7 +259,7 @@ function RobotsCard() {
259
259
  <div className="space-y-3">
260
260
  {validation.blocksEntireSite && (
261
261
  <div
262
- className="border-destructive/30 bg-destructive/10 flex items-start gap-2 rounded-md border p-3"
262
+ className="border-destructive/30 bg-destructive/10 flex items-start gap-2 rounded-md border p-4"
263
263
  role="alert"
264
264
  >
265
265
  <AlertTriangle className="text-destructive mt-0.5 h-4 w-4 shrink-0" aria-hidden />
@@ -92,7 +92,7 @@ export function AISettingsTab({ role, config }: AISettingsTabProps) {
92
92
  {ai.loadError && (
93
93
  <div
94
94
  role="alert"
95
- className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-3 text-sm"
95
+ className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-4 text-sm"
96
96
  >
97
97
  <AlertTriangle size={16} className="shrink-0" aria-hidden="true" />
98
98
  <span className="flex-1">{ai.loadError}</span>
@@ -107,7 +107,7 @@ export function AISettingsTab({ role, config }: AISettingsTabProps) {
107
107
  )}
108
108
 
109
109
  {!canEdit && (
110
- <div className="border-border bg-muted text-muted-foreground flex items-center gap-2 rounded-lg border p-3 text-sm">
110
+ <div className="border-border bg-muted text-muted-foreground flex items-center gap-2 rounded-lg border p-4 text-sm">
111
111
  <Info size={16} className="shrink-0" aria-hidden="true" />
112
112
  You have read-only access to AI settings. Only administrators can make changes.
113
113
  </div>
@@ -145,7 +145,7 @@ export function AISettingsTab({ role, config }: AISettingsTabProps) {
145
145
  {ai.saveState === 'error' && ai.saveError && (
146
146
  <div
147
147
  role="alert"
148
- className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-3 text-sm"
148
+ className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-4 text-sm"
149
149
  >
150
150
  <AlertTriangle size={16} className="shrink-0" aria-hidden="true" />
151
151
  <span className="flex-1">{ai.saveError}</span>
@@ -85,7 +85,7 @@ function FeatureRow({
85
85
  : 'Use default model'
86
86
 
87
87
  return (
88
- <div className="py-4 first:pt-0 last:pb-0">
88
+ <div className="py-4">
89
89
  <SettingsToggleRow
90
90
  label={feature.label}
91
91
  description={feature.description}
@@ -56,7 +56,7 @@ export function AppearanceTab({ role, config }: AppearanceTabProps) {
56
56
  return (
57
57
  <div className="space-y-4">
58
58
  {!canEdit && (
59
- <p className="text-muted-foreground border-border bg-muted rounded-lg border p-3 text-sm">
59
+ <p className="text-muted-foreground border-border bg-muted rounded-lg border p-4 text-sm">
60
60
  You have read-only access to appearance settings.
61
61
  </p>
62
62
  )}
@@ -341,7 +341,7 @@ export function BrandVoiceCard({
341
341
  {aiReadiness && !aiReadiness.ready && aiReadiness.message && (
342
342
  <div
343
343
  role="status"
344
- className="border-warning/40 bg-warning/10 text-foreground flex items-start gap-3 rounded-lg border p-3 text-sm"
344
+ className="border-warning/40 bg-warning/10 text-foreground flex items-start gap-3 rounded-lg border p-4 text-sm"
345
345
  >
346
346
  <AlertTriangle size={16} className="text-warning mt-0.5 shrink-0" aria-hidden="true" />
347
347
  <p>{aiReadiness.message}</p>
@@ -400,7 +400,7 @@ export function BrandVoiceCard({
400
400
  {importError && (
401
401
  <div
402
402
  role="alert"
403
- className="border-destructive/30 bg-destructive/10 text-destructive rounded-md border p-3 text-sm"
403
+ className="border-destructive/30 bg-destructive/10 text-destructive rounded-md border p-4 text-sm"
404
404
  >
405
405
  {importError}
406
406
  </div>
@@ -115,7 +115,7 @@ function Notice({
115
115
  <div
116
116
  role="status"
117
117
  aria-live="polite"
118
- className={`flex items-start gap-2.5 rounded-lg border p-3 text-sm ${toneClass}`}
118
+ className={`flex items-start gap-2.5 rounded-lg border p-4 text-sm ${toneClass}`}
119
119
  >
120
120
  <Icon size={16} className="mt-0.5 shrink-0" aria-hidden="true" />
121
121
  <div className="min-w-0">{children}</div>
@@ -294,7 +294,7 @@ export function EmailSettingsTab({ role }: { role?: string }) {
294
294
  return (
295
295
  <div
296
296
  role="alert"
297
- className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-3 text-sm"
297
+ className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-4 text-sm"
298
298
  >
299
299
  <AlertTriangle size={20} className="shrink-0" aria-hidden="true" />
300
300
  <span className="flex-1">{s.loadError}</span>
@@ -42,7 +42,7 @@ export function GeneralSettingsTab({ onNavigate, role }: GeneralSettingsTabProps
42
42
  {settings.loadError && (
43
43
  <div
44
44
  role="alert"
45
- className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-3 text-sm"
45
+ className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-4 text-sm"
46
46
  >
47
47
  <AlertTriangle size={16} className="shrink-0" aria-hidden="true" />
48
48
  <span className="flex-1">{settings.loadError}</span>
@@ -57,7 +57,7 @@ export function GeneralSettingsTab({ onNavigate, role }: GeneralSettingsTabProps
57
57
  )}
58
58
 
59
59
  {!canEdit && (
60
- <div className="border-border bg-muted text-muted-foreground flex items-center gap-2 rounded-lg border p-3 text-sm">
60
+ <div className="border-border bg-muted text-muted-foreground flex items-center gap-2 rounded-lg border p-4 text-sm">
61
61
  <Info size={16} className="shrink-0" aria-hidden="true" />
62
62
  You have read-only access to settings. Only administrators can make changes.
63
63
  </div>
@@ -99,7 +99,7 @@ export function GeneralSettingsTab({ onNavigate, role }: GeneralSettingsTabProps
99
99
  {settings.saveState === 'error' && settings.saveError && (
100
100
  <div
101
101
  role="alert"
102
- className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-3 text-sm"
102
+ className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-4 text-sm"
103
103
  >
104
104
  <AlertTriangle size={16} className="shrink-0" aria-hidden="true" />
105
105
  <span className="flex-1">{settings.saveError}</span>
@@ -83,7 +83,7 @@ export function SecurityStatusCard({
83
83
  <div
84
84
  key={w.id}
85
85
  role="note"
86
- className={`flex items-start justify-between gap-3 rounded-lg border p-3 text-sm ${SEVERITY_STYLES[w.severity]}`}
86
+ className={`flex items-start justify-between gap-3 rounded-lg border p-4 text-sm ${SEVERITY_STYLES[w.severity]}`}
87
87
  >
88
88
  <div>
89
89
  <p className="font-medium">{w.title}</p>
@@ -102,7 +102,7 @@ export function SeoSettingsTab({ onNavigate, role }: SeoSettingsTabProps) {
102
102
  {seo.loadError && (
103
103
  <div
104
104
  role="alert"
105
- className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-3 text-sm"
105
+ className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-4 text-sm"
106
106
  >
107
107
  <AlertTriangle size={16} className="shrink-0" aria-hidden="true" />
108
108
  <span className="flex-1">{seo.loadError}</span>
@@ -117,7 +117,7 @@ export function SeoSettingsTab({ onNavigate, role }: SeoSettingsTabProps) {
117
117
  )}
118
118
 
119
119
  {!canEdit && (
120
- <div className="border-border bg-muted text-muted-foreground flex items-center gap-2 rounded-lg border p-3 text-sm">
120
+ <div className="border-border bg-muted text-muted-foreground flex items-center gap-2 rounded-lg border p-4 text-sm">
121
121
  <Info size={16} className="shrink-0" aria-hidden="true" />
122
122
  You have read-only access to SEO defaults. Only administrators can make changes.
123
123
  </div>
@@ -150,7 +150,7 @@ export function SeoSettingsTab({ onNavigate, role }: SeoSettingsTabProps) {
150
150
  {seo.saveState === 'error' && seo.saveError && (
151
151
  <div
152
152
  role="alert"
153
- className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-3 text-sm"
153
+ className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-4 text-sm"
154
154
  >
155
155
  <AlertTriangle size={16} className="shrink-0" aria-hidden="true" />
156
156
  <span className="flex-1">{seo.saveError}</span>
@@ -381,7 +381,7 @@ function SeoPreviewCard({
381
381
  }) {
382
382
  return (
383
383
  <div className="border-border mt-4 grid gap-4 border-t pt-4 sm:grid-cols-2">
384
- <div className="border-border rounded-lg border p-3">
384
+ <div className="border-border rounded-lg border p-4">
385
385
  <p className="text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase">
386
386
  Search preview
387
387
  </p>
@@ -398,7 +398,7 @@ function SeoPreviewCard({
398
398
  <ImageIcon size={24} className="text-muted-foreground" aria-hidden="true" />
399
399
  )}
400
400
  </div>
401
- <div className="p-3">
401
+ <div className="p-4">
402
402
  <p className="text-muted-foreground truncate text-xs uppercase">
403
403
  {siteName || new URL(url).hostname}
404
404
  </p>
@@ -700,7 +700,7 @@ function SitemapDefaultsCard({
700
700
  description="Control XML sitemap generation and whether search engines are pinged on publish."
701
701
  >
702
702
  {!siteUrl && (
703
- <div className="border-warning/30 bg-warning/10 text-warning mb-4 flex items-start gap-2 rounded-md border p-3 text-sm">
703
+ <div className="border-warning/30 bg-warning/10 text-warning mb-4 flex items-start gap-2 rounded-md border p-4 text-sm">
704
704
  <AlertTriangle size={16} className="mt-0.5 shrink-0" aria-hidden="true" />
705
705
  Site URL is not set — sitemap URLs may be incorrect. Set it in Settings → General.
706
706
  </div>
@@ -109,7 +109,7 @@ function Notice({
109
109
  ? 'border-destructive/30 bg-destructive/10 text-destructive'
110
110
  : 'border-warning/30 bg-warning/10 text-warning'
111
111
  return (
112
- <div className={`mt-3 flex items-start gap-2.5 rounded-md border p-3 ${toneClass}`}>
112
+ <div className={`mt-3 flex items-start gap-2.5 rounded-md border p-4 ${toneClass}`}>
113
113
  <span className="mt-0.5 shrink-0" aria-hidden="true">
114
114
  {icon}
115
115
  </span>
@@ -180,7 +180,7 @@ export function UpdatesTab({ role }: UpdatesTabProps) {
180
180
  {error && (
181
181
  <div
182
182
  role="alert"
183
- className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-3 text-sm"
183
+ className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-4 text-sm"
184
184
  >
185
185
  <AlertTriangle size={20} className="shrink-0" aria-hidden="true" />
186
186
  <span className="flex-1">{error}</span>
@@ -309,7 +309,7 @@ export function UpdatesTab({ role }: UpdatesTabProps) {
309
309
  </div>
310
310
 
311
311
  {info.updateCommand && (
312
- <div className="border-border bg-card mt-3 rounded-md border p-3">
312
+ <div className="border-border bg-card mt-3 rounded-md border p-4">
313
313
  <p className="text-muted-foreground mb-1 text-xs">Or update manually:</p>
314
314
  <code className="bg-muted text-foreground block rounded px-2 py-1.5 font-mono text-xs select-all">
315
315
  {info.updateCommand}
@@ -49,7 +49,7 @@ export function SettingsCard({
49
49
  </div>
50
50
  {actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
51
51
  </CardHeader>
52
- <CardContent className="pt-0 text-[var(--txt)]">{children}</CardContent>
52
+ <CardContent className="text-[var(--txt)]">{children}</CardContent>
53
53
  </Card>
54
54
  )
55
55
  }
@@ -74,7 +74,7 @@ export function SettingsSubsection({
74
74
  <div className={`border-border border-t pt-4 ${className ?? ''}`}>
75
75
  <div className="text-foreground text-base font-medium">{title}</div>
76
76
  {description && <p className="text-muted-foreground mt-0.5 text-sm">{description}</p>}
77
- {children}
77
+ {children ? <div className="mt-4">{children}</div> : null}
78
78
  </div>
79
79
  )
80
80
  }
@@ -200,7 +200,7 @@ export function SettingsValidationSummary({ issues }: { issues: ValidationIssue[
200
200
  <div
201
201
  role="alert"
202
202
  aria-live="polite"
203
- className={`flex items-start gap-3 rounded-lg border p-3 text-sm ${
203
+ className={`flex items-start gap-3 rounded-lg border p-4 text-sm ${
204
204
  isError
205
205
  ? 'border-destructive/30 bg-destructive/10 text-destructive'
206
206
  : 'border-warning/30 bg-warning/10 text-warning'