@anthusai/papyrus 1.10.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/amplify/data/resource.js +70 -3
  2. package/amplify/data/resource.ts +75 -3
  3. package/amplify/data/schema.js +67 -0
  4. package/amplify/data/schema.ts +72 -0
  5. package/amplify/functions/category-action/handler.ts +190 -20
  6. package/amplify/functions/console-chat-responder/src/main.rs +1 -1
  7. package/amplify/functions/content-actions/handler.py +23 -0
  8. package/amplify/functions/content-actions/requirements.txt +1 -1
  9. package/amplify/functions/content-actions/resource.js +1 -1
  10. package/amplify/functions/content-actions/resource.ts +1 -1
  11. package/amplify/functions/renditions/requirements.txt +1 -1
  12. package/amplify/functions/shared/curation-delegation.ts +67 -0
  13. package/amplify/functions/shared/reference-curation-metadata.ts +20 -0
  14. package/amplify/functions/shared/slack-events.ts +2 -2
  15. package/amplify/publication/renditions-function.js +1 -1
  16. package/amplify/publication/renditions-function.ts +1 -1
  17. package/app/api/local-preview/derive/route.ts +28 -0
  18. package/app/api/local-preview/media/route.ts +25 -0
  19. package/app/globals.css +28 -28
  20. package/app/layout.tsx +8 -5
  21. package/app/newsroom/[section]/[...selection]/page.tsx +0 -1
  22. package/app/newsroom/[section]/page.tsx +0 -1
  23. package/app/newsroom/insights/[threadId]/page.tsx +5 -16
  24. package/app/newsroom/insights/page.tsx +4 -12
  25. package/components/administration-delegations-panel.tsx +204 -0
  26. package/components/board-card-excerpts.tsx +45 -0
  27. package/components/curation-delegation-client.ts +81 -0
  28. package/components/markus-ir-preview.tsx +47 -2
  29. package/components/news-desk-page.tsx +0 -1
  30. package/components/news-desk-taxonomy-client.ts +32 -220
  31. package/components/newsroom-article-editor.tsx +135 -39
  32. package/components/newsroom-assignment-detail.tsx +35 -1
  33. package/components/newsroom-board-view.tsx +29 -7
  34. package/components/newsroom-newsprint-board.css +9 -2
  35. package/components/newsroom-ops-overview.tsx +27 -5
  36. package/components/newsroom-organizer-shell.tsx +25 -7
  37. package/components/newsroom-organizer.css +218 -4
  38. package/components/newsroom-references-view.tsx +56 -21
  39. package/components/priming-proof-page.tsx +17 -8
  40. package/components/prospectus-account-chooser.tsx +98 -0
  41. package/components/reference-cyclotron-status.tsx +5 -5
  42. package/components/reference-cyclotron-suggestion.tsx +143 -0
  43. package/components/reference-live-agreement.tsx +62 -0
  44. package/components/story-reporting-trail-client.ts +91 -0
  45. package/components/story-reporting-trail.tsx +115 -0
  46. package/components/topic-steering-workspace.tsx +203 -674
  47. package/components/ui/sheet.tsx +1 -0
  48. package/components/wiki-concepts-view.tsx +153 -0
  49. package/components/wiki-index-client.ts +51 -0
  50. package/lib/content-actions-client.ts +3 -0
  51. package/lib/content-actions-demo.ts +55 -3
  52. package/lib/content-item-lifecycle.ts +4 -2
  53. package/lib/content-source-context.ts +9 -1
  54. package/lib/curation-delegation-helpers.ts +59 -0
  55. package/lib/cyclotron-suggestions.ts +263 -0
  56. package/lib/front-matter-fields.ts +42 -0
  57. package/lib/graphql-content-repository.ts +42 -7
  58. package/lib/local-preview-seed.ts +174 -3
  59. package/lib/local-preview-tenant.ts +27 -0
  60. package/lib/newsroom-articles.ts +2 -0
  61. package/lib/newsroom-board.ts +4 -0
  62. package/lib/newsroom-forum-routes.ts +2 -43
  63. package/lib/newsroom-index-filters.ts +3 -40
  64. package/lib/newsroom-media-backend.ts +5 -0
  65. package/lib/newsroom-nav.ts +1 -3
  66. package/lib/newsroom-organizer-nav.ts +5 -12
  67. package/lib/newsroom-section-routing.ts +1 -1
  68. package/lib/newsroom-sections.ts +12 -0
  69. package/lib/papyrus-web-locations.ts +3 -24
  70. package/lib/priming-proof-report.ts +1 -0
  71. package/lib/prospectus-tenant.ts +22 -0
  72. package/lib/relevance-decisions.ts +29 -0
  73. package/lib/semantic-graph.ts +5 -5
  74. package/lib/site-brand.ts +9 -0
  75. package/lib/story-reporting-trail.ts +106 -0
  76. package/lib/tenant-keys.ts +32 -0
  77. package/lib/wiki.ts +56 -0
  78. package/package.json +21 -1
  79. package/routes.manifest.json +23 -7
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
 
3
+ import { AdministrationDelegationsPanel } from "./administration-delegations-panel";
4
+ import { WikiConceptsView } from "./wiki-concepts-view";
3
5
  import { Hub } from "aws-amplify/utils";
4
6
  import { fetchAuthSession } from "aws-amplify/auth";
5
7
  import { gsap } from "gsap";
