@actuate-media/cms-admin 0.66.0 → 0.68.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 (244) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +36 -9
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/components/inspector-pane.test.d.ts +2 -0
  6. package/dist/__tests__/components/inspector-pane.test.d.ts.map +1 -0
  7. package/dist/__tests__/components/inspector-pane.test.js +31 -0
  8. package/dist/__tests__/components/inspector-pane.test.js.map +1 -0
  9. package/dist/__tests__/hooks/useIdleLogout.test.d.ts +2 -0
  10. package/dist/__tests__/hooks/useIdleLogout.test.d.ts.map +1 -0
  11. package/dist/__tests__/hooks/useIdleLogout.test.js +64 -0
  12. package/dist/__tests__/hooks/useIdleLogout.test.js.map +1 -0
  13. package/dist/__tests__/lib/pages-tree.test.d.ts +2 -0
  14. package/dist/__tests__/lib/pages-tree.test.d.ts.map +1 -0
  15. package/dist/__tests__/lib/pages-tree.test.js +173 -0
  16. package/dist/__tests__/lib/pages-tree.test.js.map +1 -0
  17. package/dist/__tests__/lib/post-types-service.test.d.ts +2 -0
  18. package/dist/__tests__/lib/post-types-service.test.d.ts.map +1 -0
  19. package/dist/__tests__/lib/post-types-service.test.js +88 -0
  20. package/dist/__tests__/lib/post-types-service.test.js.map +1 -0
  21. package/dist/__tests__/views/collection-list-nav.render.test.js +43 -5
  22. package/dist/__tests__/views/collection-list-nav.render.test.js.map +1 -1
  23. package/dist/__tests__/views/dashboard.test.js +7 -3
  24. package/dist/__tests__/views/dashboard.test.js.map +1 -1
  25. package/dist/__tests__/views/forms-list.render.test.js +25 -6
  26. package/dist/__tests__/views/forms-list.render.test.js.map +1 -1
  27. package/dist/__tests__/views/media-seo-audit.render.test.d.ts +2 -0
  28. package/dist/__tests__/views/media-seo-audit.render.test.d.ts.map +1 -0
  29. package/dist/__tests__/views/media-seo-audit.render.test.js +102 -0
  30. package/dist/__tests__/views/media-seo-audit.render.test.js.map +1 -0
  31. package/dist/__tests__/views/onboarding-block.render.test.d.ts +2 -0
  32. package/dist/__tests__/views/onboarding-block.render.test.d.ts.map +1 -0
  33. package/dist/__tests__/views/onboarding-block.render.test.js +146 -0
  34. package/dist/__tests__/views/onboarding-block.render.test.js.map +1 -0
  35. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +2 -0
  36. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +1 -0
  37. package/dist/__tests__/views/page-builder-sections-panel.render.test.js +121 -0
  38. package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +1 -0
  39. package/dist/__tests__/views/page-templates.render.test.d.ts +2 -0
  40. package/dist/__tests__/views/page-templates.render.test.d.ts.map +1 -0
  41. package/dist/__tests__/views/page-templates.render.test.js +105 -0
  42. package/dist/__tests__/views/page-templates.render.test.js.map +1 -0
  43. package/dist/__tests__/views/pages-list-view.test.js +77 -1
  44. package/dist/__tests__/views/pages-list-view.test.js.map +1 -1
  45. package/dist/__tests__/views/post-type-editor.render.test.d.ts +2 -0
  46. package/dist/__tests__/views/post-type-editor.render.test.d.ts.map +1 -0
  47. package/dist/__tests__/views/post-type-editor.render.test.js +102 -0
  48. package/dist/__tests__/views/post-type-editor.render.test.js.map +1 -0
  49. package/dist/__tests__/views/security-settings.render.test.js +85 -4
  50. package/dist/__tests__/views/security-settings.render.test.js.map +1 -1
  51. package/dist/__tests__/views/seo-content-pane.render.test.d.ts +2 -0
  52. package/dist/__tests__/views/seo-content-pane.render.test.d.ts.map +1 -0
  53. package/dist/__tests__/views/seo-content-pane.render.test.js +117 -0
  54. package/dist/__tests__/views/seo-content-pane.render.test.js.map +1 -0
  55. package/dist/actuate-admin.css +1 -1
  56. package/dist/components/collections/EntryDetailPane.d.ts +10 -0
  57. package/dist/components/collections/EntryDetailPane.d.ts.map +1 -0
  58. package/dist/components/collections/EntryDetailPane.js +92 -0
  59. package/dist/components/collections/EntryDetailPane.js.map +1 -0
  60. package/dist/components/forms/FieldsPanel.js +1 -1
  61. package/dist/components/forms/FieldsPanel.js.map +1 -1
  62. package/dist/components/forms/FormSchemaPane.d.ts +8 -0
  63. package/dist/components/forms/FormSchemaPane.d.ts.map +1 -0
  64. package/dist/components/forms/FormSchemaPane.js +95 -0
  65. package/dist/components/forms/FormSchemaPane.js.map +1 -0
  66. package/dist/components/script-tags/ScriptTagEditorPane.d.ts +26 -0
  67. package/dist/components/script-tags/ScriptTagEditorPane.d.ts.map +1 -0
  68. package/dist/components/script-tags/ScriptTagEditorPane.js +160 -0
  69. package/dist/components/script-tags/ScriptTagEditorPane.js.map +1 -0
  70. package/dist/components/seo/SeoEditorPane.d.ts +9 -0
  71. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -0
  72. package/dist/components/seo/SeoEditorPane.js +175 -0
  73. package/dist/components/seo/SeoEditorPane.js.map +1 -0
  74. package/dist/components/ui/Badge.d.ts +1 -1
  75. package/dist/components/ui/Button.d.ts +1 -1
  76. package/dist/components/ui/InspectorPane.d.ts +51 -0
  77. package/dist/components/ui/InspectorPane.d.ts.map +1 -0
  78. package/dist/components/ui/InspectorPane.js +42 -0
  79. package/dist/components/ui/InspectorPane.js.map +1 -0
  80. package/dist/components/ui/StatusBadge.d.ts +1 -1
  81. package/dist/components/ui/index.d.ts +2 -0
  82. package/dist/components/ui/index.d.ts.map +1 -1
  83. package/dist/components/ui/index.js +1 -0
  84. package/dist/components/ui/index.js.map +1 -1
  85. package/dist/hooks/useIdleLogout.d.ts +18 -0
  86. package/dist/hooks/useIdleLogout.d.ts.map +1 -0
  87. package/dist/hooks/useIdleLogout.js +66 -0
  88. package/dist/hooks/useIdleLogout.js.map +1 -0
  89. package/dist/lib/api.d.ts +1 -0
  90. package/dist/lib/api.d.ts.map +1 -1
  91. package/dist/lib/api.js +7 -1
  92. package/dist/lib/api.js.map +1 -1
  93. package/dist/lib/page-editor-service.d.ts +11 -2
  94. package/dist/lib/page-editor-service.d.ts.map +1 -1
  95. package/dist/lib/page-editor-service.js +8 -2
  96. package/dist/lib/page-editor-service.js.map +1 -1
  97. package/dist/lib/pages-tree.d.ts +44 -0
  98. package/dist/lib/pages-tree.d.ts.map +1 -0
  99. package/dist/lib/pages-tree.js +106 -0
  100. package/dist/lib/pages-tree.js.map +1 -0
  101. package/dist/lib/post-types-service.d.ts +44 -0
  102. package/dist/lib/post-types-service.d.ts.map +1 -0
  103. package/dist/lib/post-types-service.js +83 -0
  104. package/dist/lib/post-types-service.js.map +1 -0
  105. package/dist/views/CollectionList.d.ts.map +1 -1
  106. package/dist/views/CollectionList.js +23 -4
  107. package/dist/views/CollectionList.js.map +1 -1
  108. package/dist/views/Dashboard.d.ts.map +1 -1
  109. package/dist/views/Dashboard.js +31 -2
  110. package/dist/views/Dashboard.js.map +1 -1
  111. package/dist/views/Forms.d.ts.map +1 -1
  112. package/dist/views/Forms.js +25 -21
  113. package/dist/views/Forms.js.map +1 -1
  114. package/dist/views/MediaBrowser.d.ts.map +1 -1
  115. package/dist/views/MediaBrowser.js +28 -2
  116. package/dist/views/MediaBrowser.js.map +1 -1
  117. package/dist/views/MediaSeoAudit.d.ts +25 -0
  118. package/dist/views/MediaSeoAudit.d.ts.map +1 -0
  119. package/dist/views/MediaSeoAudit.js +97 -0
  120. package/dist/views/MediaSeoAudit.js.map +1 -0
  121. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  122. package/dist/views/Pages/PagesListView.js +90 -10
  123. package/dist/views/Pages/PagesListView.js.map +1 -1
  124. package/dist/views/Pages/PagesTreeTable.d.ts +35 -0
  125. package/dist/views/Pages/PagesTreeTable.d.ts.map +1 -0
  126. package/dist/views/Pages/PagesTreeTable.js +28 -0
  127. package/dist/views/Pages/PagesTreeTable.js.map +1 -0
  128. package/dist/views/Posts/PostTypeEditor.d.ts +16 -0
  129. package/dist/views/Posts/PostTypeEditor.d.ts.map +1 -0
  130. package/dist/views/Posts/PostTypeEditor.js +159 -0
  131. package/dist/views/Posts/PostTypeEditor.js.map +1 -0
  132. package/dist/views/Posts/PostTypesView.d.ts +3 -2
  133. package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
  134. package/dist/views/Posts/PostTypesView.js +1 -1
  135. package/dist/views/Posts/PostTypesView.js.map +1 -1
  136. package/dist/views/ScriptTags.d.ts +5 -1
  137. package/dist/views/ScriptTags.d.ts.map +1 -1
  138. package/dist/views/ScriptTags.js +32 -7
  139. package/dist/views/ScriptTags.js.map +1 -1
  140. package/dist/views/dashboard/OnboardingBlock.d.ts +29 -0
  141. package/dist/views/dashboard/OnboardingBlock.d.ts.map +1 -0
  142. package/dist/views/dashboard/OnboardingBlock.js +71 -0
  143. package/dist/views/dashboard/OnboardingBlock.js.map +1 -0
  144. package/dist/views/page-builder/BuilderSectionsPanel.d.ts +20 -0
  145. package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +1 -0
  146. package/dist/views/page-builder/BuilderSectionsPanel.js +105 -0
  147. package/dist/views/page-builder/BuilderSectionsPanel.js.map +1 -0
  148. package/dist/views/page-builder/PageBuilder.d.ts.map +1 -1
  149. package/dist/views/page-builder/PageBuilder.js +51 -2
  150. package/dist/views/page-builder/PageBuilder.js.map +1 -1
  151. package/dist/views/page-builder/PageTemplates.d.ts.map +1 -1
  152. package/dist/views/page-builder/PageTemplates.js +113 -6
  153. package/dist/views/page-builder/PageTemplates.js.map +1 -1
  154. package/dist/views/page-editor/PageSectionEditor.d.ts +7 -1
  155. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  156. package/dist/views/page-editor/PageSectionEditor.js +3 -3
  157. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  158. package/dist/views/page-editor/PageSettingsModal.d.ts +1 -1
  159. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -1
  160. package/dist/views/page-editor/PageSettingsModal.js +42 -3
  161. package/dist/views/page-editor/PageSettingsModal.js.map +1 -1
  162. package/dist/views/seo/ContentTab.d.ts.map +1 -1
  163. package/dist/views/seo/ContentTab.js +22 -6
  164. package/dist/views/seo/ContentTab.js.map +1 -1
  165. package/dist/views/settings/AccessProtectionCard.d.ts +5 -1
  166. package/dist/views/settings/AccessProtectionCard.d.ts.map +1 -1
  167. package/dist/views/settings/AccessProtectionCard.js +13 -10
  168. package/dist/views/settings/AccessProtectionCard.js.map +1 -1
  169. package/dist/views/settings/SecurityTab.d.ts.map +1 -1
  170. package/dist/views/settings/SecurityTab.js +3 -3
  171. package/dist/views/settings/SecurityTab.js.map +1 -1
  172. package/dist/views/settings/SessionPolicyCard.d.ts +1 -2
  173. package/dist/views/settings/SessionPolicyCard.d.ts.map +1 -1
  174. package/dist/views/settings/SessionPolicyCard.js +12 -5
  175. package/dist/views/settings/SessionPolicyCard.js.map +1 -1
  176. package/dist/views/settings/useSecuritySettings.d.ts +13 -1
  177. package/dist/views/settings/useSecuritySettings.d.ts.map +1 -1
  178. package/dist/views/settings/useSecuritySettings.js +47 -4
  179. package/dist/views/settings/useSecuritySettings.js.map +1 -1
  180. package/package.json +2 -2
  181. package/src/AdminRoot.tsx +43 -10
  182. package/src/__tests__/components/inspector-pane.test.tsx +64 -0
  183. package/src/__tests__/hooks/useIdleLogout.test.tsx +86 -0
  184. package/src/__tests__/lib/pages-tree.test.ts +197 -0
  185. package/src/__tests__/lib/post-types-service.test.ts +112 -0
  186. package/src/__tests__/views/collection-list-nav.render.test.tsx +51 -5
  187. package/src/__tests__/views/dashboard.test.tsx +7 -3
  188. package/src/__tests__/views/forms-list.render.test.tsx +34 -6
  189. package/src/__tests__/views/media-seo-audit.render.test.tsx +117 -0
  190. package/src/__tests__/views/onboarding-block.render.test.tsx +193 -0
  191. package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +138 -0
  192. package/src/__tests__/views/page-templates.render.test.tsx +142 -0
  193. package/src/__tests__/views/pages-list-view.test.tsx +93 -1
  194. package/src/__tests__/views/post-type-editor.render.test.tsx +116 -0
  195. package/src/__tests__/views/security-settings.render.test.tsx +111 -4
  196. package/src/__tests__/views/seo-content-pane.render.test.tsx +142 -0
  197. package/src/components/collections/EntryDetailPane.tsx +182 -0
  198. package/src/components/forms/FieldsPanel.tsx +3 -1
  199. package/src/components/forms/FormSchemaPane.tsx +192 -0
  200. package/src/components/script-tags/ScriptTagEditorPane.tsx +474 -0
  201. package/src/components/seo/SeoEditorPane.tsx +533 -0
  202. package/src/components/ui/InspectorPane.tsx +129 -0
  203. package/src/components/ui/index.ts +10 -0
  204. package/src/hooks/useIdleLogout.ts +77 -0
  205. package/src/lib/api.ts +8 -2
  206. package/src/lib/page-editor-service.ts +16 -2
  207. package/src/lib/pages-tree.ts +145 -0
  208. package/src/lib/post-types-service.ts +129 -0
  209. package/src/views/CollectionList.tsx +158 -110
  210. package/src/views/Dashboard.tsx +238 -196
  211. package/src/views/Forms.tsx +154 -126
  212. package/src/views/MediaBrowser.tsx +641 -511
  213. package/src/views/MediaSeoAudit.tsx +320 -0
  214. package/src/views/Pages/PagesListView.tsx +352 -183
  215. package/src/views/Pages/PagesTreeTable.tsx +339 -0
  216. package/src/views/Posts/PostTypeEditor.tsx +568 -0
  217. package/src/views/Posts/PostTypesView.tsx +5 -4
  218. package/src/views/ScriptTags.tsx +204 -129
  219. package/src/views/dashboard/OnboardingBlock.tsx +233 -0
  220. package/src/views/page-builder/BuilderSectionsPanel.tsx +304 -0
  221. package/src/views/page-builder/PageBuilder.tsx +69 -2
  222. package/src/views/page-builder/PageTemplates.tsx +340 -57
  223. package/src/views/page-editor/PageSectionEditor.tsx +9 -2
  224. package/src/views/page-editor/PageSettingsModal.tsx +75 -2
  225. package/src/views/seo/ContentTab.tsx +113 -80
  226. package/src/views/settings/AccessProtectionCard.tsx +83 -53
  227. package/src/views/settings/SecurityTab.tsx +23 -2
  228. package/src/views/settings/SessionPolicyCard.tsx +45 -9
  229. package/src/views/settings/useSecuritySettings.ts +78 -22
  230. package/dist/components/forms/FormSchemaDrawer.d.ts +0 -9
  231. package/dist/components/forms/FormSchemaDrawer.d.ts.map +0 -1
  232. package/dist/components/forms/FormSchemaDrawer.js +0 -99
  233. package/dist/components/forms/FormSchemaDrawer.js.map +0 -1
  234. package/dist/components/seo/SeoEditorDrawer.d.ts +0 -8
  235. package/dist/components/seo/SeoEditorDrawer.d.ts.map +0 -1
  236. package/dist/components/seo/SeoEditorDrawer.js +0 -172
  237. package/dist/components/seo/SeoEditorDrawer.js.map +0 -1
  238. package/dist/views/ScriptTagEditor.d.ts +0 -6
  239. package/dist/views/ScriptTagEditor.d.ts.map +0 -1
  240. package/dist/views/ScriptTagEditor.js +0 -111
  241. package/dist/views/ScriptTagEditor.js.map +0 -1
  242. package/src/components/forms/FormSchemaDrawer.tsx +0 -192
  243. package/src/components/seo/SeoEditorDrawer.tsx +0 -529
  244. package/src/views/ScriptTagEditor.tsx +0 -353
