@anthusai/papyrus 1.19.1 → 2.0.0-next.10

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 (80) hide show
  1. package/amplify/data/resource.js +7 -0
  2. package/amplify/data/resource.ts +8 -0
  3. package/amplify/data/schema.js +8 -0
  4. package/amplify/data/schema.ts +9 -0
  5. package/amplify/functions/assignment-action/data-access.js +88 -0
  6. package/amplify/functions/assignment-action/data-access.ts +87 -0
  7. package/amplify/functions/content-actions/handler.py +7 -0
  8. package/amplify/functions/model-attachment-upload/handler.ts +1 -0
  9. package/amplify/plugins/editorial-checks/function/handler.py +14 -0
  10. package/amplify/plugins/editorial-checks/function/requirements.txt +1 -0
  11. package/amplify/plugins/editorial-checks/function/resource.js +43 -0
  12. package/amplify/plugins/editorial-checks/function/resource.ts +60 -0
  13. package/amplify/plugins/editorial-checks/papyrus.plugin.js +71 -0
  14. package/amplify/plugins/editorial-checks/papyrus.plugin.ts +87 -0
  15. package/amplify/plugins/editorial-checks/schema.js +94 -0
  16. package/amplify/plugins/editorial-checks/schema.ts +96 -0
  17. package/amplify/plugins/registry.js +2 -0
  18. package/amplify/plugins/registry.ts +2 -0
  19. package/amplify/site-backend.js +5 -0
  20. package/amplify/site-backend.ts +11 -0
  21. package/app/globals.css +0 -175
  22. package/app/newsroom/[section]/[...selection]/page.tsx +0 -6
  23. package/app/newsroom/[section]/page.tsx +2 -4
  24. package/app/newsroom/articles/page.tsx +1 -2
  25. package/app/newsroom/sections/[sectionId]/page.tsx +2 -6
  26. package/bin/papyrus-infra.mjs +11 -2
  27. package/components/editorial-checks-admin-panel.tsx +337 -0
  28. package/components/editorial-finding-card.tsx +162 -0
  29. package/components/editorial-findings-panel.tsx +91 -0
  30. package/components/editorial-preview-popover.tsx +103 -0
  31. package/components/markus-body-editor.tsx +213 -0
  32. package/components/markus-ir-preview.tsx +147 -6
  33. package/components/news-desk-page.tsx +3 -4
  34. package/components/newsroom-article-editor.tsx +138 -11
  35. package/components/newsroom-board-view.tsx +6 -7
  36. package/components/newsroom-ops-shell.tsx +146 -234
  37. package/components/newsroom-organizer.css +57 -61
  38. package/components/newsroom-theme-fonts/broadsheet-green.css +10 -0
  39. package/components/newsroom-theme-fonts/broadsheet-green.tsx +6 -0
  40. package/components/newsroom-theme-fonts/newspaper-gothic.css +9 -0
  41. package/components/newsroom-theme-fonts/newspaper-gothic.tsx +6 -0
  42. package/components/newsroom-theme-fonts/night-desk.css +10 -0
  43. package/components/newsroom-theme-fonts/night-desk.tsx +6 -0
  44. package/components/newsroom-theme-fonts/refined-newsprint.css +11 -0
  45. package/components/newsroom-theme-fonts/refined-newsprint.tsx +6 -0
  46. package/components/newsroom-theme.css +39 -0
  47. package/components/newsroom-theme.tsx +33 -0
  48. package/components/reference-cyclotron-priming.tsx +12 -13
  49. package/components/reference-cyclotron-status.tsx +4 -4
  50. package/components/reference-cyclotron-suggestion.tsx +6 -6
  51. package/components/reference-live-agreement.tsx +10 -10
  52. package/components/reference-priming.tsx +1 -1
  53. package/components/reference-relevance-review.tsx +1 -1
  54. package/components/reference-section-suggestion.tsx +1 -1
  55. package/components/topic-steering-workspace.tsx +36 -511
  56. package/components/use-editorial-checks.ts +131 -0
  57. package/lib/cyclotron-suggestions.ts +9 -9
  58. package/lib/editorial-checks-backend.ts +16 -0
  59. package/lib/editorial-checks-client.ts +194 -0
  60. package/lib/editorial-checks-demo.ts +147 -0
  61. package/lib/editorial-checks-settings.ts +24 -0
  62. package/lib/editorial-checks.ts +178 -0
  63. package/lib/editorial-preview.ts +228 -0
  64. package/lib/newsroom-board.ts +27 -2
  65. package/lib/newsroom-list-selection.ts +0 -3
  66. package/lib/newsroom-nav.ts +5 -8
  67. package/lib/newsroom-section-routing.ts +2 -4
  68. package/lib/relevance-status.ts +4 -4
  69. package/lib/site-brand.ts +18 -8
  70. package/package.json +34 -3
  71. package/routes.manifest.json +1 -1
  72. package/src/cdk-environment.mjs +44 -0
  73. package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
  74. package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
  75. package/components/newsroom-fonts/playfair-display-OFL.txt +0 -93
  76. package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
  77. package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
  78. package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +0 -93
  79. package/components/newsroom-newsprint-board.css +0 -7
  80. package/components/newsroom-organizer-shell.tsx +0 -153
@@ -13,6 +13,7 @@ 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";
16
17
  import { SignupEntriesPanel } from "./signup-entries-panel";
17
18
  import { pluginSettings, type PluginOutputs } from "../lib/plugin-settings";
18
19
  import { generateClient } from "aws-amplify/data";
