@anthusai/papyrus 2.0.0-next.11 → 2.0.0-next.110

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 (227) 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 +152 -62
  6. package/amplify/data/resource.ts +158 -62
  7. package/amplify/data/schema.js +2101 -1987
  8. package/amplify/data/schema.ts +197 -70
  9. package/amplify/functions/assignment-action/data-access.js +2 -0
  10. package/amplify/functions/assignment-action/data-access.ts +2 -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 +64 -3
  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 +2 -2
  24. package/amplify/functions/editorial-checks/requirements.txt +1 -1
  25. package/amplify/functions/editorial-checks/resource.js +3 -3
  26. package/amplify/functions/editorial-checks/resource.ts +3 -3
  27. package/amplify/functions/editorial-checks/schema.js +38 -8
  28. package/amplify/functions/editorial-checks/schema.ts +38 -8
  29. package/amplify/functions/editorial-checks/wire.js +4 -0
  30. package/amplify/functions/editorial-checks/wire.ts +4 -0
  31. package/amplify/functions/monitoring-view/handler.py +11 -0
  32. package/amplify/functions/monitoring-view/resource.js +24 -0
  33. package/amplify/functions/monitoring-view/resource.ts +34 -0
  34. package/amplify/functions/monitoring-view/schema.js +29 -0
  35. package/amplify/functions/monitoring-view/schema.ts +31 -0
  36. package/amplify/functions/monitoring-view/wire.js +64 -0
  37. package/amplify/functions/monitoring-view/wire.ts +81 -0
  38. package/amplify/functions/public-forms/core.js +247 -0
  39. package/amplify/functions/public-forms/core.ts +310 -0
  40. package/amplify/functions/public-forms/handler.ts +41 -0
  41. package/amplify/functions/public-forms/origins.js +35 -0
  42. package/amplify/functions/public-forms/origins.ts +43 -0
  43. package/amplify/functions/public-forms/resource.js +9 -0
  44. package/amplify/functions/public-forms/resource.ts +10 -0
  45. package/amplify/functions/public-forms/wire.js +25 -0
  46. package/amplify/functions/public-forms/wire.ts +32 -0
  47. package/amplify/functions/shared/editorial-context.ts +63 -4
  48. package/amplify/functions/shared/python-bundle.js +20 -14
  49. package/amplify/functions/shared/python-bundle.ts +25 -18
  50. package/amplify/plugins/newsletter/config.js +245 -0
  51. package/amplify/plugins/newsletter/config.ts +305 -0
  52. package/amplify/plugins/newsletter/function/handler.py +6 -0
  53. package/amplify/plugins/newsletter/function/requirements.txt +5 -0
  54. package/amplify/plugins/newsletter/ops/handler.py +12 -0
  55. package/amplify/plugins/newsletter/ops/resource.js +27 -0
  56. package/amplify/plugins/newsletter/ops/resource.ts +37 -0
  57. package/amplify/plugins/newsletter/papyrus.plugin.js +263 -0
  58. package/amplify/plugins/newsletter/papyrus.plugin.ts +311 -0
  59. package/amplify/plugins/newsletter/schema.js +57 -0
  60. package/amplify/plugins/newsletter/schema.ts +62 -0
  61. package/amplify/plugins/newsletter/secure-string-parameter.js +107 -0
  62. package/amplify/plugins/newsletter/secure-string-parameter.ts +120 -0
  63. package/amplify/plugins/newsletter/sending.js +140 -0
  64. package/amplify/plugins/newsletter/sending.ts +186 -0
  65. package/amplify/plugins/reader-steering/config.js +182 -0
  66. package/amplify/plugins/reader-steering/config.ts +290 -0
  67. package/amplify/plugins/reader-steering/function/ai_moderation.py +246 -0
  68. package/amplify/plugins/reader-steering/function/handler.py +1589 -0
  69. package/amplify/plugins/reader-steering/function/moderation-prompt.md +27 -0
  70. package/amplify/plugins/reader-steering/function/moderation.py +316 -0
  71. package/amplify/plugins/reader-steering/function/proposals.py +487 -0
  72. package/amplify/plugins/reader-steering/function/resource.js +27 -0
  73. package/amplify/plugins/reader-steering/function/resource.ts +35 -0
  74. package/amplify/plugins/reader-steering/papyrus.plugin.js +187 -0
  75. package/amplify/plugins/reader-steering/papyrus.plugin.ts +213 -0
  76. package/amplify/plugins/reader-steering/schema.js +252 -0
  77. package/amplify/plugins/reader-steering/schema.ts +254 -0
  78. package/amplify/plugins/registry.js +4 -0
  79. package/amplify/plugins/registry.ts +4 -0
  80. package/amplify/plugins/signup/schema.js +2 -1
  81. package/amplify/plugins/signup/schema.ts +2 -1
  82. package/amplify/plugins/types.ts +5 -0
  83. package/amplify/plugins/wire.js +1 -0
  84. package/amplify/plugins/wire.ts +1 -0
  85. package/amplify/publication/projection-lease.js +30 -0
  86. package/amplify/publication/projection-lease.ts +39 -0
  87. package/amplify/publication/redirect-and-index.js +105 -0
  88. package/amplify/publication/redirect-rules.js +73 -0
  89. package/amplify/publication/redirect-rules.ts +93 -0
  90. package/amplify/publication/s3-projection.js +7 -1
  91. package/amplify/publication/s3-projection.ts +9 -1
  92. package/amplify/site-backend-config.js +19 -0
  93. package/amplify/site-backend-config.ts +45 -1
  94. package/amplify/site-backend-names.js +4 -2
  95. package/amplify/site-backend-names.ts +4 -2
  96. package/amplify/site-backend.js +42 -4
  97. package/amplify/site-backend.ts +45 -4
  98. package/app/.well-known/papyrus-cli.json/route.ts +9 -0
  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 +32 -17
  126. package/components/moderation-panel.tsx +248 -0
  127. package/components/monitoring-overview-view.tsx +422 -0
  128. package/components/monitoring-panel.tsx +60 -0
  129. package/components/news-desk-client-provider.tsx +18 -18
  130. package/components/news-desk-taxonomy-client.ts +28 -8
  131. package/components/newsletter-panel.tsx +474 -0
  132. package/components/newsletter-sections.tsx +163 -0
  133. package/components/newsroom-access-gate.tsx +16 -2
  134. package/components/newsroom-article-editor.tsx +9 -7
  135. package/components/newsroom-articles-view.tsx +6 -6
  136. package/components/newsroom-assignment-detail.tsx +6 -6
  137. package/components/newsroom-board-view.tsx +8 -3
  138. package/components/newsroom-curation-bootstrap.tsx +45 -0
  139. package/components/newsroom-ops-overview.tsx +26 -96
  140. package/components/newsroom-ops-shell.tsx +2 -2
  141. package/components/newsroom-organizer.css +33 -0
  142. package/components/newsroom-overview-skeleton.tsx +25 -0
  143. package/components/newsroom-references-view.tsx +192 -49
  144. package/components/notification-center.tsx +281 -0
  145. package/components/papyrus-console-conversation.css +14 -0
  146. package/components/papyrus-console-conversation.tsx +55 -0
  147. package/components/papyrus-console-shell.tsx +62 -420
  148. package/components/papyrus-private-chat-panel.tsx +134 -0
  149. package/components/publication-cyclotron-panel.tsx +15 -10
  150. package/components/reader-auth-state.ts +1 -1
  151. package/components/reference-add-entry.tsx +75 -0
  152. package/components/reference-curation-controls.tsx +31 -22
  153. package/components/reference-cyclotron-priming.tsx +58 -19
  154. package/components/reference-cyclotron-suggestion.tsx +10 -6
  155. package/components/reference-priming.tsx +14 -10
  156. package/components/reference-relevance-review.tsx +3 -3
  157. package/components/reference-section-suggestion.tsx +1 -1
  158. package/components/topic-steering-workspace.tsx +230 -144
  159. package/infra/amplify-app-shell.js +96 -16
  160. package/infra/amplify-app-shell.ts +108 -18
  161. package/infra/cloud-email-config.js +32 -0
  162. package/infra/cloud-email-config.ts +39 -0
  163. package/infra/cloud-email.js +57 -0
  164. package/infra/cloud-email.test.js +69 -0
  165. package/infra/cloud-email.test.ts +66 -0
  166. package/infra/cloud-email.ts +60 -0
  167. package/infra/index.js +1 -0
  168. package/infra/secure-string-parameter-handler.js +41 -0
  169. package/infra/secure-string-parameter-handler.ts +41 -0
  170. package/infra/secure-string-parameter.js +79 -0
  171. package/infra/secure-string-parameter.ts +91 -0
  172. package/infra/site-config.js +92 -6
  173. package/infra/site-config.ts +138 -6
  174. package/lib/automation-client.ts +54 -0
  175. package/lib/automation-fixture.ts +93 -0
  176. package/lib/automation-overview.ts +252 -0
  177. package/lib/category-repository.ts +14 -12
  178. package/lib/cli-login-discovery.ts +78 -0
  179. package/lib/console-web-context.ts +1 -1
  180. package/lib/curation-routes.ts +345 -0
  181. package/lib/cyclotron-suggestions.ts +4 -4
  182. package/lib/editorial-checks-client.ts +18 -0
  183. package/lib/editorial-checks-demo.ts +1 -1
  184. package/lib/editorial-checks.ts +8 -4
  185. package/lib/editorial-preview.ts +5 -5
  186. package/lib/monitoring-client.ts +83 -0
  187. package/lib/monitoring-overview-fixture.ts +51 -0
  188. package/lib/monitoring-overview.ts +191 -0
  189. package/lib/news-desk-session.ts +12 -21
  190. package/lib/newsletter-api.ts +418 -0
  191. package/lib/newsletter-client.ts +46 -0
  192. package/lib/newsletter-fixture.ts +184 -0
  193. package/lib/newsletter-model.ts +202 -0
  194. package/lib/newsroom-demo-dashboard.ts +1 -1
  195. package/lib/newsroom-nav.ts +1 -1
  196. package/lib/newsroom-organizer-nav.ts +2 -1
  197. package/lib/papyrus-chat-admission.ts +65 -0
  198. package/lib/papyrus-hosted-chat.ts +217 -0
  199. package/lib/papyrus-private-chat-client.ts +81 -0
  200. package/lib/prospectus-session.ts +30 -14
  201. package/lib/prospectus-sign-in-routes.ts +106 -31
  202. package/lib/prospectus-site-identity.ts +46 -6
  203. package/lib/reader-api.ts +145 -0
  204. package/lib/reader-moderation.ts +220 -0
  205. package/lib/reader-retirement-route.ts +89 -0
  206. package/lib/reader-subject.md +29 -0
  207. package/lib/reader-subject.ts +53 -0
  208. package/lib/reference-review-details.ts +71 -0
  209. package/lib/reference-review-request.ts +14 -0
  210. package/lib/relevance-decisions.ts +42 -10
  211. package/lib/relevance-status.ts +2 -2
  212. package/lib/review-client.ts +120 -0
  213. package/lib/review-fixture.ts +45 -0
  214. package/lib/review-requests.ts +246 -0
  215. package/lib/semantic-graph.ts +2 -0
  216. package/lib/shared-chat-admission.ts +182 -0
  217. package/lib/site-brand-tenant.ts +11 -6
  218. package/lib/site-brand.ts +6 -3
  219. package/lib/site-origin-value.ts +50 -0
  220. package/lib/site-origin.ts +8 -0
  221. package/lib/use-reference-review-data.ts +44 -0
  222. package/middleware.ts +14 -3
  223. package/package.json +67 -3
  224. package/routes.manifest.json +238 -2
  225. package/src/sync.mjs +11 -2
  226. package/src/with-papyrus.d.mts +1 -0
  227. package/src/with-papyrus.mjs +17 -1