@@ -41,6 +41,8 @@ import {
41
41
  } from 'lucide-react'
42
42
  import { useApiData } from '../lib/useApiData.js'
43
43
  import { editPathForDoc } from '../lib/collection-routes.js'
44
+ import { OnboardingBlock, shouldShowOnboarding } from './dashboard/OnboardingBlock.js'
45
+ import type { OnboardingSignals } from './dashboard/OnboardingBlock.js'
44
46
 
45
47
  interface DashboardStats {
46
48
  totalDocuments: number
@@ -521,6 +523,40 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
521
523
 
522
524
  const totalIssues = contentHealth.issues.reduce((s, i) => s + i.count, 0)
523
525
 
526
+ // ── Onboarding (empty-site getting-started block) ───────────────────────
527
+ // Signals come exclusively from data already fetched above (/stats +
528
+ // /stats/api-delivery) — see OnboardingBlock.tsx for the per-step
529
+ // derivation rationale. When the site is effectively empty (zero
530
+ // posts+pages AND zero media) the onboarding block REPLACES the
531
+ // Recent Activity / Publishing Queue / Content Health grid.
532
+ const onboardingSignals: OnboardingSignals = useMemo(() => {
533
+ const counts = stats?.collectionCounts ?? {}
534
+ const contentCollections = collections.filter(
535
+ (c) => c.type === 'post' || c.type === 'page' || c.slug === 'posts' || c.slug === 'pages',
536
+ )
537
+ // No post/page collections configured → fall back to the total document
538
+ // count so a populated site never regresses to the onboarding view.
539
+ const postsAndPagesCount =
540
+ contentCollections.length > 0
541
+ ? contentCollections.reduce((sum, c) => sum + (counts[c.slug] ?? 0), 0)
542
+ : (stats?.totalDocuments ?? 0)
543
+ return {
544
+ postsAndPagesCount,
545
+ mediaCount: stats?.totalMedia ?? 0,
546
+ seoScore: stats?.avgSeoScore ?? 0,
547
+ userCount: stats?.totalUsers ?? 0,
548
+ hasApiTraffic: delivery?.hasData ?? false,
549
+ }
550
+ }, [stats, collections, delivery])
551
+
552
+ // Belt-and-braces: any recent activity means the site is not empty even
553
+ // if collection counts are missing/stale, so never hide it behind
554
+ // onboarding.
555
+ const showOnboarding =
556
+ stats !== null &&
557
+ (stats.recentDocuments?.length ?? 0) === 0 &&
558
+ shouldShowOnboarding(onboardingSignals)
559
+
524
560
  // ── Render ──────────────────────────────────────────────────────────────
525
561
 
526
562
  if (loading && !stats) {
@@ -671,124 +707,71 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
671
707
  })}
