@anthusai/papyrus 2.0.0-next.1 → 2.0.0-next.100

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 (252) hide show
  1. package/amplify/auth/resource.js +16 -1
  2. package/amplify/auth/resource.ts +19 -1
  3. package/amplify/data/function-handler.js +20 -0
  4. package/amplify/data/function-handler.ts +40 -0
  5. package/amplify/data/resource.js +158 -61
  6. package/amplify/data/resource.ts +165 -61
  7. package/amplify/data/schema.js +2103 -1979
  8. package/amplify/data/schema.ts +206 -68
  9. package/amplify/functions/assignment-action/data-access.js +90 -0
  10. package/amplify/functions/assignment-action/data-access.ts +89 -0
  11. package/amplify/functions/assignment-action/handler.ts +1 -1
  12. package/amplify/functions/automation-view/handler.py +17 -0
  13. package/amplify/functions/automation-view/resource.js +24 -0
  14. package/amplify/functions/automation-view/resource.ts +34 -0
  15. package/amplify/functions/automation-view/schema.js +26 -0
  16. package/amplify/functions/automation-view/schema.ts +28 -0
  17. package/amplify/functions/automation-view/wire.js +56 -0
  18. package/amplify/functions/automation-view/wire.ts +76 -0
  19. package/amplify/functions/content-actions/handler.py +70 -2
  20. package/amplify/functions/content-actions/requirements.txt +1 -0
  21. package/amplify/functions/content-actions/resource.js +1 -1
  22. package/amplify/functions/content-actions/resource.ts +1 -1
  23. package/amplify/functions/editorial-checks/handler.py +14 -0
  24. package/amplify/functions/editorial-checks/requirements.txt +1 -0
  25. package/amplify/functions/editorial-checks/resource.js +43 -0
  26. package/amplify/functions/editorial-checks/resource.ts +60 -0
  27. package/amplify/functions/editorial-checks/schema.js +122 -0
  28. package/amplify/functions/editorial-checks/schema.ts +124 -0
  29. package/amplify/functions/editorial-checks/wire.js +53 -0
  30. package/amplify/functions/editorial-checks/wire.ts +61 -0
  31. package/amplify/functions/model-attachment-upload/handler.ts +1 -0
  32. package/amplify/functions/monitoring-view/handler.py +11 -0
  33. package/amplify/functions/monitoring-view/resource.js +24 -0
  34. package/amplify/functions/monitoring-view/resource.ts +34 -0
  35. package/amplify/functions/monitoring-view/schema.js +29 -0
  36. package/amplify/functions/monitoring-view/schema.ts +31 -0
  37. package/amplify/functions/monitoring-view/wire.js +64 -0
  38. package/amplify/functions/monitoring-view/wire.ts +81 -0
  39. package/amplify/functions/public-forms/core.js +247 -0
  40. package/amplify/functions/public-forms/core.ts +310 -0
  41. package/amplify/functions/public-forms/handler.ts +41 -0
  42. package/amplify/functions/public-forms/origins.js +35 -0
  43. package/amplify/functions/public-forms/origins.ts +43 -0
  44. package/amplify/functions/public-forms/resource.js +9 -0
  45. package/amplify/functions/public-forms/resource.ts +10 -0
  46. package/amplify/functions/public-forms/wire.js +25 -0
  47. package/amplify/functions/public-forms/wire.ts +32 -0
  48. package/amplify/functions/shared/editorial-context.ts +63 -4
  49. package/amplify/functions/shared/python-bundle.js +20 -14
  50. package/amplify/functions/shared/python-bundle.ts +25 -18
  51. package/amplify/plugins/newsletter/config.js +245 -0
  52. package/amplify/plugins/newsletter/config.ts +305 -0
  53. package/amplify/plugins/newsletter/function/handler.py +6 -0
  54. package/amplify/plugins/newsletter/function/requirements.txt +5 -0
  55. package/amplify/plugins/newsletter/ops/handler.py +12 -0
  56. package/amplify/plugins/newsletter/ops/resource.js +27 -0
  57. package/amplify/plugins/newsletter/ops/resource.ts +37 -0
  58. package/amplify/plugins/newsletter/papyrus.plugin.js +263 -0
  59. package/amplify/plugins/newsletter/papyrus.plugin.ts +311 -0
  60. package/amplify/plugins/newsletter/schema.js +57 -0
  61. package/amplify/plugins/newsletter/schema.ts +62 -0
  62. package/amplify/plugins/newsletter/secure-string-parameter.js +107 -0
  63. package/amplify/plugins/newsletter/secure-string-parameter.ts +120 -0
  64. package/amplify/plugins/newsletter/sending.js +140 -0
  65. package/amplify/plugins/newsletter/sending.ts +186 -0
  66. package/amplify/plugins/reader-steering/config.js +182 -0
  67. package/amplify/plugins/reader-steering/config.ts +290 -0
  68. package/amplify/plugins/reader-steering/function/ai_moderation.py +246 -0
  69. package/amplify/plugins/reader-steering/function/handler.py +1567 -0
  70. package/amplify/plugins/reader-steering/function/moderation-prompt.md +27 -0
  71. package/amplify/plugins/reader-steering/function/moderation.py +316 -0
  72. package/amplify/plugins/reader-steering/function/proposals.py +458 -0
  73. package/amplify/plugins/reader-steering/function/resource.js +27 -0
  74. package/amplify/plugins/reader-steering/function/resource.ts +35 -0
  75. package/amplify/plugins/reader-steering/papyrus.plugin.js +186 -0
  76. package/amplify/plugins/reader-steering/papyrus.plugin.ts +212 -0
  77. package/amplify/plugins/reader-steering/schema.js +229 -0
  78. package/amplify/plugins/reader-steering/schema.ts +231 -0
  79. package/amplify/plugins/registry.js +4 -0
  80. package/amplify/plugins/registry.ts +4 -0
  81. package/amplify/plugins/signup/schema.js +2 -1
  82. package/amplify/plugins/signup/schema.ts +2 -1
  83. package/amplify/plugins/types.ts +5 -0
  84. package/amplify/plugins/wire.js +1 -0
  85. package/amplify/plugins/wire.ts +1 -0
  86. package/amplify/publication/projection-lease.js +30 -0
  87. package/amplify/publication/projection-lease.ts +39 -0
  88. package/amplify/publication/redirect-and-index.js +105 -0
  89. package/amplify/publication/redirect-rules.js +73 -0
  90. package/amplify/publication/redirect-rules.ts +93 -0
  91. package/amplify/publication/s3-projection.js +7 -1
  92. package/amplify/publication/s3-projection.ts +9 -1
  93. package/amplify/site-backend-config.js +19 -0
  94. package/amplify/site-backend-config.ts +45 -1
  95. package/amplify/site-backend-names.js +4 -2
  96. package/amplify/site-backend-names.ts +4 -2
  97. package/amplify/site-backend.js +51 -4
  98. package/amplify/site-backend.ts +60 -4
  99. package/app/api/auth/prospectus/sign-in/route.ts +4 -0
  100. package/app/api/newsletter/confirm/route.ts +13 -0
  101. package/app/api/newsletter/preferences/route.ts +13 -0
  102. package/app/api/newsletter/state/route.ts +9 -0
  103. package/app/api/newsletter/subscribe/route.ts +9 -0
  104. package/app/api/newsletter/unsubscribe/route.ts +13 -0
  105. package/app/api/newsroom/chat/[...path]/route.ts +17 -0
  106. package/app/api/newsroom/curation/context/route.ts +9 -0
  107. package/app/api/newsroom/curation/priming/route.ts +9 -0
  108. package/app/api/newsroom/curation/publication/route.ts +9 -0
  109. package/app/api/reader/comment/delete/route.ts +9 -0
  110. package/app/api/reader/comment/edit/route.ts +9 -0
  111. package/app/api/reader/comment/route.ts +9 -0
  112. package/app/api/reader/flag/route.ts +9 -0
  113. package/app/api/reader/handle/route.ts +9 -0
  114. package/app/api/reader/me/route.ts +9 -0
  115. package/app/api/reader/rating/route.ts +9 -0
  116. package/app/api/reader/report/route.ts +9 -0
  117. package/app/api/reader/retired/route.ts +9 -0
  118. package/app/api/reader/submit/route.ts +9 -0
  119. package/app/api/reader/vote/route.ts +9 -0
  120. package/app/layout.tsx +5 -1
  121. package/bin/papyrus-infra.mjs +20 -1
  122. package/components/automation-panel.tsx +49 -0
  123. package/components/automation-run-now.tsx +97 -0
  124. package/components/automation-view.tsx +195 -0
  125. package/components/editorial-checks-admin-panel.tsx +362 -0
  126. package/components/editorial-finding-card.tsx +162 -0
  127. package/components/editorial-findings-panel.tsx +91 -0
  128. package/components/editorial-preview-popover.tsx +103 -0
  129. package/components/markus-body-editor.tsx +213 -0
  130. package/components/markus-ir-preview.tsx +147 -6
  131. package/components/moderation-panel.tsx +248 -0
  132. package/components/monitoring-overview-view.tsx +414 -0
  133. package/components/monitoring-panel.tsx +60 -0
  134. package/components/news-desk-client-provider.tsx +18 -18
  135. package/components/news-desk-taxonomy-client.ts +28 -8
  136. package/components/newsletter-panel.tsx +474 -0
  137. package/components/newsletter-sections.tsx +163 -0
  138. package/components/newsroom-access-gate.tsx +16 -2
  139. package/components/newsroom-article-editor.tsx +145 -18
  140. package/components/newsroom-articles-view.tsx +6 -6
  141. package/components/newsroom-assignment-detail.tsx +6 -6
  142. package/components/newsroom-board-view.tsx +14 -10
  143. package/components/newsroom-curation-bootstrap.tsx +45 -0
  144. package/components/newsroom-ops-overview.tsx +26 -96
  145. package/components/newsroom-ops-shell.tsx +9 -5
  146. package/components/newsroom-organizer.css +90 -61
  147. package/components/newsroom-overview-skeleton.tsx +25 -0
  148. package/components/newsroom-references-view.tsx +192 -49
  149. package/components/newsroom-theme-fonts/broadsheet-green.css +10 -0
  150. package/components/newsroom-theme-fonts/broadsheet-green.tsx +6 -0
  151. package/components/newsroom-theme-fonts/newspaper-gothic.css +9 -0
  152. package/components/newsroom-theme-fonts/newspaper-gothic.tsx +6 -0
  153. package/components/newsroom-theme-fonts/night-desk.css +10 -0
  154. package/components/newsroom-theme-fonts/night-desk.tsx +6 -0
  155. package/components/newsroom-theme-fonts/refined-newsprint.css +11 -0
  156. package/components/newsroom-theme-fonts/refined-newsprint.tsx +6 -0
  157. package/components/newsroom-theme.css +39 -0
  158. package/components/newsroom-theme.tsx +33 -0
  159. package/components/notification-center.tsx +281 -0
  160. package/components/papyrus-console-conversation.css +14 -0
  161. package/components/papyrus-console-conversation.tsx +55 -0
  162. package/components/papyrus-console-shell.tsx +62 -420
  163. package/components/papyrus-private-chat-panel.tsx +134 -0
  164. package/components/publication-cyclotron-panel.tsx +15 -10
  165. package/components/reader-auth-state.ts +1 -1
  166. package/components/reference-add-entry.tsx +75 -0
  167. package/components/reference-curation-controls.tsx +31 -22
  168. package/components/reference-cyclotron-priming.tsx +63 -25
  169. package/components/reference-cyclotron-status.tsx +4 -4
  170. package/components/reference-cyclotron-suggestion.tsx +15 -11
  171. package/components/reference-live-agreement.tsx +10 -10
  172. package/components/reference-priming.tsx +14 -10
  173. package/components/reference-relevance-review.tsx +4 -4
  174. package/components/reference-section-suggestion.tsx +2 -2
  175. package/components/topic-steering-workspace.tsx +241 -144
  176. package/components/use-editorial-checks.ts +133 -0
  177. package/infra/amplify-app-shell.js +96 -16
  178. package/infra/amplify-app-shell.ts +108 -18
  179. package/infra/cloud-email-config.js +32 -0
  180. package/infra/cloud-email-config.ts +39 -0
  181. package/infra/cloud-email.js +57 -0
  182. package/infra/cloud-email.test.js +69 -0
  183. package/infra/cloud-email.test.ts +66 -0
  184. package/infra/cloud-email.ts +60 -0
  185. package/infra/index.js +1 -0
  186. package/infra/secure-string-parameter-handler.js +41 -0
  187. package/infra/secure-string-parameter-handler.ts +41 -0
  188. package/infra/secure-string-parameter.js +79 -0
  189. package/infra/secure-string-parameter.ts +91 -0
  190. package/infra/site-config.js +92 -6
  191. package/infra/site-config.ts +138 -6
  192. package/lib/automation-client.ts +54 -0
  193. package/lib/automation-fixture.ts +93 -0
  194. package/lib/automation-overview.ts +252 -0
  195. package/lib/category-repository.ts +14 -12
  196. package/lib/console-web-context.ts +1 -1
  197. package/lib/curation-routes.ts +345 -0
  198. package/lib/cyclotron-suggestions.ts +11 -11
  199. package/lib/editorial-checks-backend.ts +16 -0
  200. package/lib/editorial-checks-client.ts +212 -0
  201. package/lib/editorial-checks-demo.ts +147 -0
  202. package/lib/editorial-checks-settings.ts +27 -0
  203. package/lib/editorial-checks.ts +175 -0
  204. package/lib/editorial-preview.ts +228 -0
  205. package/lib/monitoring-client.ts +83 -0
  206. package/lib/monitoring-overview-fixture.ts +51 -0
  207. package/lib/monitoring-overview.ts +188 -0
  208. package/lib/news-desk-session.ts +12 -21
  209. package/lib/newsletter-api.ts +418 -0
  210. package/lib/newsletter-client.ts +46 -0
  211. package/lib/newsletter-fixture.ts +184 -0
  212. package/lib/newsletter-model.ts +202 -0
  213. package/lib/newsroom-board.ts +27 -2
  214. package/lib/newsroom-demo-dashboard.ts +1 -1
  215. package/lib/newsroom-nav.ts +1 -1
  216. package/lib/newsroom-organizer-nav.ts +2 -1
  217. package/lib/papyrus-chat-admission.ts +65 -0
  218. package/lib/papyrus-hosted-chat.ts +217 -0
  219. package/lib/papyrus-private-chat-client.ts +81 -0
  220. package/lib/prospectus-session.ts +30 -14
  221. package/lib/prospectus-sign-in-routes.ts +106 -31
  222. package/lib/prospectus-site-identity.ts +46 -6
  223. package/lib/reader-api.ts +145 -0
  224. package/lib/reader-moderation.ts +220 -0
  225. package/lib/reader-retirement-route.ts +89 -0
  226. package/lib/reader-subject.md +29 -0
  227. package/lib/reader-subject.ts +53 -0
  228. package/lib/reference-review-details.ts +71 -0
  229. package/lib/reference-review-request.ts +14 -0
  230. package/lib/relevance-decisions.ts +42 -10
  231. package/lib/relevance-status.ts +6 -6
  232. package/lib/review-client.ts +120 -0
  233. package/lib/review-fixture.ts +45 -0
  234. package/lib/review-requests.ts +246 -0
  235. package/lib/semantic-graph.ts +2 -0
  236. package/lib/shared-chat-admission.ts +182 -0
  237. package/lib/site-brand-tenant.ts +11 -6
  238. package/lib/site-brand.ts +23 -3
  239. package/lib/site-origin-value.ts +50 -0
  240. package/lib/site-origin.ts +8 -0
  241. package/lib/use-reference-review-data.ts +44 -0
  242. package/middleware.ts +14 -3
  243. package/package.json +98 -4
  244. package/routes.manifest.json +227 -2
  245. package/src/with-papyrus.mjs +1 -1
  246. package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
  247. package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
  248. package/components/newsroom-fonts/playfair-display-OFL.txt +0 -93
  249. package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
  250. package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
  251. package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +0 -93
  252. package/components/newsroom-newsprint-board.css +0 -7