@@ -34,17 +36,13 @@ import {
34
36
  loadEditorProcedureData,
35
37
  loadEditorSemanticRelationsData,
36
38
  loadEditionForumThreads,
37
- loadInsightForumThreadById,
38
- loadInsightForumThreads,
39
39
  loadNewsroomAssignmentPage,
40
40
  loadNewsroomMessagePage,
41
41
  loadNewsroomReferencePage,
42
42
  loadNewsroomSemanticNodePage,
43
43
  appendForumThreadMessageRecord,
44
- appendInsightThreadReplyRecord,
45
44
  createSectionForumThreadRecord,
46
45
  deleteForumThreadMessageRecord,
47
- deleteInsightThreadMessageRecord,
48
46
  ensureEditionForumThreadRecord,
49
47
  publishProcedureVersionRecord,
50
48
  runNewsroomKnowledgeQuery,
@@ -57,6 +55,7 @@ import {
57
55
  type KnowledgeQueryResponse,
58
56
  type NewsroomRecordPage,
59
57
  loadCyclotronStatus,
58
+ loadCyclotronShadowAgreement,
60
59
  requestCyclotronPriming,
61
60
  loadCyclotronPrimingRequests,
62
61
  requestCyclotronReviewRate,
@@ -68,11 +67,8 @@ import {
68
67
  effectiveReferencesIndexFilters,
69
68
  normalizeReferenceIndexOrder,
70
69
  readAssignmentsIndexFilters,
71
- readInsightsIndexFilters,
72
70
  readMessagesIndexFilters,
73
71
  readReferencesIndexFilters,
74
- effectiveInsightsIndexFilters,
75
- parseInsightThreadIdFromNewsroomPathname,
76
72
  referencesStatusFromUrl,
77
73
  referencesStatusToUrl,
78
74
  type ReferenceIndexOrder,
@@ -162,7 +158,10 @@ import {
162
158
  REFERENCE_REJECTION_REASON_CODES,
163
159
  referenceCurationStatusForAction,
164
160
  } from "../lib/reference-policy";
161
+ import { isLocalPreviewEnabled } from "../lib/local-preview";
165
162
  import { currentRelevanceDecisions } from "../lib/relevance-decisions";
163
+ import { delegatedCurationsByReference, referencesWithHumanLatestCuration, type ShadowAgreement } from "../lib/cyclotron-suggestions";
164
+ import { createLocalPreviewCyclotronFixtures } from "../lib/local-preview-seed";
166
165
  import {
167
166
  buildPrimingRequest,
168
167
  buildReviewRateRequest,
@@ -201,12 +200,10 @@ import {
201
200
  } from "../lib/reporting-story-budget";
202
201
  import {
203
202
  buildForumThreadUrl,
204
- buildInsightThreadUrl,
205
203
  getForumMessageAnchorId,
206
204
  isForumThreadId,
207
205
  parseForumMessageAnchorFromHash,
208
206
  pushForumThreadUrl,
209
- pushInsightThreadUrl,
210
207
  readCurrentForumRoute,
211
208
  } from "../lib/newsroom-forum-routes";
212
209
 
@@ -246,8 +243,8 @@ type AssignmentAction = "claim" | "release" | "complete" | "cancel" | "reopen" |
246
243
  type ReportingPacketReviewDecision = "select" | "merge" | "brief" | "hold" | "kill";
247
244
  type AssignmentDeskViewMode = "queue" | "budget";
248
245
  type UserRoleAction = "grant" | "revoke";
249
- type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "signups";
250
- export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "insights" | "messages" | "assignments" | "administration" | "search";
246
+ type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "delegations" | "signups";
247
+ export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "messages" | "assignments" | "administration" | "search";
251
248
  type LexicalRuleScope = "publication" | "corpus" | "classifier" | "category";
252
249
  type AnalysisReindexMode = "online-update" | "classifier-retrain" | "scoped-topic-rebuild" | "entity-graph-rebuild" | "generated-analysis-rebuild";
253
250
  type ReferenceProcessingStatus = "created" | "processable" | "processed" | "blocked";
@@ -324,7 +321,7 @@ type NewsroomDetailAction = {
324
321
  onSelect: () => void;
325
322
  };
326
323
 
327
- type InsightTarget = {
324
+ type NoteTarget = {
328
325
  kind: "assignment" | "category" | "item" | "newsroomSection" | "reference" | "semanticNode";
329
326
  id: string;
330
327
  lineageId: string;
@@ -333,7 +330,7 @@ type InsightTarget = {
333
330
  versionNumber?: number | null;
334
331
  };
335
332
 
336
- type InsightComposerControl = {
333
+ type NoteComposerControl = {
337
334
  action: NewsroomDetailAction;
338
335
  dialog: ReactNode;
339
336
  error: string | null;
@@ -460,7 +457,7 @@ type ReferenceQualityResponse = {
460
457
  errors?: Array<{ message?: string | null } | string | null> | null;
461
458
  };
462
459
 
463
- type ReferenceInsightResponse = {
460
+ type ReferenceNoteResponse = {
464
461
  data?: {
465
462
  ok?: boolean | null;
466
463
  referenceId?: string | null;
@@ -792,7 +789,22 @@ function NewsDeskDashboard({
792
789
  const [referenceAttachments, setReferenceAttachments] = useState(dashboard.referenceAttachments);
793
790
  const [messages, setMessages] = useState(dashboard.messages);
794
791
  const [semanticRelations, setSemanticRelations] = useState(dashboard.semanticRelations);
795
- const relevanceDecisions = useMemo(() => currentRelevanceDecisions(semanticRelations), [semanticRelations]);
792
+ const cyclotronPreviewFixtures = useMemo(
793
+ () => (isLocalPreviewEnabled() ? createLocalPreviewCyclotronFixtures(references) : null),
794
+ [references],
795
+ );
796
+ const relevanceDecisions = useMemo(
797
+ () => currentRelevanceDecisions(cyclotronPreviewFixtures ? [...semanticRelations, ...cyclotronPreviewFixtures.relations] : semanticRelations),
798
+ [cyclotronPreviewFixtures, semanticRelations],
799
+ );
800
+ const delegatedCurations = useMemo(
801
+ () => delegatedCurationsByReference(cyclotronPreviewFixtures ? [...messages, ...cyclotronPreviewFixtures.messages] : messages),
802
+ [cyclotronPreviewFixtures, messages],
803
+ );
804
+ const humanCuratedReferenceLineageIds = useMemo(
805
+ () => referencesWithHumanLatestCuration(cyclotronPreviewFixtures ? [...messages, ...cyclotronPreviewFixtures.messages] : messages),
806
+ [cyclotronPreviewFixtures, messages],
807
+ );
796
808
  const relevanceCyclotronId = useMemo(
797
809
  () => [...relevanceDecisions.values()].find((decision) => decision.cyclotronId)?.cyclotronId ?? null,
798
810
  [relevanceDecisions],
@@ -827,6 +839,19 @@ function NewsDeskDashboard({
827
839
  .catch(() => { if (!cancelled) setCyclotronStatus(null); });
828
840
  return () => { cancelled = true; };
829
841
  }, [dashboard.isDemo, relevanceCyclotronId]);
842
+ const [shadowAgreement, setShadowAgreement] = useState<ShadowAgreement | null>(null);
843
+ useEffect(() => {
844
+ if (cyclotronPreviewFixtures) {
845
+ setShadowAgreement(cyclotronPreviewFixtures.shadowAgreement);
846
+ return;
847
+ }
848
+ if (!relevanceCyclotronId || dashboard.isDemo) return;
849
+ let cancelled = false;
850
+ loadCyclotronShadowAgreement(relevanceCyclotronId)
851
+ .then((loaded) => { if (!cancelled) setShadowAgreement(loaded); })
852
+ .catch(() => { if (!cancelled) setShadowAgreement(null); });
853
+ return () => { cancelled = true; };
854
+ }, [cyclotronPreviewFixtures, dashboard.isDemo, relevanceCyclotronId]);
830
855
  const [semanticNodes, setSemanticNodes] = useState(dashboard.semanticNodes);
831
856
  const [assignments, setAssignments] = useState(dashboard.assignments);
832
857
  const [assignmentEvents, setAssignmentEvents] = useState(dashboard.assignmentEvents);
@@ -927,9 +952,6 @@ function NewsDeskDashboard({
927
952
  overview: 0,
928
953
  desks: summaryCountFromRecord(summary, "categories"),
929
954
  messages: summaryCountFromRecord(summary, "messages"),
930
- insights: summary?.facets?.messages?.byKind?.insight
931
- ?? summary?.messageKindCounts?.insight
932
- ?? null,
933
955
  assignments: summaryCountFromRecord(summary, "assignments"),
934
956
  references: summaryCountFromRecord(summary, "references")
935
957
  ?? (hasHydratedReferences ? references.length : null),
@@ -1895,11 +1917,11 @@ function NewsDeskDashboard({
1895
1917
  });
1896
1918
  }
1897
1919
 
1898
- async function createInsight(target: InsightTarget, summary: string, body: string): Promise<void> {
1920
+ async function createNote(target: NoteTarget, summary: string, body: string): Promise<void> {
1899
1921
  const cleanSummary = summary.trim();
1900
1922
  const cleanBody = body.trim();
1901
- if (!cleanSummary) throw new Error("Insight summary is required.");
1902
- if (!cleanBody) throw new Error("Insight body is required.");
1923
+ if (!cleanSummary) throw new Error("Note summary is required.");
1924
+ if (!cleanBody) throw new Error("Note body is required.");
1903
1925
  const now = new Date().toISOString();
1904
1926
  const subjectSeed = [
1905
1927
  target.kind,
@@ -1908,10 +1930,10 @@ function NewsDeskDashboard({
1908
1930
  cleanBody,
1909
1931
  now,
1910
1932
  ];
1911
- const messageId = `message-insight-${safeUiId(target.kind)}-${safeUiId(target.lineageId)}-${hashUiKey(subjectSeed)}`;
1933
+ const messageId = `message-note-${safeUiId(target.kind)}-${safeUiId(target.lineageId)}-${hashUiKey(subjectSeed)}`;
1912
1934
  const message: MessageRecord = {
1913
1935
  id: messageId,
1914
- messageKind: "insight",
1936
+ messageKind: "note",
1915
1937
  messageDomain: "knowledge",
1916
1938
  status: "active",
1917
1939
  summary: cleanSummary,
@@ -1929,18 +1951,18 @@ function NewsDeskDashboard({
1929
1951
  targetLineageId: target.lineageId,
1930
1952
  },
1931
1953
  };
1932
- const relation = buildUiInsightRelation(message, target, now);
1933
- setActionState({ id: messageId, message: "insight pending", tone: "pending" });
1954
+ const relation = buildUiNoteRelation(message, target, now);
1955
+ setActionState({ id: messageId, message: "note pending", tone: "pending" });
1934
1956
 
1935
1957
  if (dashboard.isDemo) {
1936
1958
  setMessages((current) => [message, ...current.filter((entry) => entry.id !== message.id)]);
1937
1959
  setSemanticRelations((current) => [relation, ...current.filter((entry) => entry.id !== relation.id)]);
1938
- setActionState({ id: messageId, message: "insight saved", tone: "ok" });
1960
+ setActionState({ id: messageId, message: "note saved", tone: "ok" });
1939
1961
  return;
1940
1962
  }
1941
1963
 
1942
1964
  if (target.kind === "reference") {
1943
- const response = await dataClient.mutations.createReferenceInsight(
1965
+ const response = await dataClient.mutations.createReferenceNote(
1944
1966
  {
1945
1967
  referenceId: target.id,
1946
1968
  summary: cleanSummary,
@@ -1949,7 +1971,7 @@ function NewsDeskDashboard({
1949
1971
  },
1950
1972
  { authMode: USER_POOL_AUTH_MODE },
1951
1973
  );
1952
- const created = assertReferenceInsightMutationSucceeded(response as ReferenceInsightResponse, target.id);
1974
+ const created = assertReferenceNoteMutationSucceeded(response as ReferenceNoteResponse, target.id);
1953
1975
  const persistedMessageId = created.messageId ?? messageId;
1954
1976
  const persistedRelationId = created.relationId ?? relation.id;
1955
1977
  const persistedMessage: MessageRecord = {
@@ -1966,7 +1988,7 @@ function NewsDeskDashboard({
1966
1988
  };
1967
1989
  setMessages((current) => [persistedMessage, ...current.filter((entry) => entry.id !== persistedMessage.id)]);
1968
1990
  setSemanticRelations((current) => [persistedRelation, ...current.filter((entry) => entry.id !== persistedRelation.id)]);
1969
- setActionState({ id: persistedMessageId, message: "insight saved", tone: "ok" });
1991
+ setActionState({ id: persistedMessageId, message: "note saved", tone: "ok" });
1970
1992
  return;
1971
1993
  }
1972
1994
 
@@ -2004,7 +2026,7 @@ function NewsDeskDashboard({
2004
2026
  assertNoGraphQLErrors(relationResponse.errors);
2005
2027
  setMessages((current) => [message, ...current.filter((entry) => entry.id !== message.id)]);
2006
2028
  setSemanticRelations((current) => [relation, ...current.filter((entry) => entry.id !== relation.id)]);
2007
- setActionState({ id: messageId, message: "insight saved", tone: "ok" });
2029
+ setActionState({ id: messageId, message: "note saved", tone: "ok" });
2008
2030
  }
2009
2031
 
2010
2032
  function createLexicalSteeringRule(draft: LexicalRuleDraft) {
@@ -3073,7 +3095,7 @@ function NewsDeskDashboard({
3073
3095
 
3074
3096
  const shellTabCounts = useMemo(() => {
3075
3097
  const entries: Partial<Record<string, NewsroomNavCount>> = {};
3076
- for (const tab of ["overview", "messages", "insights", "assignments", "references", "topics", "concepts", "administration"] as NewsDeskTab[]) {
3098
+ for (const tab of ["overview", "messages", "assignments", "references", "topics", "concepts", "administration"] as NewsDeskTab[]) {
3077
3099
  if (tab === "administration") continue;
3078
3100
  entries[tab] = {
3079
3101
  count: tabCounts[tab],
@@ -3087,7 +3109,9 @@ function NewsDeskDashboard({
3087
3109
  ? mastheadTitle
3088
3110
  : activeTab === "overview"
3089
3111
  ? "Overview"
3090
- : formatDeskSectionHeadline(activeTab);
3112
+ : activeTab === "administration" && administrationPanel === "sections"
3113
+ ? "Sections"
3114
+ : formatDeskSectionHeadline(activeTab);
3091
3115
 
3092
3116
  return (
3093
3117
  <>
@@ -3124,8 +3148,8 @@ function NewsDeskDashboard({
3124
3148
  >
3125
3149
  {activeTab !== "overview" && activeTab !== "assignments" && activeTab !== "messages" && activeTab !== "references" && activeTab !== "topics" && activeTab !== "concepts" && activeTab !== "search" ? (
3126
3150
  <NewsroomOpsSectionIntro
3127
- description={formatDeskSectionLede(activeTab)}
3128
- title={formatDeskSectionHeadline(activeTab)}
3151
+ description={activeTab === "administration" && administrationPanel === "sections" ? "Organize the publication's sections and the ontology that classifies its coverage." : formatDeskSectionLede(activeTab)}
3152
+ title={activeTab === "administration" && administrationPanel === "sections" ? "Sections" : formatDeskSectionHeadline(activeTab)}
3129
3153
  />
3130
3154
  ) : null}
3131
3155
 
@@ -3145,7 +3169,7 @@ function NewsDeskDashboard({
3145
3169
  <DeepNewsroomSectionView
3146
3170
  demo={Boolean(dashboard.isDemo)}
3147
3171
  disabled={controlsDisabled}
3148
- onCreateInsight={createInsight}
3172
+ onCreateNote={createNote}
3149
3173
  section={activeNewsroomSection}
3150
3174
  sectionId={sectionPageId ?? ""}
3151
3175
  sectionsLoaded={loadedSections.sections}
@@ -3171,6 +3195,12 @@ function NewsDeskDashboard({
3171
3195
  semanticNodes={semanticNodes}
3172
3196
  />
3173
3197
  ) : null}
3198
+ {!isSectionPage && (activeTab === "desks" || activeTab === "topics") ? (
3199
+ <nav aria-label="Taxonomy views" className="taxonomy-view-switch" data-taxonomy-view-switch>
3200
+ <Link aria-current={activeTab === "desks" ? "page" : undefined} href={getNewsroomNavHref("/newsroom/desks", Boolean(dashboard.isDemo))}>Categories</Link>
3201
+ <Link aria-current={activeTab === "topics" ? "page" : undefined} href={getNewsroomNavHref("/newsroom/topics", Boolean(dashboard.isDemo))}>Topic proposals</Link>
3202
+ </nav>
3203
+ ) : null}
3174
3204
  {!isSectionPage && activeTab === "desks" ? (
3175
3205
  <DesksDeskView
3176
3206
  assignments={assignments}
@@ -3198,14 +3228,10 @@ function NewsDeskDashboard({
3198
3228
  ) : null}
3199
3229
  {!isSectionPage && activeTab === "concepts" ? (
3200
3230
  <ConceptsDeskView
3201
- categories={mergeCategoryRecords(categorys, activeCategoryTreeNodes)}
3202
- disabled={controlsDisabled}
3203
3231
  graph={graph}
3204
- initialCategoryLineageId={initialSelection.category}
3205
3232
  initialNodeLineageId={initialSelection.node}
3206
- onCreateInsight={createInsight}
3233
+ isDemo={Boolean(dashboard.isDemo)}
3207
3234
  semanticNodes={semanticNodes}
3208
- summary={summary}
3209
3235
  />
3210
3236
  ) : null}
3211
3237
  {!isSectionPage && activeTab === "references" ? (
@@ -3224,6 +3250,9 @@ function NewsDeskDashboard({
3224
3250
  },
3225
3251
  }}
3226
3252
  decisions={relevanceDecisions}
3253
+ humanCuratedReferenceLineageIds={humanCuratedReferenceLineageIds}
3254
+ delegatedCurations={delegatedCurations}
3255
+ shadowAgreement={shadowAgreement}
3227
3256
  onRequestReviewRate={relevanceCyclotronId ? async (choice) => {
3228
3257
  const request = buildReviewRateRequest(choice, authState.label);
3229
3258
  if (dashboard.isDemo) return;
@@ -3241,15 +3270,6 @@ function NewsDeskDashboard({
3241
3270
  references={references}
3242
3271
  />
3243
3272
  ) : null}
3244
- {!isSectionPage && activeTab === "insights" ? (
3245
- <InsightsDeskView
3246
- graph={graph}
3247
- initialThreadId={initialSelection.forumThread}
3248
- isDemo={Boolean(dashboard.isDemo)}
3249
- messages={messages}
3250
- summary={summary}
3251
- />
3252
- ) : null}
3253
3273
  {!isSectionPage && activeTab === "messages" ? (
3254
3274
  <MessagesDeskView
3255
3275
  assignments={assignments}
@@ -4193,20 +4213,20 @@ function RotatingSectionTriangleIcon({ expanded }: { expanded: boolean }) {
4193
4213
  function DeepNewsroomSectionView({
4194
4214
  demo,
4195
4215
  disabled,
4196
- onCreateInsight,
4216
+ onCreateNote,
4197
4217
  section,
4198
4218
  sectionId,
4199
4219
  sectionsLoaded,
4200
4220
  }: {
4201
4221
  demo: boolean;
4202
4222
  disabled: boolean;
4203
- onCreateInsight: (target: InsightTarget, summary: string, body: string) => Promise<void>;
4223
+ onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
4204
4224
  section: NewsroomSectionRecord | null;
4205
4225
  sectionId: string;
4206
4226
  sectionsLoaded: boolean;
4207
4227
  }) {
4208
- const insightTarget = section ? insightTargetForNewsroomSection(section) : null;
4209
- const insightComposer = useNewsroomInsightComposer(insightTarget, disabled, onCreateInsight);
4228
+ const noteTarget = section ? noteTargetForNewsroomSection(section) : null;
4229
+ const noteComposer = useNewsroomNoteComposer(noteTarget, disabled, onCreateNote);
4210
4230
 
4211
4231
  if (!section) {
4212
4232
  return (
@@ -4235,13 +4255,13 @@ function DeepNewsroomSectionView({
4235
4255
  <h2 data-newsroom-deep-section-title>{section.title}</h2>
4236
4256
  <p>{displayNewsroomSectionShortTitle(section)}</p>
4237
4257
  <div className="newsroom-deep-section__header-actions">
4238
- <button type="button" disabled={insightComposer.action.disabled} onClick={insightComposer.action.onSelect}>
4239
- {insightComposer.loading ? "Saving Insight" : "Add Insight"}
4258
+ <button type="button" disabled={noteComposer.action.disabled} onClick={noteComposer.action.onSelect}>
4259
+ {noteComposer.loading ? "Saving Note" : "Add Note"}
4240
4260
  </button>
4241
4261
  <Link href={getNewsDeskTabHref("/newsroom", demo)}>Back to Newsroom</Link>
4242
4262
  </div>
4243
4263
  </header>
4244
- {insightComposer.error ? <div className="category-steering-alert" role="status">{insightComposer.error}</div> : null}
4264
+ {noteComposer.error ? <div className="category-steering-alert" role="status">{noteComposer.error}</div> : null}
4245
4265
  <div className="newsroom-deep-section__body">
4246
4266
  <article className="newsroom-deep-section__field newsroom-deep-section__field--lead">
4247
4267
  <p className="story-label">Mission</p>
@@ -4258,7 +4278,7 @@ function DeepNewsroomSectionView({
4258
4278
  </article>
4259
4279
  ))}
4260
4280
  </div>
4261
- {insightComposer.dialog}
4281
+ {noteComposer.dialog}
4262
4282
  </section>
4263
4283
  );
4264
4284
  }
@@ -4449,9 +4469,9 @@ function AdministrationDeskView({
4449
4469
  <div className="news-desk-columns news-desk-columns--administration" data-news-desk-section="administration">
4450
4470
  <div className="news-desk-main-column">
4451
4471
  <section className="category-steering-section category-steering-section--lead" aria-labelledby="newsroom-administration-title">
4452
- <SectionHeader title="Administration" detail="Settings for users, policies, sections, and procedures" />
4453
- <div className="news-desk-settings-shell">
4454
- <nav className="news-desk-settings-nav" aria-label="Administration settings">
4472
+ <SectionHeader title={administrationPanel === "sections" ? "Sections" : "Administration"} detail={administrationPanel === "sections" ? "The publication's sections and ontology" : "Settings for users, policies, and procedures"} />
4473
+ <div className="news-desk-settings-shell" data-settings-nav={administrationPanel === "sections" ? "hidden" : undefined}>
4474
+ {administrationPanel === "sections" ? null : <nav className="news-desk-settings-nav" aria-label="Administration settings">
4455
4475
  <Link
4456
4476
  href={administrationPanelHref("users", isDemo)}
4457
4477
  data-news-desk-admin-nav="users"
@@ -4470,15 +4490,6 @@ function AdministrationDeskView({
4470
4490
  <strong>Policies</strong>
4471
4491
  <span>Publication and category doctrine</span>
4472
4492
  </Link>
4473
- <Link
4474
- href={administrationPanelHref("sections", isDemo)}
4475
- data-news-desk-admin-nav="sections"
4476
- data-active={administrationPanel === "sections" || undefined}
4477
- onClick={() => onPanelChange("sections")}
4478
- >
4479
- <strong>Sections</strong>
4480
- <span>Canonical and floating newspaper sections</span>
4481
- </Link>
4482
4493
  <Link
4483
4494
  href={administrationPanelHref("procedures", isDemo)}
4484
4495
  data-news-desk-admin-nav="procedures"
@@ -4488,6 +4499,15 @@ function AdministrationDeskView({
4488
4499
  <strong>Procedures</strong>
4489
4500
  <span>Procedure registry and versioned Tactus code</span>
4490
4501
  </Link>
4502
+ <Link
4503
+ href={administrationPanelHref("delegations", isDemo)}
4504
+ data-news-desk-admin-nav="delegations"
4505
+ data-active={administrationPanel === "delegations" || undefined}
4506
+ onClick={() => onPanelChange("delegations")}
4507
+ >
4508
+ <strong>Agent delegations</strong>
4509
+ <span>Let an agent curate References for you</span>
4510
+ </Link>
4491
4511
  {signupPluginEnabled() ? (
4492
4512
  <Link
4493
4513
  href={administrationPanelHref("signups", isDemo)}
@@ -4499,9 +4519,10 @@ function AdministrationDeskView({
4499
4519
  <span>Addresses from the site&apos;s signup forms</span>
4500
4520
  </Link>
4501
4521
  ) : null}
4502
- </nav>
4522
+ </nav>}
4503
4523
  <div className="news-desk-settings-panel">
4504
4524
  {administrationPanel === "signups" ? <SignupEntriesPanel /> : null}
4525
+ {administrationPanel === "delegations" ? <AdministrationDelegationsPanel disabled={disabled} /> : null}
4505
4526
  {administrationPanel === "users" ? (
4506
4527
  <AdministrationUsersPanel
4507
4528
  canManageUsers={canManageUsers}
@@ -4987,7 +5008,7 @@ function AdministrationSectionsPanel({
4987
5008
 
4988
5009
  return (
4989
5010
  <div data-news-desk-admin-panel="sections">
4990
- <SectionHeader title="Newspaper Sections" detail={`${sortedSections.length} configured sections`} />
5011
+ <SectionHeader title="Sections" detail={`${sortedSections.length} configured sections`} />
4991
5012
  <div className="news-desk-administration-category-layout">
4992
5013
  <div className="news-desk-administration-category-list" data-news-desk-section-list>
4993
5014
  {sortedSections.length ? (
@@ -5824,7 +5845,7 @@ function DesksDeskView({
5824
5845
  }, [initialRootKey, roots, selectedRoot, selectedRootKey]);
5825
5846
 
5826
5847
  return (
5827
- <div className="news-desk-columns" data-news-desk-section="desks">
5848
+ <div className="news-desk-columns news-desk-columns--administration news-desk-columns--taxonomy" data-news-desk-section="desks">
5828
5849
  <div className="news-desk-main-column">
5829
5850
  <section className="category-steering-section category-steering-section--lead" aria-labelledby="newsroom-desks-title">
5830
5851
  <SectionHeader title="News Desks" detail={`${roots.length} root topic desks`} />
@@ -6374,148 +6395,27 @@ function TopicProposalEditModal({
6374
6395
  }
6375
6396
 
6376
6397
  function ConceptsDeskView({
6377
- categories,
6378
- disabled,
6379
6398
  graph,
6380
- initialCategoryLineageId,
6381
6399
  initialNodeLineageId,
6382
- onCreateInsight,
6400
+ isDemo,
6383
6401
  semanticNodes,
6384
- summary,
6385
6402
  }: {
6386
- categories: CategorySteeringCategory[];
6387
- disabled: boolean;
6388
6403
  graph: SemanticGraph;
6389
- initialCategoryLineageId?: string | null;
6390
6404
  initialNodeLineageId?: string | null;
6391
- onCreateInsight: (target: InsightTarget, summary: string, body: string) => Promise<void>;
6405
+ isDemo: boolean;
6392
6406
  semanticNodes: SemanticNodeRecord[];
6393
- summary?: NewsroomSummaryRecord | null;
6394
6407
  }) {
6395
- const [nodeKindFilter, setNodeKindFilter] = useState("");
6396
- const [nodeStatusFilter, setNodeStatusFilter] = useState("");
6397
- const [selectedNodeLineageId, setSelectedNodeLineageId] = useState(initialNodeLineageId ?? "");
6398
- const [isConceptDetailOpen, setIsConceptDetailOpen] = useState(Boolean(initialNodeLineageId));
6399
- const categoryContext = useMemo(() => buildCategoryDrilldownContext(categories, initialCategoryLineageId), [categories, initialCategoryLineageId]);
6400
6408
  const feed = useNewsroomPagedRows({
6401
6409
  initialItems: semanticNodes,
6402
- enabled: !categoryContext?.primary,
6403
- resetKey: `semanticNodes:${nodeKindFilter}:${nodeStatusFilter}`,
6404
- loadPage: (nextToken) => loadNewsroomSemanticNodePage({
6405
- nodeKind: nodeKindFilter,
6406
- status: nodeStatusFilter,
6407
- nextToken,
6408
- }),
6410
+ enabled: true,
6411
+ resetKey: "semanticNodes:wiki",
6412
+ loadPage: (nextToken) => loadNewsroomSemanticNodePage({ nodeKind: "", status: "", nextToken }),
6409
6413
  });
6410
- const categoryFilter = categoryContext.primary ? graph.resolve("category", categoryLineageId(categoryContext.primary)) : null;
6411
- const categoryConceptLineages = useMemo(() => (
6412
- categoryContext.primary
6413
- ? new Set(semanticNodesForCategoryContext(graph, categoryContext).map((node) => node.lineageId))
6414
- : null
6415
- ), [categoryContext, graph]);
6416
- const feedNodes = useMemo(() => (
6417
- feed.items.length
6418
- ? feed.items
6419
- : (feed.isLoading || Boolean(feed.error))
6420
- ? semanticNodes
6421
- : feed.items
6422
- ), [feed.error, feed.isLoading, feed.items, semanticNodes]);
6423
- const visibleNodes = useMemo(() => (
6424
- categoryConceptLineages
6425
- ? semanticNodes.filter((node) => categoryConceptLineages.has(node.lineageId ?? node.id))
6426
- : feedNodes
6427
- ), [categoryConceptLineages, feedNodes, semanticNodes]);
6428
- const fallbackConceptCategories = useMemo(() => (
6429
- visibleNodes.length
6430
- ? []
6431
- : categories.filter((category) => category.status !== "deprecated" && category.status !== "archived")
6432
- ), [categories, visibleNodes.length]);
6433
- const requestedNodeLineageId = selectedNodeLineageId || initialNodeLineageId || "";
6434
- const selected = selectSemanticNodeSummary(graph, visibleNodes, requestedNodeLineageId)
6435
- ?? (requestedNodeLineageId ? selectSemanticNodeSummary(graph, semanticNodes, requestedNodeLineageId) : null)
6436
- ?? (requestedNodeLineageId ? selectCategorySummary(graph, categories, requestedNodeLineageId) : null)
6437
- ?? categoryFilter
6438
- ?? selectSemanticNodeSummary(graph, visibleNodes)
6439
- ?? selectCategorySummary(graph, fallbackConceptCategories);
6440
- const conceptKnowledgeQuery = useNewsroomKnowledgeContext(selected ? {
6441
- anchor: {
6442
- kind: selected.kind === "category" ? "category" : "semanticNode",
6443
- id: selected.id ?? selected.lineageId,
6444
- lineageId: selected.lineageId,
6445
- },
6446
- title: selected.label,
6447
- subtitle: selected.subtitle,
6448
- } : null);
6449
- const conceptInsight = useNewsroomInsightComposer(
6450
- selected && (selected.kind === "category" || selected.kind === "semanticNode") ? insightTargetForSemanticObject(selected) : null,
6451
- disabled,
6452
- onCreateInsight,
6453
- );
6454
- const detail = categoryFilter
6455
- ? `${visibleNodes.length} graph nodes associated with ${categoryFilter.label}`
6456
- : visibleNodes.length
6457
- ? `${summaryCountFromRecord(summary, "semanticNodes") || visibleNodes.length} graph nodes`
6458
- : `${fallbackConceptCategories.length} taxonomy concepts`;
6459
- const nodeKindCounts = categoryFilter ? countSemanticNodesByKind(visibleNodes) : summary?.facets?.semanticNodes?.byNodeKind ?? countSemanticNodesByKind(visibleNodes);
6460
- const nodeStatusCounts = categoryFilter ? countSemanticNodesByStatus(visibleNodes) : summary?.facets?.semanticNodes?.byStatus ?? countSemanticNodesByStatus(visibleNodes);
6461
- const nodeKinds = sortedCountOptions(nodeKindCounts).slice(0, 8);
6462
- const cards = visibleNodes.length
6463
- ? visibleNodes.map((node, index) => semanticNodeToNewsroomCard(node, index))
6464
- : fallbackConceptCategories.map((category, index) => categoryToConceptNewsroomCard(category, index));
6465
- const cardIds = useMemo(() => new Set(cards.map((card) => card.id)), [cards]);
6466
- const selectedLineageId = selected && cardIds.has(selected.lineageId) ? selected.lineageId : null;
6467
- const selectNode = (lineageId: string) => {
6468
- setSelectedNodeLineageId(lineageId);
6469
- setIsConceptDetailOpen(true);
6470
- pushNewsroomDetailUrl("concepts", lineageId, false);
6471
- };
6472
- return (
6473
- <>
6474
- <NewsroomListDetailShell
6475
- animatedDetail
6476
- sectionKey="concepts"
6477
- canExpandDetail={Boolean(selected)}
6478
- detailOpen={isConceptDetailOpen}
6479
- selectionScrollKey={selectedLineageId}
6480
- actions={[conceptInsight.action]}
6481
- utilityActions={[conceptKnowledgeQuery.action]}
6482
- lede={(
6483
- <NewsroomDeskSectionLede headingId="semantic-concepts-title" section="concepts" />
6484
- )}
6485
- list={(
6486
- <section className="category-steering-section category-steering-section--lead" aria-label={detail}>
6487
- <NewsroomCardGrid
6488
- cards={cards}
6489
- emptyLabel={feed.isLoading ? "Loading semantic concepts" : feed.error ?? "No semantic nodes imported"}
6490
- filterLabel="Concept kind"
6491
- filterOptions={[
6492
- { key: "", label: "All concept kinds", count: summaryCountFromRecord(summary, "semanticNodes") || visibleNodes.length },
6493
- ...nodeKinds.map((option) => ({ key: option.key, label: formatAssignmentTypeLabel(option.key), count: option.count })),
6494
- ]}
6495
- filterValue={nodeKindFilter}
6496
- footerLabel={feed.error ?? undefined}
6497
- hasMore={!categoryFilter && feed.hasMore}
6498
- isLoadingMore={feed.isLoadingMore}
6499
- metricValue={nodeStatusFilter}
6500
- metrics={[
6501
- { key: "", label: "All", count: summaryCountFromRecord(summary, "semanticNodes") || visibleNodes.length },
6502
- ...sortedCountOptions(nodeStatusCounts).map((option) => ({ key: option.key, label: option.key, count: option.count })),
6503
- ]}
6504
- onFilterChange={setNodeKindFilter}
6505
- onLoadMore={feed.loadMore}
6506
- onMetricChange={setNodeStatusFilter}
6507
- onSelect={selectNode}
6508
- selectedId={selectedLineageId}
6509
- />
6510
- </section>
6511
- )}
6512
- onCloseDetail={() => setIsConceptDetailOpen(false)}
6513
- detail={<SemanticDetailPanel graph={graph} selected={selected} knowledgeQuery={conceptKnowledgeQuery} />}
6514
- />
6515
- {conceptKnowledgeQuery.dialog}
6516
- {conceptInsight.dialog}
6517
- </>
6518
- );
6414
+ useEffect(() => {
6415
+ if (feed.hasMore && !feed.isLoadingMore) feed.loadMore();
6416
+ }, [feed]);
6417
+ const nodes = feed.items.length ? feed.items : semanticNodes;
6418
+ return <WikiConceptsView demo={isDemo} graph={graph} initialNodeLineageId={initialNodeLineageId} nodes={nodes} />;
6519
6419
  }
6520
6420
 
6521
6421
  function ReferencesDeskView({
@@ -6529,7 +6429,7 @@ function ReferencesDeskView({
6529
6429
  initialReferenceLineageId,
6530
6430
  isDemo,
6531
6431
  deepLinkFetchEnabled = true,
6532
- onCreateInsight,
6432
+ onCreateNote,
6533
6433
  onHydrateReference,
6534
6434
  onMoveCorpus,
6535
6435
  onReview,
@@ -6554,7 +6454,7 @@ function ReferencesDeskView({
6554
6454
  initialReferenceLineageId?: string | null;
6555
6455
  isDemo?: boolean;
6556
6456
  deepLinkFetchEnabled?: boolean;
6557
- onCreateInsight: (target: InsightTarget, summary: string, body: string) => Promise<void>;
6457
+ onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
6558
6458
  onHydrateReference?: (reference: ReferenceRecord) => void;
6559
6459
  onMoveCorpus: (reference: ReferenceRecord, corpusId: string) => void;
6560
6460
  onReview: (reference: ReferenceRecord, action: ReferenceCurationAction, note?: string, reasonCode?: ReferenceRejectionReasonCode | null) => void;
@@ -6958,7 +6858,7 @@ function ReferencesDeskView({
6958
6858
  corpora={corpora}
6959
6859
  disabled={disabled}
6960
6860
  graph={graph}
6961
- onCreateInsight={onCreateInsight}
6861
+ onCreateNote={onCreateNote}
6962
6862
  onMoveCorpus={onMoveCorpus}
6963
6863
  onReview={runReferenceAction}
6964
6864
  onReviewTopicLabel={onReviewTopicLabel}
@@ -7010,365 +6910,6 @@ function NewsroomDeskSectionLede({
7010
6910
  );
7011
6911
  }
7012
6912
 
7013
- function InsightsDeskView({
7014
- graph,
7015
- initialThreadId = null,
7016
- isDemo = false,
7017
- messages,
7018
- summary,
7019
- }: {
7020
- graph: SemanticGraph;
7021
- initialThreadId?: string | null;
7022
- isDemo?: boolean;
7023
- messages: MessageRecord[];
7024
- summary?: NewsroomSummaryRecord | null;
7025
- }) {
7026
- const pathname = usePathname();
7027
- const pathnameThreadId = useMemo(
7028
- () => parseInsightThreadIdFromNewsroomPathname(pathname),
7029
- [pathname],
7030
- );
7031
- const resolvedInitialThreadId = initialThreadId ?? pathnameThreadId ?? "";
7032
- const forumMessageAnchorId = useForumMessageAnchorId();
7033
- const [domainFilter, setDomainFilter] = useState(() => {
7034
- if (typeof window === "undefined") return "";
7035
- return readInsightsIndexFilters(new URLSearchParams(window.location.search)).domain;
7036
- });
7037
- const [threads, setThreads] = useState<ForumThreadWithMessages[]>([]);
7038
- const [threadsLoading, setThreadsLoading] = useState(false);
7039
- const [threadsError, setThreadsError] = useState<string | null>(null);
7040
- const [nextToken, setNextToken] = useState<string | null>(null);
7041
- const [selectedThreadId, setSelectedThreadId] = useState(resolvedInitialThreadId);
7042
- const [forumView, setForumView] = useState<ForumViewState>(
7043
- resolvedInitialThreadId ? { mode: "thread", threadId: resolvedInitialThreadId } : { mode: "index" },
7044
- );
7045
- const [replyParentId, setReplyParentId] = useState("");
7046
- const [composeSummary, setComposeSummary] = useState("");
7047
- const [composeContent, setComposeContent] = useState("");
7048
- const [composeError, setComposeError] = useState<string | null>(null);
7049
- const [deletingMessageId, setDeletingMessageId] = useState("");
7050
-
7051
- const syncInsightsIndexUrl = useCallback((nextDomain: string, replace = true) => {
7052
- if (isDemo || forumView.mode === "thread") return;
7053
- syncBrowserNewsroomIndexUrl("insights", effectiveInsightsIndexFilters({ domain: nextDomain }), { replace });
7054
- }, [forumView.mode, isDemo]);
7055
-
7056
- useEffect(() => {
7057
- if (isDemo || forumView.mode === "thread") return;
7058
- syncInsightsIndexUrl(domainFilter, true);
7059
- }, [domainFilter, forumView.mode, isDemo, syncInsightsIndexUrl]);
7060
-
7061
- const refreshThreads = useCallback(async (options?: { append?: boolean; token?: string | null }) => {
7062
- if (isDemo) {
7063
- const demoThreads = messages
7064
- .filter((message) => message.messageKind === "insight")
7065
- .filter((message) => !domainFilter || message.messageDomain === domainFilter)
7066
- .map((message) => buildDemoInsightForumThread(message, messages));
7067
- setThreads(demoThreads);
7068
- setThreadsError(null);
7069
- setNextToken(null);
7070
- return;
7071
- }
7072
- setThreadsLoading(true);
7073
- try {
7074
- const result = await loadInsightForumThreads({
7075
- domain: domainFilter,
7076
- nextToken: options?.token ?? null,
7077
- includeMessages: true,
7078
- });
7079
- setThreads((current) => (options?.append ? [...current, ...result.threads] : result.threads));
7080
- setNextToken(result.nextToken);
7081
- setThreadsError(null);
7082
- setSelectedThreadId((current) => (current && result.threads.some((thread) => thread.id === current) ? current : current));
7083
- } catch (error) {
7084
- setThreads([]);
7085
- setThreadsError(error instanceof Error ? error.message : "Could not load insight threads.");
7086
- setNextToken(null);
7087
- } finally {
7088
- setThreadsLoading(false);
7089
- }
7090
- }, [domainFilter, isDemo, messages]);
7091
-
7092
- useEffect(() => {
7093
- void refreshThreads();
7094
- }, [refreshThreads]);
7095
-
7096
- useEffect(() => {
7097
- if (!resolvedInitialThreadId || threadsLoading) return;
7098
- if (!threads.some((thread) => thread.id === resolvedInitialThreadId)) return;
7099
- if (selectedThreadId === resolvedInitialThreadId && forumView.mode === "thread") return;
7100
- setSelectedThreadId(resolvedInitialThreadId);
7101
- setForumView({ mode: "thread", threadId: resolvedInitialThreadId });
7102
- }, [forumView.mode, resolvedInitialThreadId, selectedThreadId, threads, threadsLoading]);
7103
-
7104
- useEffect(() => {
7105
- const handlePopState = () => {
7106
- const route = readCurrentForumRoute();
7107
- if (route.surface === "insight_forum" && route.threadId) {
7108
- setSelectedThreadId(route.threadId);
7109
- setForumView({ mode: "thread", threadId: route.threadId });
7110
- return;
7111
- }
7112
- setSelectedThreadId("");
7113
- setForumView({ mode: "index" });
7114
- };
7115
- window.addEventListener("popstate", handlePopState);
7116
- return () => window.removeEventListener("popstate", handlePopState);
7117
- }, []);
7118
-
7119
- const selectedThread = threads.find((thread) => thread.id === selectedThreadId) ?? null;
7120
- const domainOptions = useMemo(() => {
7121
- const domains = new Set<string>();
7122
- for (const message of messages) {
7123
- if (message.messageKind === "insight" && message.messageDomain) domains.add(message.messageDomain);
7124
- }
7125
- for (const thread of threads) {
7126
- if (thread.sectionKey) domains.add(thread.sectionKey);
7127
- }
7128
- return sortedCountOptions(
7129
- Object.fromEntries([...domains].map((domain) => [domain, threads.filter((thread) => thread.sectionKey === domain).length])),
7130
- );
7131
- }, [messages, threads]);
7132
-
7133
- const openThread = useCallback((threadId: string, options?: { messageId?: string | null; replace?: boolean }) => {
7134
- setSelectedThreadId(threadId);
7135
- setForumView({ mode: "thread", threadId });
7136
- pushInsightThreadUrl(threadId, {
7137
- demo: isDemo,
7138
- messageId: options?.messageId,
7139
- replace: options?.replace,
7140
- });
7141
- }, [isDemo]);
7142
-
7143
- const closeThread = useCallback(() => {
7144
- setSelectedThreadId("");
7145
- setForumView({ mode: "index" });
7146
- pushInsightThreadUrl(null, { demo: isDemo, replace: true });
7147
- }, [isDemo]);
7148
-
7149
- const postThreadMessage = useCallback(async () => {
7150
- if (!selectedThread) return;
7151
- const content = composeContent.trim();
7152
- if (!content) {
7153
- setComposeError("Message content is required.");
7154
- return;
7155
- }
7156
- const summary = composeSummary.trim() || content.slice(0, 120);
7157
- setComposeError(null);
7158
- try {
7159
- await appendInsightThreadReplyRecord({
7160
- threadId: selectedThread.id,
7161
- summary,
7162
- content,
7163
- role: "human",
7164
- authorLabel: "human-editor",
7165
- parentMessageId: replyParentId || undefined,
7166
- });
7167
- setComposeSummary("");
7168
- setComposeContent("");
7169
- setReplyParentId("");
7170
- await refreshThreads();
7171
- const reloaded = await loadInsightForumThreadById(selectedThread.id, { includeMessages: true });
7172
- if (reloaded) {
7173
- setThreads((current) => current.map((thread) => (thread.id === reloaded.id ? reloaded : thread)));
7174
- }
7175
- setForumView({ mode: "thread", threadId: selectedThread.id });
7176
- } catch (error) {
7177
- setComposeError(error instanceof Error ? error.message : "Could not post thread message.");
7178
- }
7179
- }, [composeContent, composeSummary, refreshThreads, replyParentId, selectedThread]);
7180
-
7181
- const deleteThreadMessage = useCallback(async (threadId: string, messageId: string) => {
7182
- setDeletingMessageId(messageId);
7183
- try {
7184
- await deleteInsightThreadMessageRecord({ threadId, messageId });
7185
- if (replyParentId === messageId) setReplyParentId("");
7186
- await refreshThreads();
7187
- const reloaded = await loadInsightForumThreadById(threadId, { includeMessages: true });
7188
- if (reloaded) {
7189
- setThreads((current) => current.map((thread) => (thread.id === reloaded.id ? reloaded : thread)));
7190
- }
7191
- setComposeError(null);
7192
- } catch (error) {
7193
- setComposeError(error instanceof Error ? error.message : "Could not delete message.");
7194
- } finally {
7195
- setDeletingMessageId("");
7196
- }
7197
- }, [refreshThreads, replyParentId]);
7198
-
7199
- const insightKnowledgeQuery = useNewsroomKnowledgeContext(selectedThread ? {
7200
- anchor: { kind: "message", id: selectedThread.id, lineageId: selectedThread.id },
7201
- title: selectedThread.title ?? "Insight thread",
7202
- subtitle: selectedThread.sectionKey ?? "insight",
7203
- } : null);
7204
-
7205
- return (
7206
- <>
7207
- <NewsroomListDetailShell
7208
- sectionKey="insights"
7209
- canExpandDetail={forumView.mode === "thread" && Boolean(forumView.threadId)}
7210
- detailOpen={forumView.mode === "thread" && Boolean(forumView.threadId)}
7211
- selectionScrollKey={forumView.mode === "thread" ? forumView.threadId : null}
7212
- utilityActions={[insightKnowledgeQuery.action]}
7213
- lede={(
7214
- <NewsroomDeskSectionLede
7215
- headingId="insights-forum-title"
7216
- section="insights"
7217
- headline="Insights"
7218
- lede="Recent research insights in reverse chronological order. Each insight opens a forum thread for discussion and refinement."
7219
- controls={(
7220
- <div className="news-desk-reference-sort-controls" role="group" aria-label="Insight domain filter">
7221
- <span className="news-desk-reference-sort-controls__label">Domain</span>
7222
- <button
7223
- type="button"
7224
- data-active={!domainFilter || undefined}
7225
- onClick={() => {
7226
- setDomainFilter("");
7227
- syncInsightsIndexUrl("", true);
7228
- }}
7229
- >
7230
- All
7231
- </button>
7232
- <button
7233
- type="button"
7234
- data-active={domainFilter === "knowledge" || undefined}
7235
- onClick={() => {
7236
- setDomainFilter("knowledge");
7237
- syncInsightsIndexUrl("knowledge", true);
7238
- }}
7239
- >
7240
- Knowledge
7241
- </button>
7242
- <button
7243
- type="button"
7244
- data-active={domainFilter === "assignment_work" || undefined}
7245
- onClick={() => {
7246
- setDomainFilter("assignment_work");
7247
- syncInsightsIndexUrl("assignment_work", true);
7248
- }}
7249
- >
7250
- Assignment work
7251
- </button>
7252
- </div>
7253
- )}
7254
- />
7255
- )}
7256
- list={(
7257
- <section className="category-steering-section category-steering-section--lead" aria-label="Insight forum threads">
7258
- <ForumThreadIndex
7259
- threads={threads}
7260
- sections={[]}
7261
- isLoading={threadsLoading}
7262
- error={threadsError}
7263
- emptyLabel="No insight threads match this filter."
7264
- variant="insight"
7265
- toolbar={domainOptions.length > 2 ? (
7266
- <div className="news-desk-forum-toolbar">
7267
- <select
7268
- aria-label="Insight domain"
7269
- value={domainFilter}
7270
- onChange={(event) => {
7271
- setDomainFilter(event.target.value);
7272
- syncInsightsIndexUrl(event.target.value, true);
7273
- }}
7274
- >
7275
- <option value="">All domains ({threads.length})</option>
7276
- {domainOptions.map((option) => (
7277
- <option key={option.key} value={option.key}>{option.key} ({option.count})</option>
7278
- ))}
7279
- </select>
7280
- {nextToken ? (
7281
- <button type="button" disabled={threadsLoading} onClick={() => void refreshThreads({ append: true, token: nextToken })}>
7282
- Load more
7283
- </button>
7284
- ) : null}
7285
- </div>
7286
- ) : nextToken ? (
7287
- <div className="news-desk-forum-toolbar">
7288
- <button type="button" disabled={threadsLoading} onClick={() => void refreshThreads({ append: true, token: nextToken })}>
7289
- Load more
7290
- </button>
7291
- </div>
7292
- ) : null}
7293
- onOpenThread={(threadId) => openThread(threadId)}
7294
- />
7295
- </section>
7296
- )}
7297
- detail={forumView.mode === "thread" && selectedThread ? (
7298
- <ForumThreadView
7299
- thread={selectedThread}
7300
- sections={[]}
7301
- replyParentId={replyParentId}
7302
- composeSummary={composeSummary}
7303
- composeContent={composeContent}
7304
- composeError={composeError}
7305
- focusMessageId={forumMessageAnchorId}
7306
- isDemo={isDemo}
7307
- variant="insight"
7308
- onBack={closeThread}
7309
- onReplyTarget={(messageId) => {
7310
- setReplyParentId(messageId);
7311
- pushInsightThreadUrl(selectedThread.id, { demo: isDemo, messageId, replace: true });
7312
- }}
7313
- onClearReplyTarget={() => {
7314
- setReplyParentId("");
7315
- pushInsightThreadUrl(selectedThread.id, { demo: isDemo, replace: true });
7316
- }}
7317
- onSummaryChange={setComposeSummary}
7318
- onContentChange={setComposeContent}
7319
- onSubmit={() => void postThreadMessage()}
7320
- onDeleteMessage={(messageId) => {
7321
- if (!selectedThread || deletingMessageId === messageId) return;
7322
- void deleteThreadMessage(selectedThread.id, messageId);
7323
- }}
7324
- deletingMessageId={deletingMessageId}
7325
- />
7326
- ) : forumView.mode === "thread" ? (
7327
- <section className="category-steering-section">
7328
- <SectionHeader title="Thread Unavailable" detail="Insights" />
7329
- <EmptyRow label="That insight thread is not available. Return to the index or adjust filters." />
7330
- </section>
7331
- ) : null}
7332
- />
7333
- {insightKnowledgeQuery.dialog}
7334
- </>
7335
- );
7336
- }
7337
-
7338
- function buildDemoInsightForumThread(root: MessageRecord, allMessages: MessageRecord[]): ForumThreadWithMessages {
7339
- const replies = allMessages.filter((message) => (
7340
- message.threadId === root.id
7341
- && message.id !== root.id
7342
- && String(message.status || "active") === "active"
7343
- ));
7344
- const messages = [root, ...replies].sort(
7345
- (left, right) => Number(left.sequenceNumber ?? 0) - Number(right.sequenceNumber ?? 0),
7346
- );
7347
- const lastMessage = messages.at(-1) ?? root;
7348
- return {
7349
- id: root.id,
7350
- threadKind: "insight_forum",
7351
- status: "active",
7352
- title: root.summary ?? "Insight",
7353
- summary: root.messageDomain ?? null,
7354
- primaryAnchorKind: "message",
7355
- primaryAnchorId: root.id,
7356
- primaryAnchorLineageId: root.id,
7357
- primaryAnchorKey: `message#${root.id}`,
7358
- createdByLabel: root.authorLabel ?? null,
7359
- messageCount: messages.length,
7360
- lastMessageId: lastMessage.id,
7361
- lastMessageAt: lastMessage.updatedAt ?? lastMessage.createdAt,
7362
- metadata: metadataRecord(root.metadata),
7363
- createdAt: root.createdAt,
7364
- updatedAt: lastMessage.updatedAt ?? lastMessage.createdAt,
7365
- newsroomFeedKey: "messages",
7366
- messages,
7367
- scope: "insight",
7368
- sectionKey: root.messageDomain ?? null,
7369
- };
7370
- }
7371
-
7372
6913
  function MessagesDeskView({
7373
6914
  assignments,
7374
6915
  graph,
@@ -7975,7 +7516,6 @@ function ForumThreadIndex({
7975
7516
  emptyLabel,
7976
7517
  toolbar,
7977
7518
  composer,
7978
- variant = "edition",
7979
7519
  onOpenThread,
7980
7520
  }: {
7981
7521
  threads: ForumThreadWithMessages[];
@@ -7985,7 +7525,6 @@ function ForumThreadIndex({
7985
7525
  emptyLabel: string;
7986
7526
  toolbar?: ReactNode;
7987
7527
  composer?: ReactNode;
7988
- variant?: "edition" | "insight";
7989
7528
  onOpenThread: (threadId: string) => void;
7990
7529
  }) {
7991
7530
  return (
@@ -7997,7 +7536,7 @@ function ForumThreadIndex({
7997
7536
  <thead>
7998
7537
  <tr>
7999
7538
  <th>Topic</th>
8000
- <th>{variant === "insight" ? "Domain" : "Scope"}</th>
7539
+ <th>Scope</th>
8001
7540
  <th>Replies</th>
8002
7541
  <th>Last Post</th>
8003
7542
  </tr>
@@ -8010,11 +7549,9 @@ function ForumThreadIndex({
8010
7549
  );
8011
7550
  const lastMessage = sortedMessages.at(-1) ?? null;
8012
7551
  const replies = Math.max(activeMessages.length - 1, 0);
8013
- const scopeLabel = thread.scope === "insight"
8014
- ? (thread.sectionKey ?? thread.summary ?? "Insight")
8015
- : thread.scope === "edition"
8016
- ? "Edition"
8017
- : `Section: ${normalizeForumThreadSectionLabel(thread, sections)}`;
7552
+ const scopeLabel = thread.scope === "edition"
7553
+ ? "Edition"
7554
+ : `Section: ${normalizeForumThreadSectionLabel(thread, sections)}`;
8018
7555
  return (
8019
7556
  <tr key={thread.id}>
8020
7557
  <td>
@@ -8128,7 +7665,6 @@ function ForumThreadView({
8128
7665
  deletingMessageId,
8129
7666
  focusMessageId = null,
8130
7667
  isDemo = false,
8131
- variant = "edition",
8132
7668
  onBack,
8133
7669
  onReplyTarget,
8134
7670
  onDeleteMessage,
@@ -8146,7 +7682,6 @@ function ForumThreadView({
8146
7682
  deletingMessageId?: string;
8147
7683
  focusMessageId?: string | null;
8148
7684
  isDemo?: boolean;
8149
- variant?: "edition" | "insight";
8150
7685
  onBack: () => void;
8151
7686
  onReplyTarget: (messageId: string) => void;
8152
7687
  onDeleteMessage: (messageId: string) => void;
@@ -8159,9 +7694,7 @@ function ForumThreadView({
8159
7694
  (left, right) => Number(left.sequenceNumber ?? 0) - Number(right.sequenceNumber ?? 0),
8160
7695
  );
8161
7696
  const visibleCount = messages.length;
8162
- const threadShareUrl = variant === "insight"
8163
- ? buildInsightThreadUrl(thread.id, { demo: isDemo })
8164
- : buildForumThreadUrl(thread.id, { demo: isDemo });
7697
+ const threadShareUrl = buildForumThreadUrl(thread.id, { demo: isDemo });
8165
7698
 
8166
7699
  useEffect(() => {
8167
7700
  if (!focusMessageId || typeof window === "undefined") return;
@@ -8190,11 +7723,9 @@ function ForumThreadView({
8190
7723
  Back to Threads
8191
7724
  </button>
8192
7725
  <p className="story-label">
8193
- {thread.scope === "insight"
8194
- ? `Insight · ${thread.sectionKey ?? "discussion"}`
8195
- : thread.scope === "edition"
8196
- ? "Edition Thread"
8197
- : `Section Thread: ${normalizeForumThreadSectionLabel(thread, sections)}`}
7726
+ {thread.scope === "edition"
7727
+ ? "Edition Thread"
7728
+ : `Section Thread: ${normalizeForumThreadSectionLabel(thread, sections)}`}
8198
7729
  </p>
8199
7730
  <h3>{thread.title}</h3>
8200
7731
  </div>
@@ -8234,9 +7765,7 @@ function ForumThreadView({
8234
7765
  </DropdownMenuItem>
8235
7766
  <DropdownMenuItem
8236
7767
  onSelect={() => {
8237
- const url = variant === "insight"
8238
- ? buildInsightThreadUrl(thread.id, { messageId: message.id, demo: isDemo })
8239
- : buildForumThreadUrl(thread.id, { messageId: message.id, demo: isDemo });
7768
+ const url = buildForumThreadUrl(thread.id, { messageId: message.id, demo: isDemo });
8240
7769
  void copyShareLink(url);
8241
7770
  }}
8242
7771
  >
@@ -8539,7 +8068,7 @@ function NewsroomListDetailShell({
8539
8068
  list: ReactNode;
8540
8069
  onCloseDetail?: () => void;
8541
8070
  selectionScrollKey?: string | null;
8542
- sectionKey: "assignments" | "concepts" | "insights" | "messages" | "references" | "topics";
8071
+ sectionKey: "assignments" | "concepts" | "messages" | "references" | "topics";
8543
8072
  utilityActions?: NewsroomDetailAction[];
8544
8073
  }) {
8545
8074
  const [detailMode, setDetailMode] = useState<"split" | "full">("split");
@@ -9051,7 +8580,7 @@ function LibraryBigIcon() {
9051
8580
  );
9052
8581
  }
9053
8582
 
9054
- function InsightIcon() {
8583
+ function NoteIcon() {
9055
8584
  return (
9056
8585
  <svg
9057
8586
  aria-hidden="true"
@@ -9512,7 +9041,7 @@ function SemanticDetailPanel({
9512
9041
  }
9513
9042
 
9514
9043
  const messages = graph.messagesFor(selected.kind, selected.lineageId);
9515
- const insights = graph.insightsFor(selected.kind, selected.lineageId);
9044
+ const notes = graph.notesFor(selected.kind, selected.lineageId);
9516
9045
  const attachments = selected.kind === "reference" ? graph.attachmentsForReference(selected.lineageId) : [];
9517
9046
  const neighborGroups = graph.neighbors(selected.kind, selected.lineageId);
9518
9047
  const selectedReference = selected.kind === "reference" ? selected.record as ReferenceRecord : null;
@@ -9586,8 +9115,8 @@ function SemanticDetailPanel({
9586
9115
  ) : null}
9587
9116
  </div>
9588
9117
  ) : null}
9589
- {insights.length ? (
9590
- <InsightMessageBlock insights={insights} />
9118
+ {notes.length ? (
9119
+ <NoteMessageBlock notes={notes} />
9591
9120
  ) : null}
9592
9121
  {messages.length ? (
9593
9122
  <div className="news-desk-detail-block">
@@ -9791,11 +9320,11 @@ function useNewsroomKnowledgeContext(target: KnowledgeQueryTarget | null) {
9791
9320
  };
9792
9321
  }
9793
9322
 
9794
- function useNewsroomInsightComposer(
9795
- target: InsightTarget | null,
9323
+ function useNewsroomNoteComposer(
9324
+ target: NoteTarget | null,
9796
9325
  disabled: boolean,
9797
- onCreate: (target: InsightTarget, summary: string, body: string) => Promise<void>,
9798
- ): InsightComposerControl {
9326
+ onCreate: (target: NoteTarget, summary: string, body: string) => Promise<void>,
9327
+ ): NoteComposerControl {
9799
9328
  const [dialogOpen, setDialogOpen] = useState(false);
9800
9329
  const [summary, setSummary] = useState("");
9801
9330
  const [body, setBody] = useState("");
@@ -9804,7 +9333,7 @@ function useNewsroomInsightComposer(
9804
9333
  loading: false,
9805
9334
  targetKey: null,
9806
9335
  });
9807
- const targetKey = target ? insightTargetKey(target) : null;
9336
+ const targetKey = target ? noteTargetKey(target) : null;
9808
9337
 
9809
9338
  useEffect(() => {
9810
9339
  setDialogOpen(false);
@@ -9818,7 +9347,7 @@ function useNewsroomInsightComposer(
9818
9347
  const cleanSummary = summary.trim();
9819
9348
  const cleanBody = body.trim();
9820
9349
  if (!cleanSummary || !cleanBody) {
9821
- setState({ error: "Insight summary and body are required.", loading: false, targetKey });
9350
+ setState({ error: "Note summary and body are required.", loading: false, targetKey });
9822
9351
  return;
9823
9352
  }
9824
9353
  setState({ error: null, loading: true, targetKey });
@@ -9830,7 +9359,7 @@ function useNewsroomInsightComposer(
9830
9359
  setBody("");
9831
9360
  } catch (error) {
9832
9361
  setState({
9833
- error: error instanceof Error ? error.message : "Could not save insight.",
9362
+ error: error instanceof Error ? error.message : "Could not save note.",
9834
9363
  loading: false,
9835
9364
  targetKey,
9836
9365
  });
@@ -9842,15 +9371,15 @@ function useNewsroomInsightComposer(
9842
9371
 
9843
9372
  return {
9844
9373
  action: {
9845
- ariaLabel: "Add insight",
9374
+ ariaLabel: "Add note",
9846
9375
  disabled: disabled || !target || loading,
9847
- icon: <InsightIcon />,
9848
- key: "add-insight",
9849
- label: loading ? "Saving Insight" : "Add Insight",
9376
+ icon: <NoteIcon />,
9377
+ key: "add-note",
9378
+ label: loading ? "Saving Note" : "Add Note",
9850
9379
  onSelect: () => setDialogOpen(true),
9851
9380
  } satisfies NewsroomDetailAction,
9852
9381
  dialog: target ? (
9853
- <InsightComposerDialog
9382
+ <NoteComposerDialog
9854
9383
  body={body}
9855
9384
  disabled={disabled || loading}
9856
9385
  error={error}
@@ -10189,7 +9718,7 @@ function KnowledgeQueryDialog({
10189
9718
  );
10190
9719
  }
10191
9720
 
10192
- function InsightComposerDialog({
9721
+ function NoteComposerDialog({
10193
9722
  body,
10194
9723
  disabled,
10195
9724
  error,
@@ -10210,7 +9739,7 @@ function InsightComposerDialog({
10210
9739
  onSummaryChange: (value: string) => void;
10211
9740
  open: boolean;
10212
9741
  summary: string;
10213
- target: InsightTarget;
9742
+ target: NoteTarget;
10214
9743
  }) {
10215
9744
  useEffect(() => {
10216
9745
  if (!open) return;
@@ -10227,16 +9756,16 @@ function InsightComposerDialog({
10227
9756
  return (
10228
9757
  <div
10229
9758
  className="news-desk-modal"
10230
- data-news-desk-insight-modal
9759
+ data-news-desk-note-modal
10231
9760
  onMouseDown={(event) => {
10232
9761
  if (event.target === event.currentTarget && !disabled) onClose();
10233
9762
  }}
10234
9763
  >
10235
- <div className="news-desk-modal__dialog news-desk-modal__dialog--knowledge-query" role="dialog" aria-modal="true" aria-labelledby="insight-composer-title">
9764
+ <div className="news-desk-modal__dialog news-desk-modal__dialog--knowledge-query" role="dialog" aria-modal="true" aria-labelledby="note-composer-title">
10236
9765
  <header className="news-desk-modal__header">
10237
9766
  <div>
10238
- <p className="story-label">Insight</p>
10239
- <h3 id="insight-composer-title">{target.title}</h3>
9767
+ <p className="story-label">Note</p>
9768
+ <h3 id="note-composer-title">{target.title}</h3>
10240
9769
  <span>{target.kind} / {target.lineageId}</span>
10241
9770
  </div>
10242
9771
  <button type="button" disabled={disabled} onClick={onClose}>Close</button>
@@ -10251,7 +9780,7 @@ function InsightComposerDialog({
10251
9780
  />
10252
9781
  </label>
10253
9782
  <label>
10254
- <span>Insight Body</span>
9783
+ <span>Note Body</span>
10255
9784
  <textarea
10256
9785
  disabled={disabled}
10257
9786
  rows={8}
@@ -10264,7 +9793,7 @@ function InsightComposerDialog({
10264
9793
  <footer className="news-desk-modal__actions">
10265
9794
  <button type="button" disabled={disabled} onClick={onClose}>Cancel</button>
10266
9795
  <button type="button" className="news-desk-assignment-create-button" disabled={disabled || !summary.trim() || !body.trim()} onClick={onRun}>
10267
- {disabled ? "Saving" : "Save Insight"}
9796
+ {disabled ? "Saving" : "Save Note"}
10268
9797
  </button>
10269
9798
  </footer>
10270
9799
  </div>
@@ -11011,14 +10540,14 @@ function PayloadDetailBlock({
11011
10540
  );
11012
10541
  }
11013
10542
 
11014
- function InsightMessageBlock({ insights }: { insights: MessageRecord[] }) {
10543
+ function NoteMessageBlock({ notes }: { notes: MessageRecord[] }) {
11015
10544
  return (
11016
10545
  <div className="news-desk-detail-block">
11017
- <p className="story-label">Insights</p>
11018
- {insights.slice(0, 6).map((message) => (
10546
+ <p className="story-label">Notes</p>
10547
+ {notes.slice(0, 6).map((message) => (
11019
10548
  <div className="news-desk-detail-line" key={message.id}>
11020
10549
  <span>{message.authorLabel ?? "knowledge"}</span>
11021
- <strong>{message.summary ?? "Stored insight"}</strong>
10550
+ <strong>{message.summary ?? "Stored note"}</strong>
11022
10551
  </div>
11023
10552
  ))}
11024
10553
  </div>
@@ -11032,7 +10561,7 @@ function ReferenceDetailPanel({
11032
10561
  curation,
11033
10562
  disabled,
11034
10563
  graph,
11035
- onCreateInsight,
10564
+ onCreateNote,
11036
10565
  onMoveCorpus,
11037
10566
  onReview,
11038
10567
  onReviewTopicLabel,
@@ -11050,7 +10579,7 @@ function ReferenceDetailPanel({
11050
10579
  curation: ReferenceCurationDisplayState | null;
11051
10580
  disabled: boolean;
11052
10581
  graph: SemanticGraph;
11053
- onCreateInsight: (target: InsightTarget, summary: string, body: string) => Promise<void>;
10582
+ onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
11054
10583
  onMoveCorpus: (reference: ReferenceRecord, corpusId: string) => void;
11055
10584
  onReview: (action: ReferenceCurationAction) => void;
11056
10585
  onReviewTopicLabel: (input: { action: TopicLabelAction; category: CategorySteeringCategory; note?: string | null; reference: ReferenceRecord; sourceRelationId?: string | null }) => void;
@@ -11329,7 +10858,7 @@ function ReferenceDetailPanel({
11329
10858
 
11330
10859
  const lineageId = referenceLineageId;
11331
10860
  const messages = graph.messagesFor("reference", lineageId);
11332
- const insights = graph.insightsFor("reference", lineageId);
10861
+ const notes = graph.notesFor("reference", lineageId);
11333
10862
  const neighborGroups = graph.neighbors("reference", lineageId);
11334
10863
  const fallbackOutgoingCitationGroup = neighborGroups.find((group) => group.direction === "outgoing" && group.predicate === "cites") ?? null;
11335
10864
  const fallbackIncomingCitationGroup = neighborGroups.find((group) => group.direction === "incoming" && group.predicate === "cites") ?? null;
@@ -11448,10 +10977,10 @@ function ReferenceDetailPanel({
11448
10977
  reference={reference}
11449
10978
  semanticRelations={semanticRelations}
11450
10979
  />
11451
- <ReferenceInsightPanel
10980
+ <ReferenceNotePanel
11452
10981
  disabled={disabled}
11453
- insights={insights}
11454
- onCreateInsight={onCreateInsight}
10982
+ notes={notes}
10983
+ onCreateNote={onCreateNote}
11455
10984
  reference={reference}
11456
10985
  />
11457
10986
  <div data-news-desk-reference-metadata-expander>
@@ -12341,49 +11870,49 @@ function ReferenceCurationStatusPanel({ status }: { status: ReferenceCurationRun
12341
11870
  );
12342
11871
  }
12343
11872
 
12344
- function ReferenceInsightPanel({
11873
+ function ReferenceNotePanel({
12345
11874
  disabled,
12346
- insights,
12347
- onCreateInsight,
11875
+ notes,
11876
+ onCreateNote,
12348
11877
  reference,
12349
11878
  }: {
12350
11879
  disabled: boolean;
12351
- insights: MessageRecord[];
12352
- onCreateInsight: (target: InsightTarget, summary: string, body: string) => Promise<void>;
11880
+ notes: MessageRecord[];
11881
+ onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
12353
11882
  reference: ReferenceRecord;
12354
11883
  }) {
12355
11884
  const [composerOpen, setComposerOpen] = useState(false);
12356
11885
  const [body, setBody] = useState("");
12357
11886
  const [error, setError] = useState<string | null>(null);
12358
11887
  const [saving, setSaving] = useState(false);
12359
- const target = useMemo(() => insightTargetForReference(reference), [reference]);
12360
- const hasInsights = insights.length > 0;
11888
+ const target = useMemo(() => noteTargetForReference(reference), [reference]);
11889
+ const hasNotes = notes.length > 0;
12361
11890
 
12362
11891
  return (
12363
- <section className="news-desk-reference-workflow" data-reference-insight-workflow>
11892
+ <section className="news-desk-reference-workflow" data-reference-note-workflow>
12364
11893
  <header className="news-desk-reference-workflow__header">
12365
- <p className="story-label">Insights</p>
12366
- {!hasInsights && !composerOpen ? (
11894
+ <p className="story-label">Notes</p>
11895
+ {!hasNotes && !composerOpen ? (
12367
11896
  <button
12368
11897
  type="button"
12369
- data-news-desk-reference-insight-trigger
11898
+ data-news-desk-reference-note-trigger
12370
11899
  disabled={disabled || saving}
12371
11900
  onClick={() => {
12372
11901
  setComposerOpen(true);
12373
11902
  setError(null);
12374
11903
  }}
12375
11904
  >
12376
- Add insight
11905
+ Add note
12377
11906
  </button>
12378
11907
  ) : null}
12379
11908
  </header>
12380
- <div className="news-desk-reference-insight-shell" data-reference-insight-input>
12381
- {hasInsights ? (
12382
- <div className="news-desk-reference-insight-list" data-reference-insight-state>
12383
- {insights.map((message) => (
11909
+ <div className="news-desk-reference-note-shell" data-reference-note-input>
11910
+ {hasNotes ? (
11911
+ <div className="news-desk-reference-note-list" data-reference-note-state>
11912
+ {notes.map((message) => (
12384
11913
  <article className="news-desk-reference-topic-state-row" key={message.id}>
12385
11914
  <div className="news-desk-reference-topic-state-row__headline">
12386
- <strong>{message.summary ?? "Stored insight"}</strong>
11915
+ <strong>{message.summary ?? "Stored note"}</strong>
12387
11916
  <span>{message.authorLabel ?? message.source ?? "knowledge"}</span>
12388
11917
  </div>
12389
11918
  <div className="news-desk-reference-topic-state-row__meta">
@@ -12393,35 +11922,35 @@ function ReferenceInsightPanel({
12393
11922
  ))}
12394
11923
  </div>
12395
11924
  ) : null}
12396
- {hasInsights && !composerOpen ? (
11925
+ {hasNotes && !composerOpen ? (
12397
11926
  <button
12398
11927
  type="button"
12399
- data-news-desk-reference-insight-trigger
11928
+ data-news-desk-reference-note-trigger
12400
11929
  disabled={disabled || saving}
12401
11930
  onClick={() => {
12402
11931
  setComposerOpen(true);
12403
11932
  setError(null);
12404
11933
  }}
12405
11934
  >
12406
- Add insight
11935
+ Add note
12407
11936
  </button>
12408
11937
  ) : null}
12409
11938
  {composerOpen ? (
12410
11939
  <form
12411
- className="news-desk-reference-insight-form"
12412
- data-news-desk-reference-insight-form
11940
+ className="news-desk-reference-note-form"
11941
+ data-news-desk-reference-note-form
12413
11942
  onSubmit={(event) => {
12414
11943
  event.preventDefault();
12415
11944
  if (disabled || saving) return;
12416
11945
  const cleanBody = body.trim();
12417
11946
  if (!cleanBody) {
12418
- setError("Insight text is required.");
11947
+ setError("Note text is required.");
12419
11948
  return;
12420
11949
  }
12421
- const cleanSummary = summarizeInsightForStorage(cleanBody);
11950
+ const cleanSummary = summarizeNoteForStorage(cleanBody);
12422
11951
  setSaving(true);
12423
11952
  setError(null);
12424
- void onCreateInsight(target, cleanSummary, cleanBody)
11953
+ void onCreateNote(target, cleanSummary, cleanBody)
12425
11954
  .then(() => {
12426
11955
  setSaving(false);
12427
11956
  setBody("");
@@ -12429,12 +11958,12 @@ function ReferenceInsightPanel({
12429
11958
  })
12430
11959
  .catch((submitError) => {
12431
11960
  setSaving(false);
12432
- setError(submitError instanceof Error ? submitError.message : "Could not save insight.");
11961
+ setError(submitError instanceof Error ? submitError.message : "Could not save note.");
12433
11962
  });
12434
11963
  }}
12435
11964
  >
12436
11965
  <label className="news-desk-reference-curation-note">
12437
- <span>Insight</span>
11966
+ <span>Note</span>
12438
11967
  <textarea
12439
11968
  disabled={disabled || saving}
12440
11969
  value={body}
@@ -12442,9 +11971,9 @@ function ReferenceInsightPanel({
12442
11971
  />
12443
11972
  </label>
12444
11973
  {error ? <p className="news-desk-detail-copy">{error}</p> : null}
12445
- <div className="news-desk-reference-insight-form__actions">
11974
+ <div className="news-desk-reference-note-form__actions">
12446
11975
  <button type="submit" disabled={disabled || saving || !body.trim()}>
12447
- {saving ? "Saving" : "Save insight"}
11976
+ {saving ? "Saving" : "Save note"}
12448
11977
  </button>
12449
11978
  <button
12450
11979
  type="button"
@@ -12465,9 +11994,9 @@ function ReferenceInsightPanel({
12465
11994
  );
12466
11995
  }
12467
11996
 
12468
- function summarizeInsightForStorage(body: string): string {
11997
+ function summarizeNoteForStorage(body: string): string {
12469
11998
  const normalized = body.trim().replace(/\s+/g, " ");
12470
- if (!normalized) return "Insight";
11999
+ if (!normalized) return "Note";
12471
12000
  const firstSentenceMatch = normalized.match(/(.+?[.!?])(?:\s|$)/);
12472
12001
  const candidate = (firstSentenceMatch?.[1] ?? normalized).trim();
12473
12002
  if (candidate.length <= 120) return candidate;
@@ -13332,7 +12861,7 @@ function buildUiAnalysisReindexAssignmentPlan({
13332
12861
  return { assignment, event, relation, metadata };
13333
12862
  }
13334
12863
 
13335
- function insightTargetForReference(reference: ReferenceRecord): InsightTarget {
12864
+ function noteTargetForReference(reference: ReferenceRecord): NoteTarget {
13336
12865
  return {
13337
12866
  kind: "reference",
13338
12867
  id: reference.id,
@@ -13343,7 +12872,7 @@ function insightTargetForReference(reference: ReferenceRecord): InsightTarget {
13343
12872
  };
13344
12873
  }
13345
12874
 
13346
- function insightTargetForNewsroomSection(section: NewsroomSectionRecord): InsightTarget {
12875
+ function noteTargetForNewsroomSection(section: NewsroomSectionRecord): NoteTarget {
13347
12876
  return {
13348
12877
  kind: "newsroomSection",
13349
12878
  id: section.id,
@@ -13354,7 +12883,7 @@ function insightTargetForNewsroomSection(section: NewsroomSectionRecord): Insigh
13354
12883
  };
13355
12884
  }
13356
12885
 
13357
- function insightTargetForSemanticObject(object: SemanticObjectSummary): InsightTarget | null {
12886
+ function noteTargetForSemanticObject(object: SemanticObjectSummary): NoteTarget | null {
13358
12887
  if (object.kind !== "category" && object.kind !== "semanticNode") return null;
13359
12888
  return {
13360
12889
  kind: object.kind,
@@ -13366,21 +12895,21 @@ function insightTargetForSemanticObject(object: SemanticObjectSummary): InsightT
13366
12895
  };
13367
12896
  }
13368
12897
 
13369
- function insightTargetKey(target: InsightTarget): string {
12898
+ function noteTargetKey(target: NoteTarget): string {
13370
12899
  return `${target.kind}#${target.lineageId}`;
13371
12900
  }
13372
12901
 
13373
- function buildUiInsightRelation(message: MessageRecord, target: InsightTarget, now: string): SemanticRelationRecord {
12902
+ function buildUiNoteRelation(message: MessageRecord, target: NoteTarget, now: string): SemanticRelationRecord {
13374
12903
  const subjectStateKey = semanticStateKey("message", message.id);
13375
12904
  const objectStateKey = semanticStateKey(target.kind, target.lineageId);
13376
12905
  const subjectVersionKey = semanticVersionKey("message", message.id);
13377
12906
  const objectVersionKey = semanticVersionKey(target.kind, target.id);
13378
12907
  return {
13379
- id: `semantic-relation-${hashUiKey([subjectVersionKey, "insight_about", objectVersionKey])}`,
12908
+ id: `semantic-relation-${hashUiKey([subjectVersionKey, "note_about", objectVersionKey])}`,
13380
12909
  relationState: "current",
13381
- predicate: "insight_about",
13382
- relationTypeId: "semantic-relation-type-insight-about",
13383
- relationTypeKey: "insight_about",
12910
+ predicate: "note_about",
12911
+ relationTypeId: "semantic-relation-type-note-about",
12912
+ relationTypeKey: "note_about",
13384
12913
  relationDomain: "knowledge",
13385
12914
  subjectKind: "message",
13386
12915
  subjectId: message.id,
@@ -13393,7 +12922,7 @@ function buildUiInsightRelation(message: MessageRecord, target: InsightTarget, n
13393
12922
  subjectStateKey,
13394
12923
  objectStateKey,
13395
12924
  objectSubjectStateKey: `${objectStateKey}#message`,
13396
- predicateObjectStateKey: `insight_about#${objectStateKey}`,
12925
+ predicateObjectStateKey: `note_about#${objectStateKey}`,
13397
12926
  subjectVersionKey,
13398
12927
  objectVersionKey,
13399
12928
  score: null,
@@ -13698,7 +13227,7 @@ function signupPluginEnabled(): boolean {
13698
13227
  }
13699
13228
 
13700
13229
  function normalizeAdministrationPanel(value: string | null | undefined): AdministrationPanel {
13701
- if (value === "policies" || value === "sections" || value === "procedures") return value;
13230
+ if (value === "policies" || value === "sections" || value === "procedures" || value === "delegations") return value;
13702
13231
  if (value === "signups" && signupPluginEnabled()) return value;
13703
13232
  return "users";
13704
13233
  }
@@ -13890,10 +13419,10 @@ function compareDoctrineCategories(left: DoctrineCategory, right: DoctrineCatego
13890
13419
 
13891
13420
  function formatDeskSectionHeadline(section: NewsDeskTab): string {
13892
13421
  if (section === "search") return "Search";
13893
- if (section === "topics") return "Topics";
13422
+ if (section === "desks") return "Taxonomy";
13423
+ if (section === "topics") return "Topic proposals";
13894
13424
  if (section === "concepts") return "Concepts";
13895
13425
  if (section === "references") return "References";
13896
- if (section === "insights") return "Insights";
13897
13426
  if (section === "messages") return "Messages";
13898
13427
  if (section === "assignments") return "Assignments";
13899
13428
  if (section === "administration") return "Administration";
@@ -13902,13 +13431,13 @@ function formatDeskSectionHeadline(section: NewsDeskTab): string {
13902
13431
 
13903
13432
  function formatDeskSectionLede(section: NewsDeskTab): string {
13904
13433
  if (section === "search") return "Search the newsroom knowledge base with semantic and ontology context.";
13905
- if (section === "topics") return "Review and shape the subject areas the newsroom covers.";
13434
+ if (section === "desks") return "The desks and topic categories that organize coverage, with each desk's mission and policies.";
13435
+ if (section === "topics") return "Review suggested changes to the subject areas the newsroom covers.";
13906
13436
  if (section === "concepts") return "Browse people, organizations, places, and ideas found in the source material.";
13907
13437
  if (section === "references") return "Review source materials before they become usable evidence.";
13908
- if (section === "insights") return "Browse recent research insights and discuss each one in a forum thread to refine the synthesis.";
13909
13438
  if (section === "messages") return "Read notes, rationales, forum threads, curation decisions, and other work products from people and agents.";
13910
13439
  if (section === "assignments") return "Create, filter, claim, and complete newsroom work.";
13911
- if (section === "administration") return "Manage users, roles, doctrine, configurable newspaper sections, and newsroom procedures.";
13440
+ if (section === "administration") return "Manage users, roles, doctrine, and newsroom procedures.";
13912
13441
  return "";
13913
13442
  }
13914
13443
 
@@ -16537,21 +16066,21 @@ function assertReferenceQualityMutationSucceeded(
16537
16066
  return response.data;
16538
16067
  }
16539
16068
 
16540
- function assertReferenceInsightMutationSucceeded(
16541
- response: ReferenceInsightResponse,
16069
+ function assertReferenceNoteMutationSucceeded(
16070
+ response: ReferenceNoteResponse,
16542
16071
  referenceId: string,
16543
- ): NonNullable<ReferenceInsightResponse["data"]> {
16072
+ ): NonNullable<ReferenceNoteResponse["data"]> {
16544
16073
  if (response.errors?.length) {
16545
16074
  throw new Error(response.errors.map(formatGraphQLError).join("; "));
16546
16075
  }
16547
16076
  if (!response.data?.ok) {
16548
- throw new Error(`Reference insight was not saved for ${referenceId}.`);
16077
+ throw new Error(`Reference note was not saved for ${referenceId}.`);
16549
16078
  }
16550
16079
  if (response.data.referenceId && response.data.referenceId !== referenceId) {
16551
- throw new Error(`Reference insight response did not match ${referenceId}.`);
16080
+ throw new Error(`Reference note response did not match ${referenceId}.`);
16552
16081
  }
16553
16082
  if (!response.data.messageId || !response.data.relationId) {
16554
- throw new Error(`Reference insight saved without required audit relations for ${referenceId}.`);
16083
+ throw new Error(`Reference note saved without required audit relations for ${referenceId}.`);
16555
16084
  }
16556
16085
  return response.data;
16557
16086
  }