672
708
  </div>
673
709
 
674
- {/* Main grid ──────────────────────────────────────────────────── */}
675
- <div className="grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1fr)_320px]">
676
- {/* Recent Activity ───────────────────────────────────────── */}
677
- <section className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
678
- <header className="border-border flex items-center justify-between border-b px-4 py-3">
679
- <div className="min-w-0">
680
- <h2 className="text-foreground text-sm font-medium">Recent Activity</h2>
681
- <p className="text-muted-foreground mt-0.5 text-xs">Last 7 days across all content</p>
682
- </div>
683
- {(stats?.recentDocuments?.length ?? 0) > 8 && (
684
- <button
685
- className="text-brand text-xs font-medium hover:underline"
686
- onClick={() => setActivityLimit((n) => (n >= 20 ? 8 : 20))}
687
- >
688
- {activityLimit >= 20 ? 'Show less' : 'Show more'}
689
- </button>
690
- )}
691
- </header>
692
- {activity.length === 0 ? (
693
- <EmptyState
694
- icon={Activity}
695
- title="No activity yet"
696
- subtitle="Content changes will appear here as your team works."
697
- />
698
- ) : (
699
- <ul role="list" className="divide-border divide-y">
700
- {activity.map((it) => (
701
- <li key={it.id}>
702
- <button
703
- type="button"
704
- onClick={() => nav(it.editPath)}
705
- className="hover:bg-accent/50 focus-visible:bg-accent/50 w-full cursor-pointer px-4 py-3 text-left transition-colors"
706
- >
707
- <div className="flex min-w-0 items-start gap-3">
708
- <div
709
- className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-[11px] font-medium text-white"
710
- style={{ background: it.avatar.color }}
711
- aria-hidden
712
- >
713
- {it.avatar.initials}
714
- </div>
715
- <div className="min-w-0 flex-1">
716
- <p className="text-foreground truncate text-sm leading-snug">
717
- <span className="font-medium">
718
- &ldquo;{it.title || 'Untitled'}&rdquo;
719
- </span>{' '}
720
- <span className="text-muted-foreground">— {it.typeLabel}</span>
721
- </p>
722
- <div className="text-muted-foreground mt-1 flex flex-wrap items-center gap-2 text-[11px]">
723
- <span
724
- className={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium ${it.statusInfo.cls}`}
725
- >
726
- {it.statusInfo.label}
727
- </span>
728
- <span className="max-w-[120px] truncate">{it.author}</span>
729
- <span aria-hidden>·</span>
730
- <span>{it.relTime}</span>
731
- </div>
732
- </div>
733
- </div>
734
- </button>
735
- </li>
736
- ))}
737
- </ul>
738
- )}
739
- </section>
740
-
741
- {/* Right column ─────────────────────────────────────────── */}
742
- <aside className="flex min-w-0 flex-col gap-4">
743
- {/* Publishing Queue */}
710
+ {/* Onboarding (empty site) or the main grid ──────────────────── */}
711
+ {showOnboarding ? (
712
+ <OnboardingBlock signals={onboardingSignals} onNavigate={onNavigate} />
713
+ ) : (
714
+ <div className="grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1fr)_320px]">
715
+ {/* Recent Activity ───────────────────────────────────────── */}
744
716
  <section className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
745
717
  <header className="border-border flex items-center justify-between border-b px-4 py-3">
746
718
  <div className="min-w-0">
747
- <h2 className="text-foreground text-sm font-medium">Publishing Queue</h2>
719
+ <h2 className="text-foreground text-sm font-medium">Recent Activity</h2>
748
720
  <p className="text-muted-foreground mt-0.5 text-xs">
749
- {publishQueue.length} item{publishQueue.length === 1 ? '' : 's'} scheduled
721
+ Last 7 days across all content
750
722
  </p>
751
723
  </div>
752
- <button
753
- className="text-brand text-xs font-medium hover:underline disabled:cursor-default disabled:no-underline disabled:opacity-50"
754
- disabled={publishQueue.length === 0}
755
- onClick={() => {
756
- // We don't have a dedicated "scheduled" admin page, so deep-
757
- // link to the first scheduled item that's where authors
758
- // typically need to land to reschedule or cancel.
759
- const first = publishQueue[0]
760
- if (first) nav(first.editPath)
761
- }}
762
- >
763
- Manage
764
- </button>
724
+ {(stats?.recentDocuments?.length ?? 0) > 8 && (
725
+ <button
726
+ className="text-brand text-xs font-medium hover:underline"
727
+ onClick={() => setActivityLimit((n) => (n >= 20 ? 8 : 20))}
728
+ >
729
+ {activityLimit >= 20 ? 'Show less' : 'Show more'}
730
+ </button>
731
+ )}
765
732
  </header>
766
- {publishQueue.length === 0 ? (
733
+ {activity.length === 0 ? (
767
734
  <EmptyState
768
- icon={Clock}
769
- title="No scheduled content"
770
- subtitle="Scheduled posts and pages appear here."
771
- compact
735
+ icon={Activity}
736
+ title="No activity yet"
737
+ subtitle="Content changes will appear here as your team works."
772
738
  />
773
739
  ) : (
774
740
  <ul role="list" className="divide-border divide-y">
775
- {publishQueue.map((q) => (
776
- <li key={q.id}>
741
+ {activity.map((it) => (
742
+ <li key={it.id}>
777
743
  <button
778
744
  type="button"
779
- onClick={() => nav(q.editPath)}
780
- className="hover:bg-accent/50 focus-visible:bg-accent/50 flex w-full min-w-0 cursor-pointer items-center gap-2.5 px-4 py-2.5 text-left transition-colors"
745
+ onClick={() => nav(it.editPath)}
746
+ className="hover:bg-accent/50 focus-visible:bg-accent/50 w-full cursor-pointer px-4 py-3 text-left transition-colors"
781
747
  >
782
- <span className="bg-warning h-2 w-2 shrink-0 rounded-full" aria-hidden />
783
- <div className="min-w-0 flex-1">
784
- <p className="text-foreground truncate text-sm font-medium">{q.title}</p>
785
- <p className="text-muted-foreground truncate text-[11px]">
786
- {q.date} · {q.author}
787
- </p>
748
+ <div className="flex min-w-0 items-start gap-3">
749
+ <div
750
+ className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-[11px] font-medium text-white"
751
+ style={{ background: it.avatar.color }}
752
+ aria-hidden
753
+ >
754
+ {it.avatar.initials}
755
+ </div>
756
+ <div className="min-w-0 flex-1">
757
+ <p className="text-foreground truncate text-sm leading-snug">
758
+ <span className="font-medium">
759
+ &ldquo;{it.title || 'Untitled'}&rdquo;
760
+ </span>{' '}
761
+ <span className="text-muted-foreground">— {it.typeLabel}</span>
762
+ </p>
763
+ <div className="text-muted-foreground mt-1 flex flex-wrap items-center gap-2 text-[11px]">
764
+ <span
765
+ className={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium ${it.statusInfo.cls}`}
766
+ >
767
+ {it.statusInfo.label}
768
+ </span>
769
+ <span className="max-w-[120px] truncate">{it.author}</span>
770
+ <span aria-hidden>·</span>
771
+ <span>{it.relTime}</span>
772
+ </div>
773
+ </div>
788
774
  </div>
789
- <span className="border-border bg-background text-muted-foreground shrink-0 rounded border px-1.5 py-0.5 text-[10px] capitalize">
790
- {q.type}
791
- </span>
792
775
  </button>
793
776
  </li>
794
777
  ))}
@@ -796,103 +779,162 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
796
779
  )}