@@ -13,6 +13,11 @@ import { usePathname, useRouter } from "next/navigation";
13
13
  import type { ReactNode, RefObject } from "react";
14
14
  import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useTransition } from "react";
15
15
  import amplifyOutputs from "papyrus-amplify-outputs";
16
+ import { EditorialChecksAdminPanel } from "./editorial-checks-admin-panel";
17
+ import { AutomationPanel } from "./automation-panel";
18
+ import { MonitoringPanel } from "./monitoring-panel";
19
+ import { ModerationPanel } from "./moderation-panel";
20
+ import { NewsletterPanel } from "./newsletter-panel";
16
21
  import { SignupEntriesPanel } from "./signup-entries-panel";
17
22
  import { pluginSettings, type PluginOutputs } from "../lib/plugin-settings";
18
23
  import { generateClient } from "aws-amplify/data";
@@ -35,6 +40,7 @@ import {
35
40
  loadEditorReferencesData,
36
41
  loadEditorProcedureData,
37
42
  loadEditorSemanticRelationsData,
43
+ loadEditorReferenceReviewDetails,
38
44
  loadEditionForumThreads,
39
45
  loadNewsroomAssignmentPage,
40
46
  loadNewsroomMessagePage,
@@ -88,6 +94,7 @@ import { ReferenceSourcePreview } from "./reference-source-preview";
88
94
  import type { ReaderAuthSnapshot } from "./reader-auth-state";
89
95
  import { NewsDeskAccessGate } from "./newsroom-access-gate";
90
96
  import { NewsroomOpsOverview } from "./newsroom-ops-overview";
97
+ import { NewsroomCurationBootstrap } from "./newsroom-curation-bootstrap";
91
98
  import { NewsroomReferencesView } from "./newsroom-references-view";
92
99
  import { NewsroomAssignmentsView } from "./newsroom-assignments-view";
93
100
  import { NewsroomTopicsView } from "./newsroom-topics-view";
@@ -161,6 +168,9 @@ import {
161
168
  } from "../lib/reference-policy";
162
169
  import { isLocalPreviewEnabled } from "../lib/local-preview";
163
170
  import { currentRelevanceDecisions } from "../lib/relevance-decisions";
171
+ import { runReferenceReviewRequest } from "../lib/reference-review-request";
172
+ import { useReferenceReviewData } from "../lib/use-reference-review-data";
173
+ import { latestReferenceReviewReasons, latestReferenceReviewSections } from "../lib/reference-review-details";
164
174
  import { delegatedCurationsByReference, referencesWithHumanLatestCuration, type ShadowAgreement } from "../lib/cyclotron-suggestions";
165
175
  import { createLocalPreviewCyclotronFixtures } from "../lib/local-preview-seed";
166
176
  import { createLocalPreviewSectionFixtures } from "../lib/local-preview-section-fixtures";
@@ -246,7 +256,7 @@ type AssignmentAction = "claim" | "release" | "complete" | "cancel" | "reopen" |
246
256
  type ReportingPacketReviewDecision = "select" | "merge" | "brief" | "hold" | "kill";
247
257
  type AssignmentDeskViewMode = "queue" | "budget";
248
258
  type UserRoleAction = "grant" | "revoke";
249
- type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "delegations" | "signups";
259
+ type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "automations" | "delegations" | "signups" | "newsletter" | "moderation" | "editorial-checks" | "monitoring";
250
260
  export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "messages" | "assignments" | "administration" | "search";
251
261
  type LexicalRuleScope = "publication" | "corpus" | "classifier" | "category";
252
262
  type AnalysisReindexMode = "online-update" | "classifier-retrain" | "scoped-topic-rebuild" | "entity-graph-rebuild" | "generated-analysis-rebuild";
@@ -657,14 +667,14 @@ export function NewsDeskWorkspace({
657
667
  editorShellReady
658
668
  initialSelection={initialSelection}
659
669
  initialTab={initialTab}
660
- authState={{ status: "signedIn", label: "Demo Desk" }}
670
+ authState={{ status: "signedIn", label: "Demo section" }}
661
671
  isRefreshing={false}
662
672
  shellError={null}
663
673
  />
664
674
  );
665
675
  }
666
676
 
667
- if (session && sessionDashboard) {
677
+ if (session && sessionDashboard && session.shell.phase !== "loadingDesk") {
668
678
  return (
669
679
  <NewsDeskDashboard
670
680
  analysisProfiles={analysisProfiles}
@@ -878,7 +888,7 @@ function NewsDeskDashboard({
878
888
  const [mergeSelection, setMergeSelection] = useState<MergeSelection | null>(null);
879
889
  const [isPending, startTransition] = useTransition();
880
890
  const controlsDisabled = isPending || !canEdit;
881
- const initialAdministrationPanel = normalizeAdministrationPanel(initialSelection.panel);
891
+ const initialAdministrationPanel = normalizeAdministrationPanel(initialSelection.panel, Boolean(dashboard.isDemo));
882
892
  const [administrationPanel, setAdministrationPanel] = useState<AdministrationPanel>(initialAdministrationPanel);
883
893
  const [doctrineDrafts, setDoctrineDrafts] = useState<DoctrineEditorState>(() => buildDoctrineEditorState(dashboard.doctrineRecords));
884
894
  const [loadedSections, setLoadedSections] = useState<Record<string, boolean>>({
@@ -896,6 +906,11 @@ function NewsDeskDashboard({
896
906
  const [referencesRealtimeStatus, setReferencesRealtimeStatus] = useState<RealtimeSubscriptionStatus>("idle");
897
907
  const [referencesRealtimeError, setReferencesRealtimeError] = useState<string | null>(null);
898
908
  const referencesRef = useRef(references);
909
+ const referenceReviewsPending = useRef(new Map<string, Promise<void>>());
910
+ const [selectedReviewRead, setSelectedReviewRead] = useState<{ lineageId: string; state: "loading" | "ready" | "error"; error: string | null }>({ lineageId: "", state: "ready", error: null });
911
+ const [selectedReviewAttempt, setSelectedReviewAttempt] = useState(0);
912
+ const reviewReasons = useMemo(() => latestReferenceReviewReasons(messages), [messages]);
913
+ const reviewSectionIds = useMemo(() => latestReferenceReviewSections(messages), [messages]);
899
914
 
900
915
  useEffect(() => {
901
916
  referencesRef.current = references;
@@ -1119,8 +1134,8 @@ function NewsDeskDashboard({
1119
1134
  }, [dashboard.procedureDefinitions, dashboard.procedureRuns, dashboard.procedureVersions]);
1120
1135
 
1121
1136
  useEffect(() => {
1122
- setAdministrationPanel(normalizeAdministrationPanel(initialSelection.panel));
1123
- }, [initialSelection.panel]);
1137
+ setAdministrationPanel(normalizeAdministrationPanel(initialSelection.panel, Boolean(dashboard.isDemo)));
1138
+ }, [initialSelection.panel, dashboard.isDemo]);
1124
1139
 
1125
1140
  useEffect(() => {
1126
1141
  if (activeTab !== "administration") return;
@@ -1225,17 +1240,6 @@ function NewsDeskDashboard({
1225
1240
  if (dashboard.isDemo) return;
1226
1241
  let active = true;
1227
1242
 
1228
- if (activeTab === "messages" && !loadedSections.messages) {
1229
- setLoadedSections((current) => ({ ...current, messages: true }));
1230
- void loadEditorMessagesData()
1231
- .then((nextMessages) => {
1232
- if (active) setMessages(nextMessages);
1233
- })
1234
- .catch((error) => {
1235
- if (active) setActionState({ id: "messages-load", message: error instanceof Error ? error.message : "messages load failed", tone: "error" });
1236
- });
1237
- }
1238
-
1239
1243
  if (activeTab === "assignments" && !loadedSections.assignments) {
1240
1244
  setLoadedSections((current) => ({ ...current, assignments: true }));
1241
1245
  void loadEditorAssignmentsData()
@@ -1263,23 +1267,6 @@ function NewsDeskDashboard({
1263
1267
  });
1264
1268
  }
1265
1269
 
1266
- if ((activeTab === "assignments" || activeTab === "references" || activeTab === "messages") && !loadedSections.semanticRelations) {
1267
- setLoadedSections((current) => ({ ...current, semanticRelations: true }));
1268
- void loadEditorSemanticRelationsData()
1269
- .then((relations) => {
1270
- if (active) setSemanticRelations(relations);
1271
- })
1272
- .catch(() => {
1273
- if (active) setSemanticRelations([]);
1274
- });
1275
- if (!loadedSections.messages) {
1276
- setLoadedSections((current) => ({ ...current, messages: true }));
1277
- void loadEditorMessagesData().then((nextMessages) => {
1278
- if (active) setMessages(nextMessages);
1279
- }).catch(() => undefined);
1280
- }
1281
- }
1282
-
1283
1270
  if ((activeTab === "topics" || activeTab === "desks" || activeTab === "concepts" || activeTab === "administration") && !loadedSections.fullDashboard) {
1284
1271
  setLoadedSections((current) => ({ ...current, fullDashboard: true }));
1285
1272
  void loadEditorFullNewsDeskDashboard({ isAdmin: Boolean(dashboard.canManageUsers) })
@@ -1334,6 +1321,37 @@ function NewsDeskDashboard({
1334
1321
  refreshNewsroomSections,
1335
1322
  ]);
1336
1323
 
1324
+ const needsReferenceReviewData = activeTab === "references" || activeTab === "assignments" || activeTab === "messages";
1325
+ const selectedReviewLineageId = pathname ? pathnameReferenceLineageId ?? "" : initialSelection.reference ?? "";
1326
+ const referenceReviewData = useReferenceReviewData({
1327
+ enabled: canLoadPrivateCorpus && needsReferenceReviewData && !(activeTab === "references" && selectedReviewLineageId),
1328
+ loadRelations: loadEditorSemanticRelationsData,
1329
+ loadMessages: loadEditorMessagesData,
1330
+ onRelations: setSemanticRelations,
1331
+ onMessages: setMessages,
1332
+ });
1333
+
1334
+ useEffect(() => {
1335
+ if (!canLoadPrivateCorpus || activeTab !== "references" || !selectedReviewLineageId) return;
1336
+ let active = true;
1337
+ setSelectedReviewRead({ lineageId: selectedReviewLineageId, state: "loading", error: null });
1338
+ void loadEditorReferenceReviewDetails(selectedReviewLineageId).then(({ relations, messages: history }) => {
1339
+ if (!active) return;
1340
+ setSemanticRelations((current) => [...current.filter((relation) => relation.subjectLineageId !== selectedReviewLineageId || relation.subjectKind !== "reference"), ...relations]);
1341
+ setMessages((current) => [...current.filter((message) => !history.some((entry) => entry.id === message.id)), ...history]);
1342
+ setSelectedReviewRead({ lineageId: selectedReviewLineageId, state: "ready", error: null });
1343
+ }).catch((error) => {
1344
+ if (active) setSelectedReviewRead({ lineageId: selectedReviewLineageId, state: "error", error: error instanceof Error ? error.message : "Reference review details could not be loaded." });
1345
+ });
1346
+ return () => { active = false; };
1347
+ }, [activeTab, canLoadPrivateCorpus, selectedReviewLineageId, selectedReviewAttempt]);
1348
+ const selectedReviewIsCurrent = selectedReviewLineageId && !dashboard.isDemo;
1349
+ const predictionState = selectedReviewIsCurrent
1350
+ ? selectedReviewRead.lineageId === selectedReviewLineageId ? selectedReviewRead.state : "loading"
1351
+ : referenceReviewData.state;
1352
+ const predictionError = selectedReviewIsCurrent ? selectedReviewRead.error : referenceReviewData.error;
1353
+ const retryReferenceReviewData = () => { referenceReviewData.retry(); setSelectedReviewAttempt((current) => current + 1); };
1354
+
1337
1355
  const applyReferenceRecord = useCallback((nextReference: ReferenceRecord) => {
1338
1356
  const { nextRecords, nextRecord, previousRecord } = upsertReferenceRecords(referencesRef.current, nextReference);
1339
1357
  referencesRef.current = nextRecords;
@@ -1588,96 +1606,98 @@ function NewsDeskDashboard({
1588
1606
  reasonCode?: ReferenceRejectionReasonCode | null,
1589
1607
  relevance?: { decisionRelationId: string; shareable: boolean },
1590
1608
  sectionId?: string | null,
1591
- ) {
1592
- const nextStatus = referenceCurationStatusForAction(action);
1593
- setActionState({ id: reference.id, message: `${action} pending`, tone: "pending" });
1594
- if (dashboard.isDemo) {
1595
- const now = new Date().toISOString();
1596
- const messageId = `message-demo-${reference.id}-${action}-${now.replace(/[^0-9]/g, "")}`;
1597
- const relationId = `semantic-relation-demo-${messageId}`;
1598
- applyReferenceRecord(buildReviewedReferenceRecord(referencesRef.current, reference, {
1599
- actorLabel: authState.label,
1600
- nextStatus,
1601
- note: note ?? null,
1602
- now,
1603
- }));
1604
- setMessages((current) => [{
1605
- id: messageId,
1606
- messageKind: "reference_curation",
1607
- messageDomain: "commentary",
1608
- status: "active",
1609
- body: note?.trim() || `${authState.label} marked this reference ${nextStatus}.`,
1610
- summary: `${reference.title ?? reference.externalItemId}: ${nextStatus}`,
1611
- source: "newsroom",
1612
- authorLabel: authState.label,
1613
- createdAt: now,
1614
- updatedAt: now,
1615
- metadata: {
1616
- action,
1617
- curationStatus: nextStatus,
1618
- reasonCode: action === "reject" ? reasonCode ?? null : null,
1619
- ...(relevance ? { decisionRelationId: relevance.decisionRelationId, shareable: relevance.shareable } : {}),
1620
- ...(action === "accept" && sectionId ? { sectionId } : {}),
1621
- },
1622
- }, ...current]);
1623
- setSemanticRelations((current) => [{
1624
- id: relationId,
1625
- relationState: "current",
1626
- predicate: "comment",
1627
- relationTypeId: "semantic-relation-type-comment",
1628
- relationTypeKey: "comment",
1629
- relationDomain: "commentary",
1630
- subjectKind: "message",
1631
- subjectId: messageId,
1632
- subjectLineageId: messageId,
1633
- subjectVersionNumber: 1,
1634
- objectKind: "reference",
1635
- objectId: reference.id,
1636
- objectLineageId: reference.lineageId ?? reference.id,
1637
- objectVersionNumber: reference.versionNumber ?? null,
1638
- subjectStateKey: `message#${messageId}#current`,
1639
- objectStateKey: `reference#${reference.lineageId ?? reference.id}#current`,
1640
- objectSubjectStateKey: `reference#${reference.lineageId ?? reference.id}#current#message`,
1641
- predicateObjectStateKey: `comment#reference#${reference.lineageId ?? reference.id}#current`,
1642
- subjectVersionKey: `message#${messageId}`,
1643
- objectVersionKey: `reference#${reference.id}`,
1644
- rank: 1,
1645
- reviewRecommended: false,
1646
- importedAt: now,
1647
- }, ...current]);
1648
- setActionState({ id: reference.id, message: `${action} saved`, tone: "ok" });
1649
- return;
1650
- }
1651
- startTransition(() => {
1652
- void (async () => {
1653
- try {
1654
- const response = await dataClient.mutations.reviewReferenceCuration(
1655
- {
1656
- referenceId: reference.id,
1657
- action,
1658
- actorLabel: authState.label,
1659
- note: note?.trim() || undefined,
1660
- reasonCode: action === "reject" ? reasonCode ?? undefined : undefined,
1661
- decisionRelationId: relevance?.decisionRelationId,
1662
- shareable: relevance ? relevance.shareable : undefined,
1663
- sectionId: action === "accept" ? sectionId ?? undefined : undefined,
1664
- },
1665
- { authMode: USER_POOL_AUTH_MODE },
1666
- );
1667
- const review = assertReferenceReviewMutationSucceeded(response, reference.id);
1668
- const status = review.status ?? nextStatus;
1669
- const now = new Date().toISOString();
1670
- applyReferenceRecord(buildReviewedReferenceRecord(referencesRef.current, reference, {
1609
+ ): Promise<void> {
1610
+ return runReferenceReviewRequest(referenceReviewsPending.current, reference.lineageId ?? reference.id, async () => {
1611
+ if (!canEdit) throw new Error("Your session does not have permission to review references.");
1612
+ const nextStatus = referenceCurationStatusForAction(action);
1613
+ setActionState({ id: reference.id, message: `${action} pending`, tone: "pending" });
1614
+ if (dashboard.isDemo) {
1615
+ const now = new Date().toISOString();
1616
+ const messageId = `message-demo-${reference.id}-${action}-${now.replace(/[^0-9]/g, "")}`;
1617
+ const relationId = `semantic-relation-demo-${messageId}`;
1618
+ applyReferenceRecord(buildReviewedReferenceRecord(referencesRef.current, reference, {
1619
+ actorLabel: authState.label,
1620
+ nextStatus,
1621
+ note: note ?? null,
1622
+ now,
1623
+ }));
1624
+ setMessages((current) => [{
1625
+ id: messageId,
1626
+ messageKind: "reference_curation",
1627
+ messageDomain: "commentary",
1628
+ status: "active",
1629
+ body: note?.trim() || `${authState.label} marked this reference ${nextStatus}.`,
1630
+ summary: `${reference.title ?? reference.externalItemId}: ${nextStatus}`,
1631
+ source: "newsroom",
1632
+ authorLabel: authState.label,
1633
+ createdAt: now,
1634
+ updatedAt: now,
1635
+ metadata: {
1636
+ referenceLineageId: reference.lineageId ?? reference.id,
1637
+ action,
1638
+ curationStatus: nextStatus,
1639
+ reasonCode: action === "reject" ? reasonCode ?? null : null,
1640
+ ...(relevance ? { decisionRelationId: relevance.decisionRelationId, shareable: relevance.shareable } : {}),
1641
+ ...(action === "accept" && sectionId ? { sectionId } : {}),
1642
+ },
1643
+ }, ...current]);
1644
+ setSemanticRelations((current) => [{
1645
+ id: relationId,
1646
+ relationState: "current",
1647
+ predicate: "comment",
1648
+ relationTypeId: "semantic-relation-type-comment",
1649
+ relationTypeKey: "comment",
1650
+ relationDomain: "commentary",
1651
+ subjectKind: "message",
1652
+ subjectId: messageId,
1653
+ subjectLineageId: messageId,
1654
+ subjectVersionNumber: 1,
1655
+ objectKind: "reference",
1656
+ objectId: reference.id,
1657
+ objectLineageId: reference.lineageId ?? reference.id,
1658
+ objectVersionNumber: reference.versionNumber ?? null,
1659
+ subjectStateKey: `message#${messageId}#current`,
1660
+ objectStateKey: `reference#${reference.lineageId ?? reference.id}#current`,
1661
+ objectSubjectStateKey: `reference#${reference.lineageId ?? reference.id}#current#message`,
1662
+ predicateObjectStateKey: `comment#reference#${reference.lineageId ?? reference.id}#current`,
1663
+ subjectVersionKey: `message#${messageId}`,
1664
+ objectVersionKey: `reference#${reference.id}`,
1665
+ rank: 1,
1666
+ reviewRecommended: false,
1667
+ importedAt: now,
1668
+ }, ...current]);
1669
+ setActionState({ id: reference.id, message: `${action} saved`, tone: "ok" });
1670
+ return;
1671
+ }
1672
+ try {
1673
+ const response = await dataClient.mutations.reviewReferenceCuration(
1674
+ {
1675
+ referenceId: reference.id,
1676
+ action,
1671
1677
  actorLabel: authState.label,
1672
- nextStatus: status,
1673
- note: note?.trim() || null,
1674
- now,
1675
- }));
1676
- setActionState({ id: reference.id, message: `${action} saved`, tone: "ok" });
1677
- } catch (error) {
1678
- setActionState({ id: reference.id, message: error instanceof Error ? error.message : `${action} failed`, tone: "error" });
1679
- }
1680
- })();
1678
+ note: note?.trim() || undefined,
1679
+ reasonCode: action === "reject" ? reasonCode ?? undefined : undefined,
1680
+ decisionRelationId: relevance?.decisionRelationId,
1681
+ shareable: relevance ? relevance.shareable : undefined,
1682
+ sectionId: action === "accept" ? sectionId ?? undefined : undefined,
1683
+ },
1684
+ { authMode: USER_POOL_AUTH_MODE },
1685
+ );
1686
+ const review = assertReferenceReviewMutationSucceeded(response, reference.id);
1687
+ const status = review.status ?? nextStatus;
1688
+ const now = new Date().toISOString();
1689
+ applyReferenceRecord(buildReviewedReferenceRecord(referencesRef.current, reference, {
1690
+ actorLabel: authState.label,
1691
+ nextStatus: status,
1692
+ note: note?.trim() || null,
1693
+ now,
1694
+ }));
1695
+ setActionState({ id: reference.id, message: `${action} saved`, tone: "ok" });
1696
+ retryReferenceReviewData();
1697
+ } catch (error) {
1698
+ setActionState({ id: reference.id, message: error instanceof Error ? error.message : `${action} failed`, tone: "error" });
1699
+ throw error;
1700
+ }
1681
1701
  });
1682
1702
  }
1683
1703
 
@@ -2998,7 +3018,7 @@ function NewsDeskDashboard({
2998
3018
  return entries;
2999
3019
  }, [summaryStatus, tabCounts]);
3000
3020
  const opsPageTitle = activeTab === "overview"
3001
- ? "Overview"
3021
+ ? "Notification Center"
3002
3022
  : activeTab === "administration" && administrationPanel === "sections"
3003
3023
  ? "Sections"
3004
3024
  : formatDeskSectionHeadline(activeTab);
@@ -3056,9 +3076,11 @@ function NewsDeskDashboard({
3056
3076
 
3057
3077
  {activeTab === "overview" ? (
3058
3078
  <NewsroomOpsOverview
3079
+ actorLabel={authState.label}
3059
3080
  assignments={assignments}
3081
+ decisions={relevanceDecisions}
3060
3082
  demo={Boolean(dashboard.isDemo)}
3061
- messages={messages}
3083
+ humanCuratedReferenceLineageIds={humanCuratedReferenceLineageIds}
3062
3084
  newsroomSections={newsroomSections}
3063
3085
  references={references}
3064
3086
  />
@@ -3114,10 +3136,11 @@ function NewsDeskDashboard({
3114
3136
  />
3115
3137
  ) : null}
3116
3138
  {activeTab === "references" ? (
3139
+ <NewsroomCurationBootstrap enabled={!dashboard.isDemo}>
3117
3140
  <NewsroomReferencesView
3118
3141
  demo={Boolean(dashboard.isDemo)}
3119
3142
  disabled={controlsDisabled}
3120
- initialReferenceLineageId={initialSelection.reference ?? pathnameReferenceLineageId}
3143
+ initialReferenceLineageId={selectedReviewLineageId}
3121
3144
  cyclotronStatus={cyclotronStatus}
3122
3145
  cyclotronPriming={{
3123
3146
  labels: primingLabels,
@@ -3129,8 +3152,8 @@ function NewsDeskDashboard({
3129
3152
  },
3130
3153
  }}
3131
3154
  decisions={relevanceDecisions}
3132
- humanCuratedReferenceLineageIds={humanCuratedReferenceLineageIds}
3133
- delegatedCurations={delegatedCurations}
3155
+ humanCuratedReferenceLineageIds={selectedReviewIsCurrent && predictionState !== "ready" ? undefined : humanCuratedReferenceLineageIds}
3156
+ delegatedCurations={selectedReviewIsCurrent && predictionState !== "ready" ? undefined : delegatedCurations}
3134
3157
  shadowAgreement={shadowAgreement}
3135
3158
  onRequestReviewRate={relevanceCyclotronId ? async (choice) => {
3136
3159
  const request = buildReviewRateRequest(choice, authState.label);
@@ -3143,14 +3166,23 @@ function NewsDeskDashboard({
3143
3166
  curation.note ?? undefined,
3144
3167
  normalizeReferenceRejectionReasonCode(curation.reasonCode),
3145
3168
  { decisionRelationId: curation.decisionRelationId, shareable: curation.shareable },
3169
+ curation.sectionId,
3146
3170
  )}
3147
3171
  sections={sectionChoices}
3148
3172
  sectionSuggestions={sectionSuggestions}
3149
3173
  sectionAgreement={siteSectionAgreement}
3150
- onReview={(reference, action, options) => runReferenceCurationAction(reference, action, undefined, undefined, undefined, options?.sectionId)}
3174
+ predictionState={predictionState}
3175
+ predictionError={predictionError}
3176
+ onRetryPredictions={retryReferenceReviewData}
3177
+ reviewReasons={reviewReasons}
3178
+ reviewSectionIds={reviewSectionIds}
3179
+ onReview={(reference, action, options) => runReferenceCurationAction(reference, action, options?.note, options?.reasonCode,
3180
+ options?.decisionRelationId ? { decisionRelationId: options.decisionRelationId, shareable: options.shareable === true } : undefined,
3181
+ options?.sectionId)}
3151
3182
  referenceAttachments={referenceAttachments}
3152
3183
  references={references}
3153
3184
  />
3185
+ </NewsroomCurationBootstrap>
3154
3186
  ) : null}
3155
3187
  {activeTab === "messages" ? (
3156
3188
  <MessagesDeskView
@@ -3871,7 +3903,7 @@ function NewsroomSectionRail({
3871
3903
  <aside className="newsroom-section-rail" data-newsroom-section-rail aria-labelledby="newsroom-section-rail-title" ref={railRef}>
3872
3904
  <header className="newsroom-section-rail__header">
3873
3905
  <p className="story-label">Sections</p>
3874
- <h2 id="newsroom-section-rail-title">Section Desk</h2>
3906
+ <h2 id="newsroom-section-rail-title">Section</h2>
3875
3907
  </header>
3876
3908
  <div className="newsroom-section-rail__body">
3877
3909
  {canonicalSections.length ? (
@@ -4308,6 +4340,15 @@ function AdministrationDeskView({
4308
4340
  <strong>Procedures</strong>
4309
4341
  <span>Procedure registry and versioned Tactus code</span>
4310
4342
  </Link>
4343
+ <Link
4344
+ href={administrationPanelHref("automations", isDemo)}
4345
+ data-news-desk-admin-nav="automations"
4346
+ data-active={administrationPanel === "automations" || undefined}
4347
+ onClick={() => onPanelChange("automations")}
4348
+ >
4349
+ <strong>Automation</strong>
4350
+ <span>Procedures that start on their own</span>
4351
+ </Link>
4311
4352
  <Link
4312
4353
  href={administrationPanelHref("delegations", isDemo)}
4313
4354
  data-news-desk-admin-nav="delegations"
@@ -4317,6 +4358,24 @@ function AdministrationDeskView({
4317
4358
  <strong>Agent delegations</strong>
4318
4359
  <span>Let an agent curate References for you</span>
4319
4360
  </Link>
4361
+ <Link
4362
+ href={administrationPanelHref("editorial-checks", isDemo)}
4363
+ data-news-desk-admin-nav="editorial-checks"
4364
+ data-active={administrationPanel === "editorial-checks" || undefined}
4365
+ onClick={() => onPanelChange("editorial-checks")}
4366
+ >
4367
+ <strong>Editorial checks</strong>
4368
+ <span>Policies the article editor checks drafts against</span>
4369
+ </Link>
4370
+ <Link
4371
+ href={administrationPanelHref("monitoring", isDemo)}
4372
+ data-news-desk-admin-nav="monitoring"
4373
+ data-active={administrationPanel === "monitoring" || undefined}
4374
+ onClick={() => onPanelChange("monitoring")}
4375
+ >
4376
+ <strong>Monitoring</strong>
4377
+ <span>The automated research pipeline, read only</span>
4378
+ </Link>
4320
4379
  {signupPluginEnabled() ? (
4321
4380
  <Link
4322
4381
  href={administrationPanelHref("signups", isDemo)}
@@ -4328,9 +4387,36 @@ function AdministrationDeskView({
4328
4387
  <span>Addresses from the site&apos;s signup forms</span>
4329
4388
  </Link>
4330
4389
  ) : null}
4390
+ {newsletterPluginEnabled(isDemo) ? (
4391
+ <Link
4392
+ href={administrationPanelHref("newsletter", isDemo)}
4393
+ data-news-desk-admin-nav="newsletter"
4394
+ data-active={administrationPanel === "newsletter" || undefined}
4395
+ onClick={() => onPanelChange("newsletter")}
4396
+ >
4397
+ <strong>Newsletter</strong>
4398
+ <span>Subscribers, issues and delivery for the site&apos;s email</span>
4399
+ </Link>
4400
+ ) : null}
4401
+ {readerPluginEnabled() ? (
4402
+ <Link
4403
+ href={administrationPanelHref("moderation", isDemo)}
4404
+ data-news-desk-admin-nav="moderation"
4405
+ data-active={administrationPanel === "moderation" || undefined}
4406
+ onClick={() => onPanelChange("moderation")}
4407
+ >
4408
+ <strong>Moderation</strong>
4409
+ <span>Reader submissions and comments</span>
4410
+ </Link>
4411
+ ) : null}
4331
4412
  </nav>}
4332
4413
  <div className="news-desk-settings-panel">
4333
4414
  {administrationPanel === "signups" ? <SignupEntriesPanel /> : null}
4415
+ {administrationPanel === "newsletter" ? <NewsletterPanel isAdmin={canManageUsers} isDemo={isDemo} /> : null}
4416
+ {administrationPanel === "moderation" ? <ModerationPanel /> : null}
4417
+ {administrationPanel === "editorial-checks" ? <EditorialChecksAdminPanel /> : null}
4418
+ {administrationPanel === "automations" ? <AutomationPanel isDemo={isDemo} /> : null}
4419
+ {administrationPanel === "monitoring" ? <MonitoringPanel isAdmin={canManageUsers} isDemo={isDemo} /> : null}
4334
4420
  {administrationPanel === "delegations" ? <AdministrationDelegationsPanel disabled={disabled} /> : null}
4335
4421
  {administrationPanel === "users" ? (
4336
4422
  <AdministrationUsersPanel
@@ -5657,7 +5743,7 @@ function DesksDeskView({
5657
5743
  <div className="news-desk-columns news-desk-columns--administration news-desk-columns--taxonomy" data-news-desk-section="desks">
5658
5744
  <div className="news-desk-main-column">
5659
5745
  <section className="category-steering-section category-steering-section--lead" aria-labelledby="newsroom-desks-title">
5660
- <SectionHeader title="News Desks" detail={`${roots.length} root topic desks`} />
5746
+ <SectionHeader title="News sections" detail={`${roots.length} root topic sections`} />
5661
5747
  <div className="news-desk-desk-list">
5662
5748
  {roots.length ? roots.map((root) => {
5663
5749
  const rootNode = root.node ?? categoryToCategoryTreeNode(root.category);
@@ -5677,11 +5763,11 @@ function DesksDeskView({
5677
5763
  >
5678
5764
  <span>{rootNode.shortTitle ?? deriveShortTitle(rootNode.displayName)}</span>
5679
5765
  <strong>{rootNode.displayName}</strong>
5680
- <p>{rootNode.description ?? "No desk description yet."}</p>
5766
+ <p>{rootNode.description ?? "No section description yet."}</p>
5681
5767
  <small>{referenceCount} refs / {assignmentCount} assignments / {root.subcategorys.length} subtopics / {doctrineStatus.savedCount} of 2 doctrine slots</small>
5682
5768
  </Link>
5683
5769
  );
5684
- }) : <EmptyRow label="No root topic desks are available in the canonical category set" />}
5770
+ }) : <EmptyRow label="No root topic sections are available in the canonical category set" />}
5685
5771
  </div>
5686
5772
  </section>
5687
5773
 
@@ -5702,18 +5788,18 @@ function DesksDeskView({
5702
5788
 
5703
5789
  <aside className="news-desk-rail-column">
5704
5790
  <section className="category-steering-section" aria-labelledby="desk-index-notes-title">
5705
- <SectionHeader title="Desk Rules" detail="Root topics only" />
5791
+ <SectionHeader title="Section rules" detail="Root topics only" />
5706
5792
  <div className="news-desk-ledger-list">
5707
5793
  <article className="news-desk-ledger-item">
5708
5794
  <header>
5709
- <strong>Desk Identity</strong>
5795
+ <strong>Section identity</strong>
5710
5796
  <span>Category</span>
5711
5797
  </header>
5712
5798
  <p>Official name, short title, subtitle, and description stay on the accepted root Category record.</p>
5713
5799
  </article>
5714
5800
  <article className="news-desk-ledger-item">
5715
5801
  <header>
5716
- <strong>Desk Doctrine</strong>
5802
+ <strong>Section doctrine</strong>
5717
5803
  <span>Private Item</span>
5718
5804
  </header>
5719
5805
  <p>Mission and policies are private doctrine Items tied to the root category lineage.</p>
@@ -5763,7 +5849,7 @@ function DeskDetailPanel({
5763
5849
  <section className="category-steering-section news-desk-desk-detail" aria-labelledby="selected-desk-title">
5764
5850
  <header className="news-desk-topic-detail__header">
5765
5851
  <div>
5766
- <p className="story-label">News Desk</p>
5852
+ <p className="story-label">News section</p>
5767
5853
  <h3 id="selected-desk-title">{rootNode.displayName}</h3>
5768
5854
  <span>{rootNode.shortTitle ?? deriveShortTitle(rootNode.displayName)}</span>
5769
5855
  </div>
@@ -5846,7 +5932,7 @@ function CategoryDoctrineEditorCard({
5846
5932
  <article className="news-desk-doctrine-card" data-news-desk-doctrine={definition.slug}>
5847
5933
  <header className="news-desk-doctrine-card__header">
5848
5934
  <div>
5849
- <p className="story-label">Desk Doctrine</p>
5935
+ <p className="story-label">Section doctrine</p>
5850
5936
  <h3>{definition.label}</h3>
5851
5937
  </div>
5852
5938
  <span>{record ? "Saved record" : "Empty slot"}</span>
@@ -12211,7 +12297,7 @@ function AssignmentRow({
12211
12297
  {context ? (
12212
12298
  <>
12213
12299
  <p className="news-desk-assignment-row__angle">
12214
- <span>{context.deskTitle ?? context.deskKey ?? "Desk"}</span>
12300
+ <span>{context.deskTitle ?? context.deskKey ?? "Section"}</span>
12215
12301
  {`${context.focusTitle ?? context.focusKey ?? "focus"} / ${context.targetSystemType ?? context.contextProfile ?? "context"}`}
12216
12302
  </p>
12217
12303
  {context.contextProfile || context.contextTokenBudget ? (
@@ -12326,7 +12412,7 @@ function normalizeAnalysisReindexMode(value: string | null | undefined): Analysi
12326
12412
  function formatAnalysisReindexModeLabel(mode: AnalysisReindexMode): string {
12327
12413
  if (mode === "online-update") return "Online update - new or changed accepted references";
12328
12414
  if (mode === "classifier-retrain") return "Classifier retrain - rebuild topic classifier";
12329
- if (mode === "scoped-topic-rebuild") return "Scoped topic rebuild - one desk or focus";
12415
+ if (mode === "scoped-topic-rebuild") return "Scoped topic rebuild - one section or focus";
12330
12416
  if (mode === "entity-graph-rebuild") return "Entity graph rebuild - generated graph outputs";
12331
12417
  return "Generated analysis rebuild - clear and rebuild targeted generated outputs";
12332
12418
  }
@@ -12356,7 +12442,7 @@ function mergeAnalysisCorpora(configuredCorpora: CategorySteeringCorpus[], liveC
12356
12442
  function analysisReindexModeHelp(mode: AnalysisReindexMode): string {
12357
12443
  if (mode === "generated-analysis-rebuild") return "Use this for a full generated-output rebuild for the selected profile. It still creates an assignment only; the worker/operator executes it later.";
12358
12444
  if (mode === "classifier-retrain") return "Use this when authoritative labels or accepted references should retrain the semi-supervised topic classifier.";
12359
- if (mode === "scoped-topic-rebuild") return "Use this when proposals for a desk or focus topic should be regenerated from the accepted taxonomy plus steering feedback.";
12445
+ if (mode === "scoped-topic-rebuild") return "Use this when proposals for a section or focus topic should be regenerated from the accepted taxonomy plus steering feedback.";
12360
12446
  if (mode === "entity-graph-rebuild") return "Use this when generated ontology/entity graph records should be rebuilt from accepted references.";
12361
12447
  return "Use this for incremental processing of new or changed accepted references without clearing broader generated outputs.";
12362
12448
  }
@@ -12794,9 +12880,20 @@ function signupPluginEnabled(): boolean {
12794
12880
  return pluginSettings(amplifyOutputs as PluginOutputs, "signup") !== null;
12795
12881
  }
12796
12882
 
12797
- function normalizeAdministrationPanel(value: string | null | undefined): AdministrationPanel {
12798
- if (value === "policies" || value === "sections" || value === "procedures" || value === "delegations") return value;
12883
+ /** The Newsletter panel exists when the site enabled the plugin; `?demo=1` shows it with fixture data so it can be seen and tested. */
12884
+ function newsletterPluginEnabled(demo = false): boolean {
12885
+ return demo || pluginSettings(amplifyOutputs as PluginOutputs, "newsletter") !== null;
12886
+ }
12887
+
12888
+ function readerPluginEnabled(): boolean {
12889
+ return pluginSettings(amplifyOutputs as PluginOutputs, "reader-steering") !== null;
12890
+ }
12891
+
12892
+ function normalizeAdministrationPanel(value: string | null | undefined, demo = false): AdministrationPanel {
12893
+ if (value === "policies" || value === "sections" || value === "procedures" || value === "automations" || value === "delegations" || value === "editorial-checks" || value === "monitoring") return value;
12799
12894
  if (value === "signups" && signupPluginEnabled()) return value;
12895
+ if (value === "newsletter" && newsletterPluginEnabled(demo)) return value;
12896
+ if (value === "moderation" && readerPluginEnabled()) return value;
12800
12897
  return "users";
12801
12898
  }
12802
12899
 
@@ -12995,7 +13092,7 @@ function formatDeskSectionHeadline(section: NewsDeskTab): string {
12995
13092
 
12996
13093
  function formatDeskSectionLede(section: NewsDeskTab): string {
12997
13094
  if (section === "search") return "Search the newsroom knowledge base with semantic and ontology context.";
12998
- if (section === "desks") return "The desks and topic categories that organize coverage, with each desk's mission and policies.";
13095
+ if (section === "desks") return "The sections and topic categories that organize coverage, with each section's mission and policies.";
12999
13096
  if (section === "topics") return "Review suggested changes to the subject areas the newsroom covers.";
13000
13097
  if (section === "concepts") return "Browse people, organizations, places, and ideas found in the source material.";
13001
13098
  if (section === "references") return "Review source materials before they become usable evidence.";