@@ -14,6 +14,10 @@ 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
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";
17
21
  import { SignupEntriesPanel } from "./signup-entries-panel";
18
22
  import { pluginSettings, type PluginOutputs } from "../lib/plugin-settings";
19
23
  import { generateClient } from "aws-amplify/data";
@@ -36,6 +40,7 @@ import {
36
40
  loadEditorReferencesData,
37
41
  loadEditorProcedureData,
38
42
  loadEditorSemanticRelationsData,
43
+ loadEditorReferenceReviewDetails,
39
44
  loadEditionForumThreads,
40
45
  loadNewsroomAssignmentPage,
41
46
  loadNewsroomMessagePage,
@@ -89,6 +94,7 @@ import { ReferenceSourcePreview } from "./reference-source-preview";
89
94
  import type { ReaderAuthSnapshot } from "./reader-auth-state";
90
95
  import { NewsDeskAccessGate } from "./newsroom-access-gate";
91
96
  import { NewsroomOpsOverview } from "./newsroom-ops-overview";
97
+ import { NewsroomCurationBootstrap } from "./newsroom-curation-bootstrap";
92
98
  import { NewsroomReferencesView } from "./newsroom-references-view";
93
99
  import { NewsroomAssignmentsView } from "./newsroom-assignments-view";
94
100
  import { NewsroomTopicsView } from "./newsroom-topics-view";
@@ -162,6 +168,9 @@ import {
162
168
  } from "../lib/reference-policy";
163
169
  import { isLocalPreviewEnabled } from "../lib/local-preview";
164
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";
165
174
  import { delegatedCurationsByReference, referencesWithHumanLatestCuration, type ShadowAgreement } from "../lib/cyclotron-suggestions";
166
175
  import { createLocalPreviewCyclotronFixtures } from "../lib/local-preview-seed";
167
176
  import { createLocalPreviewSectionFixtures } from "../lib/local-preview-section-fixtures";
@@ -247,7 +256,7 @@ type AssignmentAction = "claim" | "release" | "complete" | "cancel" | "reopen" |
247
256
  type ReportingPacketReviewDecision = "select" | "merge" | "brief" | "hold" | "kill";
248
257
  type AssignmentDeskViewMode = "queue" | "budget";
249
258
  type UserRoleAction = "grant" | "revoke";
250
- type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "delegations" | "signups" | "editorial-checks";
259
+ type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "automations" | "delegations" | "signups" | "newsletter" | "moderation" | "editorial-checks" | "monitoring";
251
260
  export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "messages" | "assignments" | "administration" | "search";
252
261
  type LexicalRuleScope = "publication" | "corpus" | "classifier" | "category";
253
262
  type AnalysisReindexMode = "online-update" | "classifier-retrain" | "scoped-topic-rebuild" | "entity-graph-rebuild" | "generated-analysis-rebuild";
@@ -658,14 +667,14 @@ export function NewsDeskWorkspace({
658
667
  editorShellReady
659
668
  initialSelection={initialSelection}
660
669
  initialTab={initialTab}
661
- authState={{ status: "signedIn", label: "Demo Desk" }}
670
+ authState={{ status: "signedIn", label: "Demo section" }}
662
671
  isRefreshing={false}
663
672
  shellError={null}
664
673
  />
665
674
  );
666
675
  }
667
676
 
668
- if (session && sessionDashboard) {
677
+ if (session && sessionDashboard && session.shell.phase !== "loadingDesk") {
669
678
  return (
670
679
  <NewsDeskDashboard
671
680
  analysisProfiles={analysisProfiles}
@@ -879,7 +888,7 @@ function NewsDeskDashboard({
879
888
  const [mergeSelection, setMergeSelection] = useState<MergeSelection | null>(null);
880
889
  const [isPending, startTransition] = useTransition();
881
890
  const controlsDisabled = isPending || !canEdit;
882
- const initialAdministrationPanel = normalizeAdministrationPanel(initialSelection.panel);
891
+ const initialAdministrationPanel = normalizeAdministrationPanel(initialSelection.panel, Boolean(dashboard.isDemo));
883
892
  const [administrationPanel, setAdministrationPanel] = useState<AdministrationPanel>(initialAdministrationPanel);
884
893
  const [doctrineDrafts, setDoctrineDrafts] = useState<DoctrineEditorState>(() => buildDoctrineEditorState(dashboard.doctrineRecords));
885
894
  const [loadedSections, setLoadedSections] = useState<Record<string, boolean>>({
@@ -897,6 +906,11 @@ function NewsDeskDashboard({
897
906
  const [referencesRealtimeStatus, setReferencesRealtimeStatus] = useState<RealtimeSubscriptionStatus>("idle");
898
907
  const [referencesRealtimeError, setReferencesRealtimeError] = useState<string | null>(null);
899
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]);
900
914
 
901
915
  useEffect(() => {
902
916
  referencesRef.current = references;
@@ -1120,8 +1134,8 @@ function NewsDeskDashboard({
1120
1134
  }, [dashboard.procedureDefinitions, dashboard.procedureRuns, dashboard.procedureVersions]);
1121
1135
 
1122
1136
  useEffect(() => {
1123
- setAdministrationPanel(normalizeAdministrationPanel(initialSelection.panel));
1124
- }, [initialSelection.panel]);
1137
+ setAdministrationPanel(normalizeAdministrationPanel(initialSelection.panel, Boolean(dashboard.isDemo)));
1138
+ }, [initialSelection.panel, dashboard.isDemo]);
1125
1139
 
1126
1140
  useEffect(() => {
1127
1141
  if (activeTab !== "administration") return;
@@ -1226,17 +1240,6 @@ function NewsDeskDashboard({
1226
1240
  if (dashboard.isDemo) return;
1227
1241
  let active = true;
1228
1242
 
1229
- if (activeTab === "messages" && !loadedSections.messages) {
1230
- setLoadedSections((current) => ({ ...current, messages: true }));
1231
- void loadEditorMessagesData()
1232
- .then((nextMessages) => {
1233
- if (active) setMessages(nextMessages);
1234
- })
1235
- .catch((error) => {
1236
- if (active) setActionState({ id: "messages-load", message: error instanceof Error ? error.message : "messages load failed", tone: "error" });
1237
- });
1238
- }
1239
-
1240
1243
  if (activeTab === "assignments" && !loadedSections.assignments) {
1241
1244
  setLoadedSections((current) => ({ ...current, assignments: true }));
1242
1245
  void loadEditorAssignmentsData()
@@ -1264,23 +1267,6 @@ function NewsDeskDashboard({
1264
1267
  });
1265
1268
  }
1266
1269
 
1267
- if ((activeTab === "assignments" || activeTab === "references" || activeTab === "messages") && !loadedSections.semanticRelations) {
1268
- setLoadedSections((current) => ({ ...current, semanticRelations: true }));
1269
- void loadEditorSemanticRelationsData()
1270
- .then((relations) => {
1271
- if (active) setSemanticRelations(relations);
1272
- })
1273
- .catch(() => {
1274
- if (active) setSemanticRelations([]);
1275
- });
1276
- if (!loadedSections.messages) {
1277
- setLoadedSections((current) => ({ ...current, messages: true }));
1278
- void loadEditorMessagesData().then((nextMessages) => {
1279
- if (active) setMessages(nextMessages);
1280
- }).catch(() => undefined);
1281
- }
1282
- }
1283
-
1284
1270
  if ((activeTab === "topics" || activeTab === "desks" || activeTab === "concepts" || activeTab === "administration") && !loadedSections.fullDashboard) {
1285
1271
  setLoadedSections((current) => ({ ...current, fullDashboard: true }));
1286
1272
  void loadEditorFullNewsDeskDashboard({ isAdmin: Boolean(dashboard.canManageUsers) })
@@ -1335,6 +1321,37 @@ function NewsDeskDashboard({
1335
1321
  refreshNewsroomSections,
1336
1322
  ]);
1337
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
+
1338
1355
  const applyReferenceRecord = useCallback((nextReference: ReferenceRecord) => {
1339
1356
  const { nextRecords, nextRecord, previousRecord } = upsertReferenceRecords(referencesRef.current, nextReference);
1340
1357
  referencesRef.current = nextRecords;
@@ -1589,96 +1606,98 @@ function NewsDeskDashboard({
1589
1606
  reasonCode?: ReferenceRejectionReasonCode | null,
1590
1607
  relevance?: { decisionRelationId: string; shareable: boolean },
1591
1608
  sectionId?: string | null,
1592
- ) {
1593
- const nextStatus = referenceCurationStatusForAction(action);
1594
- setActionState({ id: reference.id, message: `${action} pending`, tone: "pending" });
1595
- if (dashboard.isDemo) {
1596
- const now = new Date().toISOString();
1597
- const messageId = `message-demo-${reference.id}-${action}-${now.replace(/[^0-9]/g, "")}`;
1598
- const relationId = `semantic-relation-demo-${messageId}`;
1599
- applyReferenceRecord(buildReviewedReferenceRecord(referencesRef.current, reference, {
1600
- actorLabel: authState.label,
1601
- nextStatus,
1602
- note: note ?? null,
1603
- now,
1604
- }));
1605
- setMessages((current) => [{
1606
- id: messageId,
1607
- messageKind: "reference_curation",
1608
- messageDomain: "commentary",
1609
- status: "active",
1610
- body: note?.trim() || `${authState.label} marked this reference ${nextStatus}.`,
1611
- summary: `${reference.title ?? reference.externalItemId}: ${nextStatus}`,
1612
- source: "newsroom",
1613
- authorLabel: authState.label,
1614
- createdAt: now,
1615
- updatedAt: now,
1616
- metadata: {
1617
- action,
1618
- curationStatus: nextStatus,
1619
- reasonCode: action === "reject" ? reasonCode ?? null : null,
1620
- ...(relevance ? { decisionRelationId: relevance.decisionRelationId, shareable: relevance.shareable } : {}),
1621
- ...(action === "accept" && sectionId ? { sectionId } : {}),
1622
- },
1623
- }, ...current]);
1624
- setSemanticRelations((current) => [{
1625
- id: relationId,
1626
- relationState: "current",
1627
- predicate: "comment",
1628
- relationTypeId: "semantic-relation-type-comment",
1629
- relationTypeKey: "comment",
1630
- relationDomain: "commentary",
1631
- subjectKind: "message",
1632
- subjectId: messageId,
1633
- subjectLineageId: messageId,
1634
- subjectVersionNumber: 1,
1635
- objectKind: "reference",
1636
- objectId: reference.id,
1637
- objectLineageId: reference.lineageId ?? reference.id,
1638
- objectVersionNumber: reference.versionNumber ?? null,
1639
- subjectStateKey: `message#${messageId}#current`,
1640
- objectStateKey: `reference#${reference.lineageId ?? reference.id}#current`,
1641
- objectSubjectStateKey: `reference#${reference.lineageId ?? reference.id}#current#message`,
1642
- predicateObjectStateKey: `comment#reference#${reference.lineageId ?? reference.id}#current`,
1643
- subjectVersionKey: `message#${messageId}`,
1644
- objectVersionKey: `reference#${reference.id}`,
1645
- rank: 1,
1646
- reviewRecommended: false,
1647
- importedAt: now,
1648
- }, ...current]);
1649
- setActionState({ id: reference.id, message: `${action} saved`, tone: "ok" });
1650
- return;
1651
- }
1652
- startTransition(() => {
1653
- void (async () => {
1654
- try {
1655
- const response = await dataClient.mutations.reviewReferenceCuration(
1656
- {
1657
- referenceId: reference.id,
1658
- action,
1659
- actorLabel: authState.label,
1660
- note: note?.trim() || undefined,
1661
- reasonCode: action === "reject" ? reasonCode ?? undefined : undefined,
1662
- decisionRelationId: relevance?.decisionRelationId,
1663
- shareable: relevance ? relevance.shareable : undefined,
1664
- sectionId: action === "accept" ? sectionId ?? undefined : undefined,
1665
- },
1666
- { authMode: USER_POOL_AUTH_MODE },
1667
- );
1668
- const review = assertReferenceReviewMutationSucceeded(response, reference.id);
1669
- const status = review.status ?? nextStatus;
1670
- const now = new Date().toISOString();
1671
- 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,
1672
1677
  actorLabel: authState.label,
1673
- nextStatus: status,
1674
- note: note?.trim() || null,
1675
- now,
1676
- }));
1677
- setActionState({ id: reference.id, message: `${action} saved`, tone: "ok" });
1678
- } catch (error) {
1679
- setActionState({ id: reference.id, message: error instanceof Error ? error.message : `${action} failed`, tone: "error" });
1680
- }
1681
- })();
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
+ }
1682
1701
  });
1683
1702
  }
1684
1703
 
@@ -2999,7 +3018,7 @@ function NewsDeskDashboard({
2999
3018
  return entries;
3000
3019
  }, [summaryStatus, tabCounts]);
3001
3020
  const opsPageTitle = activeTab === "overview"
3002
- ? "Overview"
3021
+ ? "Notification Center"
3003
3022
  : activeTab === "administration" && administrationPanel === "sections"
3004
3023
  ? "Sections"
3005
3024
  : formatDeskSectionHeadline(activeTab);
@@ -3057,9 +3076,11 @@ function NewsDeskDashboard({
3057
3076
 
3058
3077
  {activeTab === "overview" ? (
3059
3078
  <NewsroomOpsOverview
3079
+ actorLabel={authState.label}
3060
3080
  assignments={assignments}
3081
+ decisions={relevanceDecisions}
3061
3082
  demo={Boolean(dashboard.isDemo)}
3062
- messages={messages}
3083
+ humanCuratedReferenceLineageIds={humanCuratedReferenceLineageIds}
3063
3084
  newsroomSections={newsroomSections}
3064
3085
  references={references}
3065
3086
  />
@@ -3115,10 +3136,11 @@ function NewsDeskDashboard({
3115
3136
  />
3116
3137
  ) : null}
3117
3138
  {activeTab === "references" ? (
3139
+ <NewsroomCurationBootstrap enabled={!dashboard.isDemo}>
3118
3140
  <NewsroomReferencesView
3119
3141
  demo={Boolean(dashboard.isDemo)}
3120
3142
  disabled={controlsDisabled}
3121
- initialReferenceLineageId={initialSelection.reference ?? pathnameReferenceLineageId}
3143
+ initialReferenceLineageId={selectedReviewLineageId}
3122
3144
  cyclotronStatus={cyclotronStatus}
3123
3145
  cyclotronPriming={{
3124
3146
  labels: primingLabels,
@@ -3130,8 +3152,8 @@ function NewsDeskDashboard({
3130
3152
  },
3131
3153
  }}
3132
3154
  decisions={relevanceDecisions}
3133
- humanCuratedReferenceLineageIds={humanCuratedReferenceLineageIds}
3134
- delegatedCurations={delegatedCurations}
3155
+ humanCuratedReferenceLineageIds={selectedReviewIsCurrent && predictionState !== "ready" ? undefined : humanCuratedReferenceLineageIds}
3156
+ delegatedCurations={selectedReviewIsCurrent && predictionState !== "ready" ? undefined : delegatedCurations}
3135
3157
  shadowAgreement={shadowAgreement}
3136
3158
  onRequestReviewRate={relevanceCyclotronId ? async (choice) => {
3137
3159
  const request = buildReviewRateRequest(choice, authState.label);
@@ -3144,14 +3166,23 @@ function NewsDeskDashboard({
3144
3166
  curation.note ?? undefined,
3145
3167
  normalizeReferenceRejectionReasonCode(curation.reasonCode),
3146
3168
  { decisionRelationId: curation.decisionRelationId, shareable: curation.shareable },
3169
+ curation.sectionId,
3147
3170
  )}
3148
3171
  sections={sectionChoices}
3149
3172
  sectionSuggestions={sectionSuggestions}
3150
3173
  sectionAgreement={siteSectionAgreement}
3151
- 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)}
3152
3182
  referenceAttachments={referenceAttachments}
3153
3183
  references={references}
3154
3184
  />
3185
+ </NewsroomCurationBootstrap>
3155
3186
  ) : null}
3156
3187
  {activeTab === "messages" ? (
3157
3188
  <MessagesDeskView
@@ -3872,7 +3903,7 @@ function NewsroomSectionRail({
3872
3903
  <aside className="newsroom-section-rail" data-newsroom-section-rail aria-labelledby="newsroom-section-rail-title" ref={railRef}>
3873
3904
  <header className="newsroom-section-rail__header">
3874
3905
  <p className="story-label">Sections</p>
3875
- <h2 id="newsroom-section-rail-title">Section Desk</h2>
3906
+ <h2 id="newsroom-section-rail-title">Section</h2>
3876
3907
  </header>
3877
3908
  <div className="newsroom-section-rail__body">
3878
3909
  {canonicalSections.length ? (
@@ -4309,6 +4340,15 @@ function AdministrationDeskView({
4309
4340
  <strong>Procedures</strong>
4310
4341
  <span>Procedure registry and versioned Tactus code</span>
4311
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>
4312
4352
  <Link
4313
4353
  href={administrationPanelHref("delegations", isDemo)}
4314
4354
  data-news-desk-admin-nav="delegations"
@@ -4327,6 +4367,15 @@ function AdministrationDeskView({
4327
4367
  <strong>Editorial checks</strong>
4328
4368
  <span>Policies the article editor checks drafts against</span>
4329
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>
4330
4379
  {signupPluginEnabled() ? (
4331
4380
  <Link
4332
4381
  href={administrationPanelHref("signups", isDemo)}
@@ -4338,10 +4387,36 @@ function AdministrationDeskView({
4338
4387
  <span>Addresses from the site&apos;s signup forms</span>
4339
4388
  </Link>
4340
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}
4341
4412
  </nav>}
4342
4413
  <div className="news-desk-settings-panel">
4343
4414
  {administrationPanel === "signups" ? <SignupEntriesPanel /> : null}
4415
+ {administrationPanel === "newsletter" ? <NewsletterPanel isAdmin={canManageUsers} isDemo={isDemo} /> : null}
4416
+ {administrationPanel === "moderation" ? <ModerationPanel /> : null}
4344
4417
  {administrationPanel === "editorial-checks" ? <EditorialChecksAdminPanel /> : null}
4418
+ {administrationPanel === "automations" ? <AutomationPanel isDemo={isDemo} /> : null}
4419
+ {administrationPanel === "monitoring" ? <MonitoringPanel isAdmin={canManageUsers} isDemo={isDemo} /> : null}
4345
4420
  {administrationPanel === "delegations" ? <AdministrationDelegationsPanel disabled={disabled} /> : null}
4346
4421
  {administrationPanel === "users" ? (
4347
4422
  <AdministrationUsersPanel
@@ -5668,7 +5743,7 @@ function DesksDeskView({
5668
5743
  <div className="news-desk-columns news-desk-columns--administration news-desk-columns--taxonomy" data-news-desk-section="desks">
5669
5744
  <div className="news-desk-main-column">
5670
5745
  <section className="category-steering-section category-steering-section--lead" aria-labelledby="newsroom-desks-title">
5671
- <SectionHeader title="News Desks" detail={`${roots.length} root topic desks`} />
5746
+ <SectionHeader title="News sections" detail={`${roots.length} root topic sections`} />
5672
5747
  <div className="news-desk-desk-list">
5673
5748
  {roots.length ? roots.map((root) => {
5674
5749
  const rootNode = root.node ?? categoryToCategoryTreeNode(root.category);
@@ -5688,11 +5763,11 @@ function DesksDeskView({
5688
5763
  >
5689
5764
  <span>{rootNode.shortTitle ?? deriveShortTitle(rootNode.displayName)}</span>
5690
5765
  <strong>{rootNode.displayName}</strong>
5691
- <p>{rootNode.description ?? "No desk description yet."}</p>
5766
+ <p>{rootNode.description ?? "No section description yet."}</p>
5692
5767
  <small>{referenceCount} refs / {assignmentCount} assignments / {root.subcategorys.length} subtopics / {doctrineStatus.savedCount} of 2 doctrine slots</small>
5693
5768
  </Link>
5694
5769
  );
5695
- }) : <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" />}
5696
5771
  </div>
5697
5772
  </section>
5698
5773
 
@@ -5713,18 +5788,18 @@ function DesksDeskView({
5713
5788
 
5714
5789
  <aside className="news-desk-rail-column">
5715
5790
  <section className="category-steering-section" aria-labelledby="desk-index-notes-title">
5716
- <SectionHeader title="Desk Rules" detail="Root topics only" />
5791
+ <SectionHeader title="Section rules" detail="Root topics only" />
5717
5792
  <div className="news-desk-ledger-list">
5718
5793
  <article className="news-desk-ledger-item">
5719
5794
  <header>
5720
- <strong>Desk Identity</strong>
5795
+ <strong>Section identity</strong>
5721
5796
  <span>Category</span>
5722
5797
  </header>
5723
5798
  <p>Official name, short title, subtitle, and description stay on the accepted root Category record.</p>
5724
5799
  </article>
5725
5800
  <article className="news-desk-ledger-item">
5726
5801
  <header>
5727
- <strong>Desk Doctrine</strong>
5802
+ <strong>Section doctrine</strong>
5728
5803
  <span>Private Item</span>
5729
5804
  </header>
5730
5805
  <p>Mission and policies are private doctrine Items tied to the root category lineage.</p>
@@ -5774,7 +5849,7 @@ function DeskDetailPanel({
5774
5849
  <section className="category-steering-section news-desk-desk-detail" aria-labelledby="selected-desk-title">
5775
5850
  <header className="news-desk-topic-detail__header">
5776
5851
  <div>
5777
- <p className="story-label">News Desk</p>
5852
+ <p className="story-label">News section</p>
5778
5853
  <h3 id="selected-desk-title">{rootNode.displayName}</h3>
5779
5854
  <span>{rootNode.shortTitle ?? deriveShortTitle(rootNode.displayName)}</span>
5780
5855
  </div>
@@ -5857,7 +5932,7 @@ function CategoryDoctrineEditorCard({
5857
5932
  <article className="news-desk-doctrine-card" data-news-desk-doctrine={definition.slug}>
5858
5933
  <header className="news-desk-doctrine-card__header">
5859
5934
  <div>
5860
- <p className="story-label">Desk Doctrine</p>
5935
+ <p className="story-label">Section doctrine</p>
5861
5936
  <h3>{definition.label}</h3>
5862
5937
  </div>
5863
5938
  <span>{record ? "Saved record" : "Empty slot"}</span>
@@ -12222,7 +12297,7 @@ function AssignmentRow({
12222
12297
  {context ? (
12223
12298
  <>
12224
12299
  <p className="news-desk-assignment-row__angle">
12225
- <span>{context.deskTitle ?? context.deskKey ?? "Desk"}</span>
12300
+ <span>{context.deskTitle ?? context.deskKey ?? "Section"}</span>
12226
12301
  {`${context.focusTitle ?? context.focusKey ?? "focus"} / ${context.targetSystemType ?? context.contextProfile ?? "context"}`}
12227
12302
  </p>
12228
12303
  {context.contextProfile || context.contextTokenBudget ? (
@@ -12337,7 +12412,7 @@ function normalizeAnalysisReindexMode(value: string | null | undefined): Analysi
12337
12412
  function formatAnalysisReindexModeLabel(mode: AnalysisReindexMode): string {
12338
12413
  if (mode === "online-update") return "Online update - new or changed accepted references";
12339
12414
  if (mode === "classifier-retrain") return "Classifier retrain - rebuild topic classifier";
12340
- 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";
12341
12416
  if (mode === "entity-graph-rebuild") return "Entity graph rebuild - generated graph outputs";
12342
12417
  return "Generated analysis rebuild - clear and rebuild targeted generated outputs";
12343
12418
  }
@@ -12367,7 +12442,7 @@ function mergeAnalysisCorpora(configuredCorpora: CategorySteeringCorpus[], liveC
12367
12442
  function analysisReindexModeHelp(mode: AnalysisReindexMode): string {
12368
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.";
12369
12444
  if (mode === "classifier-retrain") return "Use this when authoritative labels or accepted references should retrain the semi-supervised topic classifier.";
12370
- 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.";
12371
12446
  if (mode === "entity-graph-rebuild") return "Use this when generated ontology/entity graph records should be rebuilt from accepted references.";
12372
12447
  return "Use this for incremental processing of new or changed accepted references without clearing broader generated outputs.";
12373
12448
  }
@@ -12805,9 +12880,20 @@ function signupPluginEnabled(): boolean {
12805
12880
  return pluginSettings(amplifyOutputs as PluginOutputs, "signup") !== null;
12806
12881
  }
12807
12882
 
12808
- function normalizeAdministrationPanel(value: string | null | undefined): AdministrationPanel {
12809
- if (value === "policies" || value === "sections" || value === "procedures" || value === "delegations" || value === "editorial-checks") 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;
12810
12894
  if (value === "signups" && signupPluginEnabled()) return value;
12895
+ if (value === "newsletter" && newsletterPluginEnabled(demo)) return value;
12896
+ if (value === "moderation" && readerPluginEnabled()) return value;
12811
12897
  return "users";
12812
12898
  }
12813
12899
 
@@ -13006,7 +13092,7 @@ function formatDeskSectionHeadline(section: NewsDeskTab): string {
13006
13092
 
13007
13093
  function formatDeskSectionLede(section: NewsDeskTab): string {
13008
13094
  if (section === "search") return "Search the newsroom knowledge base with semantic and ontology context.";
13009
- 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.";
13010
13096
  if (section === "topics") return "Review suggested changes to the subject areas the newsroom covers.";
13011
13097
  if (section === "concepts") return "Browse people, organizations, places, and ideas found in the source material.";
13012
13098
  if (section === "references") return "Review source materials before they become usable evidence.";