797
780
  </section>
798
781
 
799
- {/* Content Health */}
800
- <section className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
801
- <header className="border-border flex items-center justify-between border-b px-4 py-3">
802
- <div className="min-w-0">
803
- <h2 className="text-foreground text-sm font-medium">Content Health</h2>
804
- <p className="text-muted-foreground mt-0.5 text-xs">SEO &amp; quality issues</p>
805
- </div>
806
- <button
807
- className="text-brand text-xs font-medium hover:underline"
808
- onClick={() => nav('/seo')}
809
- >
810
- Fix issues
811
- </button>
812
- </header>
813
- <div className="px-4 pt-3.5 pb-2.5">
814
- <div className="mb-1.5 flex items-baseline justify-between">
815
- <p className="text-foreground text-2xl leading-none font-medium tracking-tight">
816
- {contentHealth.score > 0 ? contentHealth.score : '—'}
817
- {contentHealth.score > 0 && (
818
- <span className="text-muted-foreground ml-0.5 text-sm font-normal">/100</span>
819
- )}
820
- </p>
821
- <span
822
- className={`text-xs font-medium ${
823
- contentHealth.tone === 'ok'
824
- ? 'text-success'
825
- : contentHealth.tone === 'warn'
826
- ? 'text-warning'
827
- : contentHealth.tone === 'err'
828
- ? 'text-destructive dark:text-red-400'
829
- : 'text-muted-foreground'
830
- }`}
782
+ {/* Right column ─────────────────────────────────────────── */}
783
+ <aside className="flex min-w-0 flex-col gap-4">
784
+ {/* Publishing Queue */}
785
+ <section className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
786
+ <header className="border-border flex items-center justify-between border-b px-4 py-3">
787
+ <div className="min-w-0">
788
+ <h2 className="text-foreground text-sm font-medium">Publishing Queue</h2>
789
+ <p className="text-muted-foreground mt-0.5 text-xs">
790
+ {publishQueue.length} item{publishQueue.length === 1 ? '' : 's'} scheduled
791
+ </p>
792
+ </div>
793
+ <button
794
+ className="text-brand text-xs font-medium hover:underline disabled:cursor-default disabled:no-underline disabled:opacity-50"
795
+ disabled={publishQueue.length === 0}
796
+ onClick={() => {
797
+ // We don't have a dedicated "scheduled" admin page, so deep-
798
+ // link to the first scheduled item — that's where authors
799
+ // typically need to land to reschedule or cancel.
800
+ const first = publishQueue[0]
801
+ if (first) nav(first.editPath)
802
+ }}
831
803
  >