@@ -246,7 +247,7 @@ type AssignmentAction = "claim" | "release" | "complete" | "cancel" | "reopen" |
246
247
  type ReportingPacketReviewDecision = "select" | "merge" | "brief" | "hold" | "kill";
247
248
  type AssignmentDeskViewMode = "queue" | "budget";
248
249
  type UserRoleAction = "grant" | "revoke";
249
- type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "delegations" | "signups";
250
+ type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "delegations" | "signups" | "editorial-checks";
250
251
  export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "messages" | "assignments" | "administration" | "search";
251
252
  type LexicalRuleScope = "publication" | "corpus" | "classifier" | "category";
252
253
  type AnalysisReindexMode = "online-update" | "classifier-retrain" | "scoped-topic-rebuild" | "entity-graph-rebuild" | "generated-analysis-rebuild";
@@ -333,13 +334,6 @@ type NoteTarget = {
333
334
  versionNumber?: number | null;
334
335
  };
335
336
 
336
- type NoteComposerControl = {
337
- action: NewsroomDetailAction;
338
- dialog: ReactNode;
339
- error: string | null;
340
- loading: boolean;
341
- };
342
-
343
337
  type NewsroomSearchRequest = {
344
338
  anchor?: KnowledgeQueryAnchor | null;
345
339
  from?: string | null;
@@ -460,17 +454,6 @@ type ReferenceQualityResponse = {
460
454
  errors?: Array<{ message?: string | null } | string | null> | null;
461
455
  };
462
456
 
463
- type ReferenceNoteResponse = {
464
- data?: {
465
- ok?: boolean | null;
466
- referenceId?: string | null;
467
- messageId?: string | null;
468
- relationId?: string | null;
469
- status?: string | null;
470
- } | null;
471
- errors?: Array<{ message?: string | null } | string | null> | null;
472
- };
473
-
474
457
  type ReferenceCorpusMoveResponse = {
475
458
  data?: {
476
459
  ok?: boolean | null;
@@ -642,14 +625,12 @@ export function NewsDeskWorkspace({
642
625
  dashboard,
643
626
  initialTab = "overview",
644
627
  initialSelection = {},
645
- sectionPageId = null,
646
628
  }: {
647
629
  analysisProfiles?: AnalysisProfileSummary[];
648
630
  configuredCorpora?: CategorySteeringCorpus[];
649
631
  dashboard: CategorySteeringDashboard | null;
650
632
  initialTab?: NewsDeskTab;
651
633
  initialSelection?: NewsDeskSelection;
652
- sectionPageId?: string | null;
653
634
  }) {
654
635
  const session = useOptionalNewsDeskClient();
655
636
  const sessionDashboard = useMemo(() => {
@@ -665,7 +646,7 @@ export function NewsDeskWorkspace({
665
646
  proposals: needsProposalFallback ? fallbackProposals : session.shell.dashboard.proposals,
666
647
  };
667
648
  }, [dashboard?.newsroomSections, dashboard?.proposals, session?.shell.dashboard]);
668
- const showSectionTabs = initialTab === "overview" && !sectionPageId;
649
+ const showSectionTabs = initialTab === "overview";
669
650
 
670
651
  if (dashboard?.isDemo) {
671
652
  return (
@@ -677,7 +658,6 @@ export function NewsDeskWorkspace({
677
658
  editorShellReady
678
659
  initialSelection={initialSelection}
679
660
  initialTab={initialTab}
680
- sectionPageId={sectionPageId}
681
661
  authState={{ status: "signedIn", label: "Demo Desk" }}
682
662
  isRefreshing={false}
683
663
  shellError={null}
@@ -695,7 +675,6 @@ export function NewsDeskWorkspace({
695
675
  editorShellReady={session.shell.phase === "ready"}
696
676
  initialSelection={initialSelection}
697
677
  initialTab={initialTab}
698
- sectionPageId={sectionPageId}
699
678
  authState={session.shell.auth}
700
679
  isRefreshing={session.shell.phase === "refreshing"}
701
680
  shellError={session.shell.error}
@@ -720,7 +699,6 @@ export function NewsDeskWorkspace({
720
699
  editorShellReady={false}
721
700
  initialSelection={initialSelection}
722
701
  initialTab={initialTab}
723
- sectionPageId={sectionPageId}
724
702
  authState={session?.shell.auth ?? { status: "signedOut", label: "Signed out" }}
725
703
  isRefreshing={session?.shell.phase === "refreshing"}
726
704
  shellError={session?.shell.error ?? null}
@@ -739,7 +717,6 @@ function NewsDeskDashboard({
739
717
  editorShellReady = false,
740
718
  initialTab,
741
719
  initialSelection,
742
- sectionPageId,
743
720
  authState,
744
721
  isRefreshing,
745
722
  shellError,
@@ -754,7 +731,6 @@ function NewsDeskDashboard({
754
731
  editorShellReady?: boolean;
755
732
  initialTab: NewsDeskTab;
756
733
  initialSelection: NewsDeskSelection;
757
- sectionPageId?: string | null;
758
734
  authState: ReaderAuthSnapshot;
759
735
  isRefreshing: boolean;
760
736
  shellError: string | null;
@@ -773,7 +749,6 @@ function NewsDeskDashboard({
773
749
  () => parseReferenceLineageIdFromNewsroomPathname(pathname),
774
750
  [pathname],
775
751
  );
776
- const isSectionPage = Boolean(sectionPageId);
777
752
  const [corpora, setCorpora] = useState(dashboard.corpora);
778
753
  const [importRuns, setImportRuns] = useState(dashboard.importRuns);
779
754
  const [categorySets, setCategorySets] = useState(dashboard.categorySets);
@@ -976,10 +951,6 @@ function NewsDeskDashboard({
976
951
  })
977
952
  ), [activeCategorySet?.id, activeCategoryTreeNodes, categorys]);
978
953
  const summaryStatus = newsroomSummaryStatus({ summary, summaryStatus: dashboard.summaryStatus });
979
- const activeNewsroomSection = sectionPageId
980
- ? newsroomSections.find((section) => section.id === sectionPageId && section.enabled !== false && section.enabledStatus !== "disabled") ?? null
981
- : null;
982
- const mastheadTitle = activeNewsroomSection?.title ?? (isSectionPage ? "SECTION" : "NEWSROOM");
983
954
  const tabCounts = useMemo<Record<NewsDeskTab, number | null>>(() => ({
984
955
  overview: 0,
985
956
  desks: summaryCountFromRecord(summary, "categories"),
@@ -1282,7 +1253,7 @@ function NewsDeskDashboard({
1282
1253
  if (
1283
1254
  canRefreshNewsroomSections
1284
1255
  && newsroomSections.length === 0
1285
- && (activeTab === "administration" || activeTab === "overview" || isSectionPage)
1256
+ && (activeTab === "administration" || activeTab === "overview")
1286
1257
  && !hasRefreshedNewsroomSections
1287
1258
  ) {
1288
1259
  setHasRefreshedNewsroomSections(true);
@@ -1361,7 +1332,6 @@ function NewsDeskDashboard({
1361
1332
  loadedSections.messages,
1362
1333
  loadedSections.references,
1363
1334
  loadedSections.semanticRelations,
1364
- isSectionPage,
1365
1335
  refreshNewsroomSections,
1366
1336
  ]);
1367
1337
 
@@ -1952,118 +1922,6 @@ function NewsDeskDashboard({
1952
1922
  });
1953
1923
  }
1954
1924
 
1955
- async function createNote(target: NoteTarget, summary: string, body: string): Promise<void> {
1956
- const cleanSummary = summary.trim();
1957
- const cleanBody = body.trim();
1958
- if (!cleanSummary) throw new Error("Note summary is required.");
1959
- if (!cleanBody) throw new Error("Note body is required.");
1960
- const now = new Date().toISOString();
1961
- const subjectSeed = [
1962
- target.kind,
1963
- target.lineageId,
1964
- cleanSummary,
1965
- cleanBody,
1966
- now,
1967
- ];
1968
- const messageId = `message-note-${safeUiId(target.kind)}-${safeUiId(target.lineageId)}-${hashUiKey(subjectSeed)}`;
1969
- const message: MessageRecord = {
1970
- id: messageId,
1971
- messageKind: "note",
1972
- messageDomain: "knowledge",
1973
- status: "active",
1974
- summary: cleanSummary,
1975
- source: "newsroom",
1976
- authorLabel: authState.label,
1977
- threadId: messageId,
1978
- sequenceNumber: 1,
1979
- createdAt: now,
1980
- updatedAt: now,
1981
- newsroomFeedKey: "messages",
1982
- body: cleanBody,
1983
- metadata: {
1984
- targetKind: target.kind,
1985
- targetId: target.id,
1986
- targetLineageId: target.lineageId,
1987
- },
1988
- };
1989
- const relation = buildUiNoteRelation(message, target, now);
1990
- setActionState({ id: messageId, message: "note pending", tone: "pending" });
1991
-
1992
- if (dashboard.isDemo) {
1993
- setMessages((current) => [message, ...current.filter((entry) => entry.id !== message.id)]);
1994
- setSemanticRelations((current) => [relation, ...current.filter((entry) => entry.id !== relation.id)]);
1995
- setActionState({ id: messageId, message: "note saved", tone: "ok" });
1996
- return;
1997
- }
1998
-
1999
- if (target.kind === "reference") {
2000
- const response = await dataClient.mutations.createReferenceNote(
2001
- {
2002
- referenceId: target.id,
2003
- summary: cleanSummary,
2004
- body: cleanBody,
2005
- actorLabel: authState.label,
2006
- },
2007
- { authMode: USER_POOL_AUTH_MODE },
2008
- );
2009
- const created = assertReferenceNoteMutationSucceeded(response as ReferenceNoteResponse, target.id);
2010
- const persistedMessageId = created.messageId ?? messageId;
2011
- const persistedRelationId = created.relationId ?? relation.id;
2012
- const persistedMessage: MessageRecord = {
2013
- ...message,
2014
- id: persistedMessageId,
2015
- };
2016
- const persistedRelation: SemanticRelationRecord = {
2017
- ...relation,
2018
- id: persistedRelationId,
2019
- subjectId: persistedMessageId,
2020
- subjectLineageId: persistedMessageId,
2021
- subjectVersionKey: semanticVersionKey("message", persistedMessageId),
2022
- subjectStateKey: semanticStateKey("message", persistedMessageId),
2023
- };
2024
- setMessages((current) => [persistedMessage, ...current.filter((entry) => entry.id !== persistedMessage.id)]);
2025
- setSemanticRelations((current) => [persistedRelation, ...current.filter((entry) => entry.id !== persistedRelation.id)]);
2026
- setActionState({ id: persistedMessageId, message: "note saved", tone: "ok" });
2027
- return;
2028
- }
2029
-
2030
- if (!("Message" in dataClient.models)) throw new Error("GraphQL model Message is not available in the deployed schema.");
2031
- if (!("SemanticRelation" in dataClient.models)) throw new Error("GraphQL model SemanticRelation is not available in the deployed schema.");
2032
-
2033
- const messageInput = {
2034
- id: message.id,
2035
- messageKind: message.messageKind,
2036
- messageDomain: message.messageDomain,
2037
- status: message.status,
2038
- summary: message.summary,
2039
- source: message.source,
2040
- authorLabel: message.authorLabel,
2041
- threadId: message.id,
2042
- sequenceNumber: 1,
2043
- createdAt: message.createdAt,
2044
- updatedAt: message.updatedAt,
2045
- newsroomFeedKey: "messages",
2046
- };
2047
- const messageResponse = await dataClient.models.Message.create(messageInput as never, { authMode: USER_POOL_AUTH_MODE });
2048
- assertNoGraphQLErrors(messageResponse.errors);
2049
- await uploadModelPayloadForOwner({
2050
- ownerKind: "message",
2051
- ownerId: messageId,
2052
- ownerLineageId: messageId,
2053
- role: "message_body",
2054
- sortKey: "message",
2055
- filename: "message.md",
2056
- mediaType: "text/markdown",
2057
- content: cleanBody.endsWith("\n") ? cleanBody : `${cleanBody}\n`,
2058
- status: "active",
2059
- });
2060
- const relationResponse = await dataClient.models.SemanticRelation.create(relation as never, { authMode: USER_POOL_AUTH_MODE });
2061
- assertNoGraphQLErrors(relationResponse.errors);
2062
- setMessages((current) => [message, ...current.filter((entry) => entry.id !== message.id)]);
2063
- setSemanticRelations((current) => [relation, ...current.filter((entry) => entry.id !== relation.id)]);
2064
- setActionState({ id: messageId, message: "note saved", tone: "ok" });
2065
- }
2066
-
2067
1925
  function createLexicalSteeringRule(draft: LexicalRuleDraft) {
2068
1926
  const normalizedTerm = normalizeKeywordTerm(draft.term);
2069
1927
  if (!normalizedTerm) {
@@ -3140,13 +2998,11 @@ function NewsDeskDashboard({
3140
2998
  }
3141
2999
  return entries;
3142
3000
  }, [summaryStatus, tabCounts]);
3143
- const opsPageTitle = isSectionPage
3144
- ? mastheadTitle
3145
- : activeTab === "overview"
3146
- ? "Overview"
3147
- : activeTab === "administration" && administrationPanel === "sections"
3148
- ? "Sections"
3149
- : formatDeskSectionHeadline(activeTab);
3001
+ const opsPageTitle = activeTab === "overview"
3002
+ ? "Overview"
3003
+ : activeTab === "administration" && administrationPanel === "sections"
3004
+ ? "Sections"
3005
+ : formatDeskSectionHeadline(activeTab);
3150
3006
 
3151
3007
  return (
3152
3008
  <>
@@ -3173,7 +3029,6 @@ function NewsDeskDashboard({
3173
3029
  </>
3174
3030
  )}
3175
3031
  pageTitle={opsPageTitle}
3176
- showNavigation={!isSectionPage}
3177
3032
  tabCounts={shellTabCounts}
3178
3033
  >
3179
3034
  <div
@@ -3200,17 +3055,7 @@ function NewsDeskDashboard({
3200
3055
  </NewsroomOpsStatusBanner>
3201
3056
  ) : null}
3202
3057
 
3203
- {isSectionPage ? (
3204
- <DeepNewsroomSectionView
3205
- demo={Boolean(dashboard.isDemo)}
3206
- disabled={controlsDisabled}
3207
- onCreateNote={createNote}
3208
- section={activeNewsroomSection}
3209
- sectionId={sectionPageId ?? ""}
3210
- sectionsLoaded={loadedSections.sections}
3211
- />
3212
- ) : null}
3213
- {!isSectionPage && activeTab === "overview" ? (
3058
+ {activeTab === "overview" ? (
3214
3059
  <NewsroomOpsOverview
3215
3060
  assignments={assignments}
3216
3061
  demo={Boolean(dashboard.isDemo)}
@@ -3219,7 +3064,7 @@ function NewsDeskDashboard({
3219
3064
  references={references}
3220
3065
  />
3221
3066
  ) : null}
3222
- {!isSectionPage && activeTab === "search" ? (
3067
+ {activeTab === "search" ? (
3223
3068
  <SearchDeskView
3224
3069
  assignments={assignments}
3225
3070
  categories={mergeCategoryRecords(categorys, activeCategoryTreeNodes)}
@@ -3230,13 +3075,13 @@ function NewsDeskDashboard({
3230
3075
  semanticNodes={semanticNodes}
3231
3076
  />
3232
3077
  ) : null}
3233
- {!isSectionPage && (activeTab === "desks" || activeTab === "topics") ? (
3078
+ {(activeTab === "desks" || activeTab === "topics") ? (
3234
3079
  <nav aria-label="Taxonomy views" className="taxonomy-view-switch" data-taxonomy-view-switch>
3235
3080
  <Link aria-current={activeTab === "desks" ? "page" : undefined} href={getNewsroomNavHref("/newsroom/desks", Boolean(dashboard.isDemo))}>Categories</Link>
3236
3081
  <Link aria-current={activeTab === "topics" ? "page" : undefined} href={getNewsroomNavHref("/newsroom/topics", Boolean(dashboard.isDemo))}>Topic proposals</Link>
3237
3082
  </nav>
3238
3083
  ) : null}
3239
- {!isSectionPage && activeTab === "desks" ? (
3084
+ {activeTab === "desks" ? (
3240
3085
  <DesksDeskView
3241
3086
  assignments={assignments}
3242
3087
  categoryByUid={categoryByUid}
@@ -3252,7 +3097,7 @@ function NewsDeskDashboard({
3252
3097
  statusMessage={actionState}
3253
3098
  />
3254
3099
  ) : null}
3255
- {!isSectionPage && activeTab === "topics" ? (
3100
+ {activeTab === "topics" ? (
3256
3101
  <TopicsDeskView
3257
3102
  disabled={controlsDisabled}
3258
3103
  initialProposalId={initialSelection.proposal}
@@ -3261,7 +3106,7 @@ function NewsDeskDashboard({
3261
3106
  proposals={proposals}
3262
3107
  />
3263
3108
  ) : null}
3264
- {!isSectionPage && activeTab === "concepts" ? (
3109
+ {activeTab === "concepts" ? (
3265
3110
  <ConceptsDeskView
3266
3111
  graph={graph}
3267
3112
  initialNodeLineageId={initialSelection.node}
@@ -3269,7 +3114,7 @@ function NewsDeskDashboard({
3269
3114
  semanticNodes={semanticNodes}
3270
3115
  />
3271
3116
  ) : null}
3272
- {!isSectionPage && activeTab === "references" ? (
3117
+ {activeTab === "references" ? (
3273
3118
  <NewsroomReferencesView
3274
3119
  demo={Boolean(dashboard.isDemo)}
3275
3120
  disabled={controlsDisabled}
@@ -3308,7 +3153,7 @@ function NewsDeskDashboard({
3308
3153
  references={references}
3309
3154
  />
3310
3155
  ) : null}
3311
- {!isSectionPage && activeTab === "messages" ? (
3156
+ {activeTab === "messages" ? (
3312
3157
  <MessagesDeskView
3313
3158
  assignments={assignments}
3314
3159
  graph={graph}
@@ -3320,7 +3165,7 @@ function NewsDeskDashboard({
3320
3165
  summary={summary}
3321
3166
  />
3322
3167
  ) : null}
3323
- {!isSectionPage && activeTab === "assignments" ? (
3168
+ {activeTab === "assignments" ? (
3324
3169
  <AssignmentDeskView
3325
3170
  actionState={actionState}
3326
3171
  analysisProfiles={analysisProfiles}
@@ -3342,7 +3187,7 @@ function NewsDeskDashboard({
3342
3187
  onReviewReportingPacket={runReportingPacketReview}
3343
3188
  />
3344
3189
  ) : null}
3345
- {!isSectionPage && activeTab === "administration" ? (
3190
+ {activeTab === "administration" ? (
3346
3191
  <AdministrationDeskView
3347
3192
  actionState={actionState}
3348
3193
  administrationPanel={administrationPanel}
@@ -4248,79 +4093,6 @@ function RotatingSectionTriangleIcon({ expanded }: { expanded: boolean }) {
4248
4093
  );
4249
4094
  }
4250
4095
 
4251
- function DeepNewsroomSectionView({
4252
- demo,
4253
- disabled,
4254
- onCreateNote,
4255
- section,
4256
- sectionId,
4257
- sectionsLoaded,
4258
- }: {
4259
- demo: boolean;
4260
- disabled: boolean;
4261
- onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
4262
- section: NewsroomSectionRecord | null;
4263
- sectionId: string;
4264
- sectionsLoaded: boolean;
4265
- }) {
4266
- const noteTarget = section ? noteTargetForNewsroomSection(section) : null;
4267
- const noteComposer = useNewsroomNoteComposer(noteTarget, disabled, onCreateNote);
4268
-
4269
- if (!section) {
4270
- return (
4271
- <section className="newsroom-deep-section" data-newsroom-deep-section-missing={sectionId}>
4272
- <p className="story-label">Section</p>
4273
- <h2>{sectionsLoaded ? "Section not found" : "Loading section"}</h2>
4274
- <p>{sectionsLoaded ? "This section is not configured or is disabled." : "Loading the configured newsroom section."}</p>
4275
- </section>
4276
- );
4277
- }
4278
-
4279
- const guidanceRows = [
4280
- ["Assignment Guidance", section.assignmentGuidance],
4281
- ["Kill Criteria", section.killCriteria],
4282
- ["Visual Guidance", section.visualGuidance],
4283
- ].filter((row): row is [string, string] => typeof row[1] === "string" && row[1].trim().length > 0);
4284
-
4285
- return (
4286
- <section
4287
- className="newsroom-deep-section"
4288
- data-newsroom-deep-section={section.id}
4289
- data-newsroom-section-type={formatNewsroomSectionTypeLabel(section.type).toLowerCase()}
4290
- >
4291
- <header className="newsroom-deep-section__header">
4292
- <p className="story-label" data-newsroom-deep-section-eyebrow>Section</p>
4293
- <h2 data-newsroom-deep-section-title>{section.title}</h2>
4294
- <p>{displayNewsroomSectionShortTitle(section)}</p>
4295
- <div className="newsroom-deep-section__header-actions">
4296
- <button type="button" disabled={noteComposer.action.disabled} onClick={noteComposer.action.onSelect}>
4297
- {noteComposer.loading ? "Saving Note" : "Add Note"}
4298
- </button>
4299
- <Link href={getNewsDeskTabHref("/newsroom", demo)}>Back to Newsroom</Link>
4300
- </div>
4301
- </header>
4302
- {noteComposer.error ? <div className="category-steering-alert" role="status">{noteComposer.error}</div> : null}
4303
- <div className="newsroom-deep-section__body">
4304
- <article className="newsroom-deep-section__field newsroom-deep-section__field--lead">
4305
- <p className="story-label">Mission</p>
4306
- <p>{section.editorialMission}</p>
4307
- </article>
4308
- <article className="newsroom-deep-section__field newsroom-deep-section__field--lead">
4309
- <p className="story-label">Policy</p>
4310
- <p>{section.editorialPolicy}</p>
4311
- </article>
4312
- {guidanceRows.map(([label, value]) => (
4313
- <article className="newsroom-deep-section__field" key={label}>
4314
- <p className="story-label">{label}</p>
4315
- <p>{value}</p>
4316
- </article>
4317
- ))}
4318
- </div>
4319
- {noteComposer.dialog}
4320
- </section>
4321
- );
4322
- }
4323
-
4324
4096
  function NewsroomOverviewSection({
4325
4097
  cards,
4326
4098
  detail,
@@ -4546,6 +4318,17 @@ function AdministrationDeskView({
4546
4318
  <strong>Agent delegations</strong>
4547
4319
  <span>Let an agent curate References for you</span>
4548
4320
  </Link>
4321
+ {editorialChecksPluginEnabled() ? (
4322
+ <Link
4323
+ href={administrationPanelHref("editorial-checks", isDemo)}
4324
+ data-news-desk-admin-nav="editorial-checks"
4325
+ data-active={administrationPanel === "editorial-checks" || undefined}
4326
+ onClick={() => onPanelChange("editorial-checks")}
4327
+ >
4328
+ <strong>Editorial checks</strong>
4329
+ <span>Policies the article editor checks drafts against</span>
4330
+ </Link>
4331
+ ) : null}
4549
4332
  {signupPluginEnabled() ? (
4550
4333
  <Link
4551
4334
  href={administrationPanelHref("signups", isDemo)}
@@ -4560,6 +4343,7 @@ function AdministrationDeskView({
4560
4343
  </nav>}
4561
4344
  <div className="news-desk-settings-panel">
4562
4345
  {administrationPanel === "signups" ? <SignupEntriesPanel /> : null}
4346
+ {administrationPanel === "editorial-checks" ? <EditorialChecksAdminPanel /> : null}
4563
4347
  {administrationPanel === "delegations" ? <AdministrationDelegationsPanel disabled={disabled} /> : null}
4564
4348
  {administrationPanel === "users" ? (
4565
4349
  <AdministrationUsersPanel
@@ -8618,27 +8402,6 @@ function LibraryBigIcon() {
8618
8402
  );
8619
8403
  }
8620
8404
 
8621
- function NoteIcon() {
8622
- return (
8623
- <svg
8624
- aria-hidden="true"
8625
- className="news-desk-detail-toggle__icon"
8626
- fill="none"
8627
- height="24"
8628
- stroke="currentColor"
8629
- strokeLinecap="round"
8630
- strokeLinejoin="round"
8631
- strokeWidth="2"
8632
- viewBox="0 0 24 24"
8633
- width="24"
8634
- >
8635
- <path d="M9 18h6" />
8636
- <path d="M10 22h4" />
8637
- <path d="M8.5 14.5A6 6 0 1 1 15.5 14.5c-.8.7-1.2 1.5-1.4 2.5h-4.2c-.2-1-.6-1.8-1.4-2.5Z" />
8638
- </svg>
8639
- );
8640
- }
8641
-
8642
8405
  function SearchMarkIcon() {
8643
8406
  return (
8644
8407
  <svg
@@ -9358,83 +9121,6 @@ function useNewsroomKnowledgeContext(target: KnowledgeQueryTarget | null) {
9358
9121
  };
9359
9122
  }
9360
9123
 
9361
- function useNewsroomNoteComposer(
9362
- target: NoteTarget | null,
9363
- disabled: boolean,
9364
- onCreate: (target: NoteTarget, summary: string, body: string) => Promise<void>,
9365
- ): NoteComposerControl {
9366
- const [dialogOpen, setDialogOpen] = useState(false);
9367
- const [summary, setSummary] = useState("");
9368
- const [body, setBody] = useState("");
9369
- const [state, setState] = useState<{ error: string | null; loading: boolean; targetKey: string | null }>({
9370
- error: null,
9371
- loading: false,
9372
- targetKey: null,
9373
- });
9374
- const targetKey = target ? noteTargetKey(target) : null;
9375
-
9376
- useEffect(() => {
9377
- setDialogOpen(false);
9378
- setSummary("");
9379
- setBody("");
9380
- setState({ error: null, loading: false, targetKey });
9381
- }, [targetKey]);
9382
-
9383
- const run = async () => {
9384
- if (!target || disabled || state.loading) return;
9385
- const cleanSummary = summary.trim();
9386
- const cleanBody = body.trim();
9387
- if (!cleanSummary || !cleanBody) {
9388
- setState({ error: "Note summary and body are required.", loading: false, targetKey });
9389
- return;
9390
- }
9391
- setState({ error: null, loading: true, targetKey });
9392
- try {
9393
- await onCreate(target, cleanSummary, cleanBody);
9394
- setState({ error: null, loading: false, targetKey });
9395
- setDialogOpen(false);
9396
- setSummary("");
9397
- setBody("");
9398
- } catch (error) {
9399
- setState({
9400
- error: error instanceof Error ? error.message : "Could not save note.",
9401
- loading: false,
9402
- targetKey,
9403
- });
9404
- }
9405
- };
9406
- const stateMatchesTarget = state.targetKey === targetKey;
9407
- const loading = stateMatchesTarget ? state.loading : false;
9408
- const error = stateMatchesTarget ? state.error : null;
9409
-
9410
- return {
9411
- action: {
9412
- ariaLabel: "Add note",
9413
- disabled: disabled || !target || loading,
9414
- icon: <NoteIcon />,
9415
- key: "add-note",
9416
- label: loading ? "Saving Note" : "Add Note",
9417
- onSelect: () => setDialogOpen(true),
9418
- } satisfies NewsroomDetailAction,
9419
- dialog: target ? (
9420
- <NoteComposerDialog
9421
- body={body}
9422
- disabled={disabled || loading}
9423
- error={error}
9424
- onBodyChange={setBody}
9425
- onClose={() => setDialogOpen(false)}
9426
- onRun={run}
9427
- onSummaryChange={setSummary}
9428
- open={dialogOpen}
9429
- summary={summary}
9430
- target={target}
9431
- />
9432
- ) : null,
9433
- error,
9434
- loading,
9435
- };
9436
- }
9437
-
9438
9124
  function useNewsroomRouteSearch({
9439
9125
  activeTab,
9440
9126
  assignments,
@@ -9756,89 +9442,6 @@ function KnowledgeQueryDialog({
9756
9442
  );
9757
9443
  }
9758
9444
 
9759
- function NoteComposerDialog({
9760
- body,
9761
- disabled,
9762
- error,
9763
- onBodyChange,
9764
- onClose,
9765
- onRun,
9766
- onSummaryChange,
9767
- open,
9768
- summary,
9769
- target,
9770
- }: {
9771
- body: string;
9772
- disabled: boolean;
9773
- error: string | null;
9774
- onBodyChange: (value: string) => void;
9775
- onClose: () => void;
9776
- onRun: () => void;
9777
- onSummaryChange: (value: string) => void;
9778
- open: boolean;
9779
- summary: string;
9780
- target: NoteTarget;
9781
- }) {
9782
- useEffect(() => {
9783
- if (!open) return;
9784
- function handleKeyDown(event: KeyboardEvent) {
9785
- if (event.key === "Escape") onClose();
9786
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") onRun();
9787
- }
9788
- window.addEventListener("keydown", handleKeyDown);
9789
- return () => window.removeEventListener("keydown", handleKeyDown);
9790
- }, [onClose, onRun, open]);
9791
-
9792
- if (!open) return null;
9793
-
9794
- return (
9795
- <div
9796
- className="news-desk-modal"
9797
- data-news-desk-note-modal
9798
- onMouseDown={(event) => {
9799
- if (event.target === event.currentTarget && !disabled) onClose();
9800
- }}
9801
- >
9802
- <div className="news-desk-modal__dialog news-desk-modal__dialog--knowledge-query" role="dialog" aria-modal="true" aria-labelledby="note-composer-title">
9803
- <header className="news-desk-modal__header">
9804
- <div>
9805
- <p className="story-label">Note</p>
9806
- <h3 id="note-composer-title">{target.title}</h3>
9807
- <span>{target.kind} / {target.lineageId}</span>
9808
- </div>
9809
- <button type="button" disabled={disabled} onClick={onClose}>Close</button>
9810
- </header>
9811
- <div className="news-desk-knowledge-query-form">
9812
- <label>
9813
- <span>Summary</span>
9814
- <input
9815
- disabled={disabled}
9816
- value={summary}
9817
- onChange={(event) => onSummaryChange(event.target.value)}
9818
- />
9819
- </label>
9820
- <label>
9821
- <span>Note Body</span>
9822
- <textarea
9823
- disabled={disabled}
9824
- rows={8}
9825
- value={body}
9826
- onChange={(event) => onBodyChange(event.target.value)}
9827
- />
9828
- </label>
9829
- {error ? <div className="category-steering-alert" role="status">{error}</div> : null}
9830
- </div>
9831
- <footer className="news-desk-modal__actions">
9832
- <button type="button" disabled={disabled} onClick={onClose}>Cancel</button>
9833
- <button type="button" className="news-desk-assignment-create-button" disabled={disabled || !summary.trim() || !body.trim()} onClick={onRun}>
9834
- {disabled ? "Saving" : "Save Note"}
9835
- </button>
9836
- </footer>
9837
- </div>
9838
- </div>
9839
- );
9840
- }
9841
-
9842
9445
  function KnowledgeQueryStatus({ error, loading }: { error: string | null; loading: boolean }) {
9843
9446
  if (!loading && !error) return null;
9844
9447
  return (
@@ -12910,17 +12513,6 @@ function noteTargetForReference(reference: ReferenceRecord): NoteTarget {
12910
12513
  };
12911
12514
  }
12912
12515
 
12913
- function noteTargetForNewsroomSection(section: NewsroomSectionRecord): NoteTarget {
12914
- return {
12915
- kind: "newsroomSection",
12916
- id: section.id,
12917
- lineageId: section.id,
12918
- title: section.title,
12919
- subtitle: section.type,
12920
- versionNumber: null,
12921
- };
12922
- }
12923
-
12924
12516
  function noteTargetForSemanticObject(object: SemanticObjectSummary): NoteTarget | null {
12925
12517
  if (object.kind !== "category" && object.kind !== "semanticNode") return null;
12926
12518
  return {
@@ -12933,55 +12525,6 @@ function noteTargetForSemanticObject(object: SemanticObjectSummary): NoteTarget
12933
12525
  };
12934
12526
  }
12935
12527
 
12936
- function noteTargetKey(target: NoteTarget): string {
12937
- return `${target.kind}#${target.lineageId}`;
12938
- }
12939
-
12940
- function buildUiNoteRelation(message: MessageRecord, target: NoteTarget, now: string): SemanticRelationRecord {
12941
- const subjectStateKey = semanticStateKey("message", message.id);
12942
- const objectStateKey = semanticStateKey(target.kind, target.lineageId);
12943
- const subjectVersionKey = semanticVersionKey("message", message.id);
12944
- const objectVersionKey = semanticVersionKey(target.kind, target.id);
12945
- return {
12946
- id: `semantic-relation-${hashUiKey([subjectVersionKey, "note_about", objectVersionKey])}`,
12947
- relationState: "current",
12948
- predicate: "note_about",
12949
- relationTypeId: "semantic-relation-type-note-about",
12950
- relationTypeKey: "note_about",
12951
- relationDomain: "knowledge",
12952
- subjectKind: "message",
12953
- subjectId: message.id,
12954
- subjectLineageId: message.id,
12955
- subjectVersionNumber: 1,
12956
- objectKind: target.kind,
12957
- objectId: target.id,
12958
- objectLineageId: target.lineageId,
12959
- objectVersionNumber: target.versionNumber ?? null,
12960
- subjectStateKey,
12961
- objectStateKey,
12962
- objectSubjectStateKey: `${objectStateKey}#message`,
12963
- predicateObjectStateKey: `note_about#${objectStateKey}`,
12964
- subjectVersionKey,
12965
- objectVersionKey,
12966
- score: null,
12967
- confidence: null,
12968
- rank: 1,
12969
- classifierId: null,
12970
- modelVersion: null,
12971
- reviewRecommended: false,
12972
- sourceSnapshotId: null,
12973
- importRunId: null,
12974
- importedAt: now,
12975
- createdAt: now,
12976
- updatedAt: now,
12977
- newsroomFeedKey: "semanticRelations",
12978
- metadata: {
12979
- targetTitle: target.title,
12980
- targetSubtitle: target.subtitle ?? null,
12981
- },
12982
- };
12983
- }
12984
-
12985
12528
  function buildUiAnalysisTargetRelation({
12986
12529
  assignment,
12987
12530
  categorySet,
@@ -13264,9 +12807,14 @@ function signupPluginEnabled(): boolean {
13264
12807
  return pluginSettings(amplifyOutputs as PluginOutputs, "signup") !== null;
13265
12808
  }
13266
12809
 
12810
+ function editorialChecksPluginEnabled(): boolean {
12811
+ return pluginSettings(amplifyOutputs as PluginOutputs, "editorial-checks") !== null;
12812
+ }
12813
+
13267
12814
  function normalizeAdministrationPanel(value: string | null | undefined): AdministrationPanel {
13268
12815
  if (value === "policies" || value === "sections" || value === "procedures" || value === "delegations") return value;
13269
12816
  if (value === "signups" && signupPluginEnabled()) return value;
12817
+ if (value === "editorial-checks" && editorialChecksPluginEnabled()) return value;
13270
12818
  return "users";
13271
12819
  }
13272
12820
 
@@ -13310,10 +12858,6 @@ function isEnabledNewsroomSection(section: NewsroomSectionRecord): boolean {
13310
12858
  return section.enabled !== false && section.enabledStatus !== "disabled";
13311
12859
  }
13312
12860
 
13313
- function formatNewsroomSectionTypeLabel(value: string | null | undefined): "Canonical" | "Rotating" {
13314
- return normalizeNewsroomSectionType(value) === "canonical" ? "Canonical" : "Rotating";
13315
- }
13316
-
13317
12861
  function buildNewsroomSectionHref(sectionId: string, demo?: boolean): string {
13318
12862
  return getNewsDeskTabHref(`/newsroom/sections/${encodeURIComponent(sectionId)}`, demo);
13319
12863
  }
@@ -16104,25 +15648,6 @@ function assertReferenceQualityMutationSucceeded(
16104
15648
  return response.data;
16105
15649
  }
16106
15650
 
16107
- function assertReferenceNoteMutationSucceeded(
16108
- response: ReferenceNoteResponse,
16109
- referenceId: string,
16110
- ): NonNullable<ReferenceNoteResponse["data"]> {
16111
- if (response.errors?.length) {
16112
- throw new Error(response.errors.map(formatGraphQLError).join("; "));
16113
- }
16114
- if (!response.data?.ok) {
16115
- throw new Error(`Reference note was not saved for ${referenceId}.`);
16116
- }
16117
- if (response.data.referenceId && response.data.referenceId !== referenceId) {
16118
- throw new Error(`Reference note response did not match ${referenceId}.`);
16119
- }
16120
- if (!response.data.messageId || !response.data.relationId) {
16121
- throw new Error(`Reference note saved without required audit relations for ${referenceId}.`);
16122
- }
16123
- return response.data;
16124
- }
16125
-
16126
15651
  function assertReferenceMoveCorpusMutationSucceeded(
16127
15652
  response: ReferenceCorpusMoveResponse,
16128
15653
  referenceId: string,