@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.
- package/amplify/data/resource.js +70 -3
- package/amplify/data/resource.ts +75 -3
- package/amplify/data/schema.js +67 -0
- package/amplify/data/schema.ts +72 -0
- package/amplify/functions/category-action/handler.ts +190 -20
- package/amplify/functions/console-chat-responder/src/main.rs +1 -1
- package/amplify/functions/content-actions/handler.py +23 -0
- package/amplify/functions/content-actions/requirements.txt +1 -1
- package/amplify/functions/content-actions/resource.js +1 -1
- package/amplify/functions/content-actions/resource.ts +1 -1
- package/amplify/functions/renditions/requirements.txt +1 -1
- package/amplify/functions/shared/curation-delegation.ts +67 -0
- package/amplify/functions/shared/reference-curation-metadata.ts +20 -0
- package/amplify/functions/shared/slack-events.ts +2 -2
- package/amplify/publication/renditions-function.js +1 -1
- package/amplify/publication/renditions-function.ts +1 -1
- package/app/api/local-preview/derive/route.ts +28 -0
- package/app/api/local-preview/media/route.ts +25 -0
- package/app/globals.css +28 -28
- package/app/layout.tsx +8 -5
- package/app/newsroom/[section]/[...selection]/page.tsx +0 -1
- package/app/newsroom/[section]/page.tsx +0 -1
- package/app/newsroom/insights/[threadId]/page.tsx +5 -16
- package/app/newsroom/insights/page.tsx +4 -12
- package/components/administration-delegations-panel.tsx +204 -0
- package/components/board-card-excerpts.tsx +45 -0
- package/components/curation-delegation-client.ts +81 -0
- package/components/markus-ir-preview.tsx +47 -2
- package/components/news-desk-page.tsx +0 -1
- package/components/news-desk-taxonomy-client.ts +32 -220
- package/components/newsroom-article-editor.tsx +135 -39
- package/components/newsroom-assignment-detail.tsx +35 -1
- package/components/newsroom-board-view.tsx +29 -7
- package/components/newsroom-newsprint-board.css +9 -2
- package/components/newsroom-ops-overview.tsx +27 -5
- package/components/newsroom-organizer-shell.tsx +25 -7
- package/components/newsroom-organizer.css +218 -4
- package/components/newsroom-references-view.tsx +56 -21
- package/components/priming-proof-page.tsx +17 -8
- package/components/prospectus-account-chooser.tsx +98 -0
- package/components/reference-cyclotron-status.tsx +5 -5
- package/components/reference-cyclotron-suggestion.tsx +143 -0
- package/components/reference-live-agreement.tsx +62 -0
- package/components/story-reporting-trail-client.ts +91 -0
- package/components/story-reporting-trail.tsx +115 -0
- package/components/topic-steering-workspace.tsx +203 -674
- package/components/ui/sheet.tsx +1 -0
- package/components/wiki-concepts-view.tsx +153 -0
- package/components/wiki-index-client.ts +51 -0
- package/lib/content-actions-client.ts +3 -0
- package/lib/content-actions-demo.ts +55 -3
- package/lib/content-item-lifecycle.ts +4 -2
- package/lib/content-source-context.ts +9 -1
- package/lib/curation-delegation-helpers.ts +59 -0
- package/lib/cyclotron-suggestions.ts +263 -0
- package/lib/front-matter-fields.ts +42 -0
- package/lib/graphql-content-repository.ts +42 -7
- package/lib/local-preview-seed.ts +174 -3
- package/lib/local-preview-tenant.ts +27 -0
- package/lib/newsroom-articles.ts +2 -0
- package/lib/newsroom-board.ts +4 -0
- package/lib/newsroom-forum-routes.ts +2 -43
- package/lib/newsroom-index-filters.ts +3 -40
- package/lib/newsroom-media-backend.ts +5 -0
- package/lib/newsroom-nav.ts +1 -3
- package/lib/newsroom-organizer-nav.ts +5 -12
- package/lib/newsroom-section-routing.ts +1 -1
- package/lib/newsroom-sections.ts +12 -0
- package/lib/papyrus-web-locations.ts +3 -24
- package/lib/priming-proof-report.ts +1 -0
- package/lib/prospectus-tenant.ts +22 -0
- package/lib/relevance-decisions.ts +29 -0
- package/lib/semantic-graph.ts +5 -5
- package/lib/site-brand.ts +9 -0
- package/lib/story-reporting-trail.ts +106 -0
- package/lib/tenant-keys.ts +32 -0
- package/lib/wiki.ts +56 -0
- package/package.json +21 -1
- 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" | "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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("
|
|
1902
|
-
if (!cleanBody) throw new Error("
|
|
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-
|
|
1933
|
+
const messageId = `message-note-${safeUiId(target.kind)}-${safeUiId(target.lineageId)}-${hashUiKey(subjectSeed)}`;
|
|
1912
1934
|
const message: MessageRecord = {
|
|
1913
1935
|
id: messageId,
|
|
1914
|
-
messageKind: "
|
|
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 =
|
|
1933
|
-
setActionState({ id: messageId, message: "
|
|
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: "
|
|
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.
|
|
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 =
|
|
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: "
|
|
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: "
|
|
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", "
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4216
|
+
onCreateNote,
|
|
4197
4217
|
section,
|
|
4198
4218
|
sectionId,
|
|
4199
4219
|
sectionsLoaded,
|
|
4200
4220
|
}: {
|
|
4201
4221
|
demo: boolean;
|
|
4202
4222
|
disabled: boolean;
|
|
4203
|
-
|
|
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
|
|
4209
|
-
const
|
|
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={
|
|
4239
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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,
|
|
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'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="
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
6403
|
-
resetKey:
|
|
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
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
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 === "
|
|
8014
|
-
?
|
|
8015
|
-
: thread
|
|
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 =
|
|
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 === "
|
|
8194
|
-
?
|
|
8195
|
-
: thread
|
|
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 =
|
|
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" | "
|
|
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
|
|
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
|
|
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
|
-
{
|
|
9590
|
-
<
|
|
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
|
|
9795
|
-
target:
|
|
9323
|
+
function useNewsroomNoteComposer(
|
|
9324
|
+
target: NoteTarget | null,
|
|
9796
9325
|
disabled: boolean,
|
|
9797
|
-
onCreate: (target:
|
|
9798
|
-
):
|
|
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 ?
|
|
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: "
|
|
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
|
|
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
|
|
9374
|
+
ariaLabel: "Add note",
|
|
9846
9375
|
disabled: disabled || !target || loading,
|
|
9847
|
-
icon: <
|
|
9848
|
-
key: "add-
|
|
9849
|
-
label: loading ? "Saving
|
|
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
|
-
<
|
|
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
|
|
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:
|
|
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-
|
|
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="
|
|
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">
|
|
10239
|
-
<h3 id="
|
|
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>
|
|
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
|
|
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
|
|
10543
|
+
function NoteMessageBlock({ notes }: { notes: MessageRecord[] }) {
|
|
11015
10544
|
return (
|
|
11016
10545
|
<div className="news-desk-detail-block">
|
|
11017
|
-
<p className="story-label">
|
|
11018
|
-
{
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
10980
|
+
<ReferenceNotePanel
|
|
11452
10981
|
disabled={disabled}
|
|
11453
|
-
|
|
11454
|
-
|
|
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
|
|
11873
|
+
function ReferenceNotePanel({
|
|
12345
11874
|
disabled,
|
|
12346
|
-
|
|
12347
|
-
|
|
11875
|
+
notes,
|
|
11876
|
+
onCreateNote,
|
|
12348
11877
|
reference,
|
|
12349
11878
|
}: {
|
|
12350
11879
|
disabled: boolean;
|
|
12351
|
-
|
|
12352
|
-
|
|
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(() =>
|
|
12360
|
-
const
|
|
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-
|
|
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">
|
|
12366
|
-
{!
|
|
11894
|
+
<p className="story-label">Notes</p>
|
|
11895
|
+
{!hasNotes && !composerOpen ? (
|
|
12367
11896
|
<button
|
|
12368
11897
|
type="button"
|
|
12369
|
-
data-news-desk-reference-
|
|
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
|
|
11905
|
+
Add note
|
|
12377
11906
|
</button>
|
|
12378
11907
|
) : null}
|
|
12379
11908
|
</header>
|
|
12380
|
-
<div className="news-desk-reference-
|
|
12381
|
-
{
|
|
12382
|
-
<div className="news-desk-reference-
|
|
12383
|
-
{
|
|
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
|
|
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
|
-
{
|
|
11925
|
+
{hasNotes && !composerOpen ? (
|
|
12397
11926
|
<button
|
|
12398
11927
|
type="button"
|
|
12399
|
-
data-news-desk-reference-
|
|
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
|
|
11935
|
+
Add note
|
|
12407
11936
|
</button>
|
|
12408
11937
|
) : null}
|
|
12409
11938
|
{composerOpen ? (
|
|
12410
11939
|
<form
|
|
12411
|
-
className="news-desk-reference-
|
|
12412
|
-
data-news-desk-reference-
|
|
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("
|
|
11947
|
+
setError("Note text is required.");
|
|
12419
11948
|
return;
|
|
12420
11949
|
}
|
|
12421
|
-
const cleanSummary =
|
|
11950
|
+
const cleanSummary = summarizeNoteForStorage(cleanBody);
|
|
12422
11951
|
setSaving(true);
|
|
12423
11952
|
setError(null);
|
|
12424
|
-
void
|
|
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
|
|
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>
|
|
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-
|
|
11974
|
+
<div className="news-desk-reference-note-form__actions">
|
|
12446
11975
|
<button type="submit" disabled={disabled || saving || !body.trim()}>
|
|
12447
|
-
{saving ? "Saving" : "Save
|
|
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
|
|
11997
|
+
function summarizeNoteForStorage(body: string): string {
|
|
12469
11998
|
const normalized = body.trim().replace(/\s+/g, " ");
|
|
12470
|
-
if (!normalized) return "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
12898
|
+
function noteTargetKey(target: NoteTarget): string {
|
|
13370
12899
|
return `${target.kind}#${target.lineageId}`;
|
|
13371
12900
|
}
|
|
13372
12901
|
|
|
13373
|
-
function
|
|
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, "
|
|
12908
|
+
id: `semantic-relation-${hashUiKey([subjectVersionKey, "note_about", objectVersionKey])}`,
|
|
13380
12909
|
relationState: "current",
|
|
13381
|
-
predicate: "
|
|
13382
|
-
relationTypeId: "semantic-relation-type-
|
|
13383
|
-
relationTypeKey: "
|
|
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: `
|
|
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 === "
|
|
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 === "
|
|
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,
|
|
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
|
|
16541
|
-
response:
|
|
16069
|
+
function assertReferenceNoteMutationSucceeded(
|
|
16070
|
+
response: ReferenceNoteResponse,
|
|
16542
16071
|
referenceId: string,
|
|
16543
|
-
): NonNullable<
|
|
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
|
|
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
|
|
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
|
|
16083
|
+
throw new Error(`Reference note saved without required audit relations for ${referenceId}.`);
|
|
16555
16084
|
}
|
|
16556
16085
|
return response.data;
|
|
16557
16086
|
}
|