832
- {contentHealth.label}
833
- </span>
834
- </div>
835
- <div className="bg-muted h-1.5 overflow-hidden rounded-full">
836
- <div
837
- className={`h-full rounded-full transition-all ${
838
- contentHealth.tone === 'ok'
839
- ? 'bg-success'
840
- : contentHealth.tone === 'warn'
841
- ? 'bg-warning'
842
- : contentHealth.tone === 'err'
843
- ? 'bg-destructive'
844
- : 'bg-muted-foreground/40'
845
- }`}
846
- style={{ width: `${Math.max(0, Math.min(100, contentHealth.score))}%` }}
804
+ Manage
805
+ </button>
806
+ </header>
807
+ {publishQueue.length === 0 ? (
808
+ <EmptyState
809
+ icon={Clock}
810
+ title="No scheduled content"
811
+ subtitle="Scheduled posts and pages appear here."
812
+ compact
847
813
  />
848
- </div>
849
- </div>
850
- {contentHealth.issues.length === 0 ? (
851
- <div className="text-muted-foreground px-4 pt-1 pb-4 text-xs">
852
- No outstanding issues. Nice work.
853
- </div>
854
- ) : (
855
- <ul role="list" className="divide-border divide-y">
856
- {contentHealth.issues.map((iss, i) => (
857
- <li key={i}>
858
- <button
859
- type="button"
860
- onClick={() => nav('/seo')}
861
- className="hover:bg-accent/50 focus-visible:bg-accent/50 flex w-full items-center gap-2.5 px-4 py-2 text-left transition-colors"
862
- >
863
- <span
864
- className={`h-1.5 w-1.5 shrink-0 rounded-full ${
865
- iss.tone === 'err'
866
- ? 'bg-destructive'
867
- : iss.tone === 'warn'
868
- ? 'bg-warning'
869
- : 'bg-muted-foreground'
870
- }`}
871
- aria-hidden
872
- />
873
- <span className="text-muted-foreground flex-1 truncate text-xs">
874
- {iss.label}
875
- </span>
876
- <span
877
- className={`shrink-0 text-sm font-medium ${
878
- iss.tone === 'err'
814
+ ) : (
815
+ <ul role="list" className="divide-border divide-y">
816
+ {publishQueue.map((q) => (
817
+ <li key={q.id}>
818
+ <button
819
+ type="button"
820
+ onClick={() => nav(q.editPath)}
821
+ className="hover:bg-accent/50 focus-visible:bg-accent/50 flex w-full min-w-0 cursor-pointer items-center gap-2.5 px-4 py-2.5 text-left transition-colors"
822
+ >
823
+ <span className="bg-warning h-2 w-2 shrink-0 rounded-full" aria-hidden />
824
+ <div className="min-w-0 flex-1">
825
+ <p className="text-foreground truncate text-sm font-medium">{q.title}</p>
826
+ <p className="text-muted-foreground truncate text-[11px]">
827
+ {q.date} · {q.author}
828
+ </p>
829
+ </div>
830
+ <span className="border-border bg-background text-muted-foreground shrink-0 rounded border px-1.5 py-0.5 text-[10px] capitalize">
831
+ {q.type}
832
+ </span>
833
+ </button>
834
+ </li>
835
+ ))}
836
+ </ul>
837
+ )}
838
+ </section>
839
+
840
+ {/* Content Health */}
841
+ <section className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
842
+ <header className="border-border flex items-center justify-between border-b px-4 py-3">
843
+ <div className="min-w-0">
844
+ <h2 className="text-foreground text-sm font-medium">Content Health</h2>
845
+ <p className="text-muted-foreground mt-0.5 text-xs">SEO &amp; quality issues</p>
846
+ </div>
847
+ <button
848
+ className="text-brand text-xs font-medium hover:underline"
849
+ onClick={() => nav('/seo')}
850
+ >
851
+ Fix issues
852
+ </button>
853
+ </header>
854
+ <div className="px-4 pt-3.5 pb-2.5">
855
+ <div className="mb-1.5 flex items-baseline justify-between">
856
+ <p className="text-foreground text-2xl leading-none font-medium tracking-tight">
857
+ {contentHealth.score > 0 ? contentHealth.score : '—'}
858
+ {contentHealth.score > 0 && (
859
+ <span className="text-muted-foreground ml-0.5 text-sm font-normal">/100</span>
860
+ )}
861
+ </p>
862
+ <span
863
+ className={`text-xs font-medium ${
864
+ contentHealth.tone === 'ok'
865
+ ? 'text-success'
866
+ : contentHealth.tone === 'warn'
867
+ ? 'text-warning'
868
+ : contentHealth.tone === 'err'
879
869
  ? 'text-destructive dark:text-red-400'
880
- : iss.tone === 'warn'
881
- ? 'text-warning'
882
- : 'text-muted-foreground'
883
- }`}
870
+ : 'text-muted-foreground'
871
+ }`}
872
+ >
873
+ {contentHealth.label}
874
+ </span>
875
+ </div>
876
+ <div className="bg-muted h-1.5 overflow-hidden rounded-full">
877
+ <div
878
+ className={`h-full rounded-full transition-all ${
879
+ contentHealth.tone === 'ok'
880
+ ? 'bg-success'
881
+ : contentHealth.tone === 'warn'
882
+ ? 'bg-warning'
883
+ : contentHealth.tone === 'err'
884
+ ? 'bg-destructive'
885
+ : 'bg-muted-foreground/40'
886
+ }`}
887
+ style={{ width: `${Math.max(0, Math.min(100, contentHealth.score))}%` }}
888
+ />
889
+ </div>
890
+ </div>
891
+ {contentHealth.issues.length === 0 ? (
892
+ <div className="text-muted-foreground px-4 pt-1 pb-4 text-xs">
893
+ No outstanding issues. Nice work.
894
+ </div>
895
+ ) : (
896
+ <ul role="list" className="divide-border divide-y">
897
+ {contentHealth.issues.map((iss, i) => (
898
+ <li key={i}>
899
+ <button
900
+ type="button"
901
+ onClick={() => nav('/seo')}
902
+ className="hover:bg-accent/50 focus-visible:bg-accent/50 flex w-full items-center gap-2.5 px-4 py-2 text-left transition-colors"
884
903
  >
885
- {iss.count}
886
- </span>
887
- <ChevronRight className="text-muted-foreground/60 h-3.5 w-3.5 shrink-0" />
888
- </button>
889
- </li>
890
- ))}
891
- </ul>
892
- )}
893
- </section>
894
- </aside>
895
- </div>
904
+ <span
905
+ className={`h-1.5 w-1.5 shrink-0 rounded-full ${
906
+ iss.tone === 'err'
907
+ ? 'bg-destructive'
908
+ : iss.tone === 'warn'
909
+ ? 'bg-warning'
910
+ : 'bg-muted-foreground'
911
+ }`}
912
+ aria-hidden
913
+ />
914
+ <span className="text-muted-foreground flex-1 truncate text-xs">
915
+ {iss.label}
916
+ </span>
917
+ <span
918
+ className={`shrink-0 text-sm font-medium ${
919
+ iss.tone === 'err'
920
+ ? 'text-destructive dark:text-red-400'
921
+ : iss.tone === 'warn'
922
+ ? 'text-warning'
923
+ : 'text-muted-foreground'
924
+ }`}
925
+ >
926
+ {iss.count}
927
+ </span>
928
+ <ChevronRight className="text-muted-foreground/60 h-3.5 w-3.5 shrink-0" />
929
+ </button>
930
+ </li>
931
+ ))}
932
+ </ul>
933
+ )}
934
+ </section>
935
+ </aside>
936
+ </div>
937
+ )}
896
938
 
897
939
  {/* Delivery API ──────────────────────────────────────────────────
898
940
  Headless content delivery metrics aggregated over the last