@anthusai/papyrus 1.3.0 → 1.4.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/amplify/data/resource.js +134 -3
- package/amplify/data/resource.ts +145 -3
- package/amplify/data/schema.js +131 -0
- package/amplify/data/schema.ts +142 -0
- package/amplify/functions/category-action/handler.ts +272 -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/model-attachment-upload/handler.ts +1 -0
- package/amplify/functions/renditions/requirements.txt +1 -1
- package/amplify/functions/shared/curation-delegation-flow.ts +147 -0
- package/amplify/functions/shared/curation-delegation.ts +115 -0
- package/amplify/functions/shared/python-bundle.js +3 -3
- package/amplify/functions/shared/python-bundle.ts +3 -3
- package/amplify/functions/shared/reference-curation-metadata.ts +51 -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/[year]/[month]/[day]/[articleSlug]/page.tsx +2 -1
- package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -1
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +2 -1
- package/app/[year]/[month]/[day]/page.tsx +3 -0
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +2 -0
- package/app/api/archive/editions/route.ts +2 -0
- package/app/api/auth/prospectus/accounts/route.ts +9 -0
- package/app/api/local-preview/derive/route.ts +28 -0
- package/app/api/local-preview/media/route.ts +25 -0
- package/app/archive/page.tsx +3 -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 +8 -1
- package/app/newsroom/articles/page.tsx +10 -0
- package/app/newsroom/insights/[threadId]/page.tsx +5 -16
- package/app/newsroom/insights/page.tsx +4 -12
- package/app/newsroom/sections/[sectionId]/page.tsx +3 -0
- package/app/page.tsx +30 -1
- package/components/administration-delegations-panel.tsx +156 -0
- package/components/curation-delegation-client.ts +104 -0
- package/components/item-stream-view.tsx +63 -0
- package/components/markus-ir-preview.tsx +47 -2
- package/components/news-desk-client-provider.tsx +17 -2
- package/components/news-desk-page.tsx +0 -1
- package/components/news-desk-taxonomy-client.ts +154 -240
- package/components/newspaper.tsx +4 -4
- package/components/newsroom-article-editor.tsx +138 -40
- package/components/newsroom-assignment-detail.tsx +79 -0
- package/components/newsroom-assignments-view.tsx +1 -1
- package/components/newsroom-board-view.tsx +153 -43
- package/components/newsroom-client-shell.tsx +8 -1
- package/components/newsroom-newsprint-board.css +5 -46
- package/components/newsroom-ops-overview.tsx +27 -5
- package/components/newsroom-organizer-shell.tsx +36 -13
- package/components/newsroom-organizer.css +269 -13
- package/components/newsroom-references-view.tsx +118 -23
- package/components/newsroom-topics-view.tsx +1 -1
- package/components/priming-proof-page.tsx +17 -8
- package/components/prospectus-account-chooser.tsx +131 -0
- package/components/reference-cyclotron-priming.tsx +85 -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/reference-section-suggestion.tsx +104 -0
- package/components/story-reporting-trail-client.ts +91 -0
- package/components/story-reporting-trail.tsx +115 -0
- package/components/topic-steering-workspace.tsx +280 -679
- package/components/ui/sheet.tsx +10 -4
- package/components/use-paged-content-item-versions.ts +151 -0
- package/components/wiki-concepts-view.tsx +153 -0
- package/components/wiki-index-client.ts +51 -0
- package/infra/amplify-app-shell.js +17 -3
- package/infra/amplify-app-shell.ts +18 -3
- package/infra/site-config.js +6 -1
- package/infra/site-config.ts +7 -1
- package/lib/approved-priming-proof-reports.ts +6 -3
- package/lib/approved-priming-proofs/papyrus.json +90 -0
- package/lib/article-flow.ts +95 -0
- package/lib/article-frame-solver.ts +519 -0
- package/lib/blog-feature-solver.ts +1 -109
- package/lib/category-repository.ts +1 -1
- package/lib/chrome-text.ts +504 -0
- package/lib/content-actions-client.ts +3 -0
- package/lib/content-actions-demo.ts +55 -0
- package/lib/content-item-lifecycle.ts +34 -5
- package/lib/content-source-context.ts +9 -1
- package/lib/curation-delegation-helpers.ts +69 -0
- package/lib/cyclotron-suggestions.ts +263 -0
- package/lib/front-matter-fields.ts +42 -0
- package/lib/front-mosaic.ts +1133 -0
- package/lib/furniture.ts +385 -0
- package/lib/graphql-content-repository.ts +42 -7
- package/lib/item-stream.ts +47 -0
- package/lib/labels.ts +37 -0
- package/lib/layout-config.ts +237 -0
- package/lib/layout-plan.ts +40 -11
- package/lib/layout-scenarios.ts +21 -11
- package/lib/layout-types.ts +457 -0
- package/lib/layout-warnings.ts +8 -0
- package/lib/local-preview-section-fixtures.ts +72 -0
- package/lib/local-preview-seed.ts +286 -0
- package/lib/local-preview-tenant.ts +27 -0
- package/lib/local-preview.ts +3 -0
- package/lib/newspaper-layout.ts +17 -3410
- package/lib/newsroom-articles.ts +2 -0
- package/lib/newsroom-assignments.ts +14 -1
- package/lib/newsroom-board.ts +99 -47
- package/lib/newsroom-demo-dashboard.ts +1 -1
- 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 +9 -16
- 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/pretext-layout.ts +22 -0
- package/lib/priming-proof-report.ts +1 -0
- package/lib/prospectus-account-client.ts +90 -0
- package/lib/prospectus-sign-in-routes.ts +45 -0
- package/lib/prospectus-site-identity.ts +13 -1
- package/lib/prospectus-tenant.ts +22 -0
- package/lib/reader-static-params.ts +2 -1
- package/lib/relevance-decisions.ts +29 -0
- package/lib/relevance-status.ts +73 -0
- package/lib/reporting-story-budget.ts +2 -2
- package/lib/semantic-graph.ts +5 -5
- package/lib/site-brand.ts +28 -1
- package/lib/site-env.ts +5 -0
- package/lib/site-section-suggestions.ts +120 -0
- package/lib/site-stack.ts +1 -1
- package/lib/story-reporting-trail.ts +106 -0
- package/lib/tenant-keys.ts +32 -0
- package/lib/text-columns.ts +251 -0
- package/lib/wiki.ts +56 -0
- package/package.json +46 -3
- package/renderers/pretext/presentation-shell.tsx +1 -1
- package/renderers/pretext/rhythm-article-page.tsx +1 -18
- package/routes.manifest.json +34 -15
- package/app/newsroom/work/page.tsx +0 -10
|
@@ -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,10 @@ import {
|
|
|
57
55
|
type KnowledgeQueryResponse,
|
|
58
56
|
type NewsroomRecordPage,
|
|
59
57
|
loadCyclotronStatus,
|
|
58
|
+
loadCyclotronShadowAgreement,
|
|
59
|
+
loadSiteSectionAgreement,
|
|
60
|
+
requestCyclotronPriming,
|
|
61
|
+
loadCyclotronPrimingRequests,
|
|
60
62
|
requestCyclotronReviewRate,
|
|
61
63
|
} from "./news-desk-taxonomy-client";
|
|
62
64
|
import { listConsoleThreads } from "../lib/console-chat-client";
|
|
@@ -66,11 +68,8 @@ import {
|
|
|
66
68
|
effectiveReferencesIndexFilters,
|
|
67
69
|
normalizeReferenceIndexOrder,
|
|
68
70
|
readAssignmentsIndexFilters,
|
|
69
|
-
readInsightsIndexFilters,
|
|
70
71
|
readMessagesIndexFilters,
|
|
71
72
|
readReferencesIndexFilters,
|
|
72
|
-
effectiveInsightsIndexFilters,
|
|
73
|
-
parseInsightThreadIdFromNewsroomPathname,
|
|
74
73
|
referencesStatusFromUrl,
|
|
75
74
|
referencesStatusToUrl,
|
|
76
75
|
type ReferenceIndexOrder,
|
|
@@ -160,8 +159,19 @@ import {
|
|
|
160
159
|
REFERENCE_REJECTION_REASON_CODES,
|
|
161
160
|
referenceCurationStatusForAction,
|
|
162
161
|
} from "../lib/reference-policy";
|
|
162
|
+
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
163
163
|
import { currentRelevanceDecisions } from "../lib/relevance-decisions";
|
|
164
|
-
import {
|
|
164
|
+
import { delegatedCurationsByReference, referencesWithHumanLatestCuration, type ShadowAgreement } from "../lib/cyclotron-suggestions";
|
|
165
|
+
import { createLocalPreviewCyclotronFixtures } from "../lib/local-preview-seed";
|
|
166
|
+
import { createLocalPreviewSectionFixtures } from "../lib/local-preview-section-fixtures";
|
|
167
|
+
import { currentSectionSuggestions, siteCyclotronIdOf, type SiteSectionAgreement } from "../lib/site-section-suggestions";
|
|
168
|
+
import {
|
|
169
|
+
buildPrimingRequest,
|
|
170
|
+
buildReviewRateRequest,
|
|
171
|
+
DEFAULT_RELEVANCE_CYCLOTRON,
|
|
172
|
+
DEMO_CYCLOTRON_STATUS,
|
|
173
|
+
type PrimingRequestView,
|
|
174
|
+
} from "../lib/relevance-status";
|
|
165
175
|
import type { CyclotronStatus } from "cyclotron/cyclotron-status";
|
|
166
176
|
import {
|
|
167
177
|
referenceDisplaySummary,
|
|
@@ -193,12 +203,10 @@ import {
|
|
|
193
203
|
} from "../lib/reporting-story-budget";
|
|
194
204
|
import {
|
|
195
205
|
buildForumThreadUrl,
|
|
196
|
-
buildInsightThreadUrl,
|
|
197
206
|
getForumMessageAnchorId,
|
|
198
207
|
isForumThreadId,
|
|
199
208
|
parseForumMessageAnchorFromHash,
|
|
200
209
|
pushForumThreadUrl,
|
|
201
|
-
pushInsightThreadUrl,
|
|
202
210
|
readCurrentForumRoute,
|
|
203
211
|
} from "../lib/newsroom-forum-routes";
|
|
204
212
|
|
|
@@ -238,8 +246,8 @@ type AssignmentAction = "claim" | "release" | "complete" | "cancel" | "reopen" |
|
|
|
238
246
|
type ReportingPacketReviewDecision = "select" | "merge" | "brief" | "hold" | "kill";
|
|
239
247
|
type AssignmentDeskViewMode = "queue" | "budget";
|
|
240
248
|
type UserRoleAction = "grant" | "revoke";
|
|
241
|
-
type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "signups";
|
|
242
|
-
export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "
|
|
249
|
+
type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "delegations" | "signups";
|
|
250
|
+
export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "messages" | "assignments" | "administration" | "search";
|
|
243
251
|
type LexicalRuleScope = "publication" | "corpus" | "classifier" | "category";
|
|
244
252
|
type AnalysisReindexMode = "online-update" | "classifier-retrain" | "scoped-topic-rebuild" | "entity-graph-rebuild" | "generated-analysis-rebuild";
|
|
245
253
|
type ReferenceProcessingStatus = "created" | "processable" | "processed" | "blocked";
|
|
@@ -316,7 +324,7 @@ type NewsroomDetailAction = {
|
|
|
316
324
|
onSelect: () => void;
|
|
317
325
|
};
|
|
318
326
|
|
|
319
|
-
type
|
|
327
|
+
type NoteTarget = {
|
|
320
328
|
kind: "assignment" | "category" | "item" | "newsroomSection" | "reference" | "semanticNode";
|
|
321
329
|
id: string;
|
|
322
330
|
lineageId: string;
|
|
@@ -325,7 +333,7 @@ type InsightTarget = {
|
|
|
325
333
|
versionNumber?: number | null;
|
|
326
334
|
};
|
|
327
335
|
|
|
328
|
-
type
|
|
336
|
+
type NoteComposerControl = {
|
|
329
337
|
action: NewsroomDetailAction;
|
|
330
338
|
dialog: ReactNode;
|
|
331
339
|
error: string | null;
|
|
@@ -452,7 +460,7 @@ type ReferenceQualityResponse = {
|
|
|
452
460
|
errors?: Array<{ message?: string | null } | string | null> | null;
|
|
453
461
|
};
|
|
454
462
|
|
|
455
|
-
type
|
|
463
|
+
type ReferenceNoteResponse = {
|
|
456
464
|
data?: {
|
|
457
465
|
ok?: boolean | null;
|
|
458
466
|
referenceId?: string | null;
|
|
@@ -784,11 +792,43 @@ function NewsDeskDashboard({
|
|
|
784
792
|
const [referenceAttachments, setReferenceAttachments] = useState(dashboard.referenceAttachments);
|
|
785
793
|
const [messages, setMessages] = useState(dashboard.messages);
|
|
786
794
|
const [semanticRelations, setSemanticRelations] = useState(dashboard.semanticRelations);
|
|
787
|
-
const
|
|
795
|
+
const cyclotronPreviewFixtures = useMemo(
|
|
796
|
+
() => (isLocalPreviewEnabled() ? createLocalPreviewCyclotronFixtures(references) : null),
|
|
797
|
+
[references],
|
|
798
|
+
);
|
|
799
|
+
const relevanceDecisions = useMemo(
|
|
800
|
+
() => currentRelevanceDecisions(cyclotronPreviewFixtures ? [...semanticRelations, ...cyclotronPreviewFixtures.relations] : semanticRelations),
|
|
801
|
+
[cyclotronPreviewFixtures, semanticRelations],
|
|
802
|
+
);
|
|
803
|
+
const delegatedCurations = useMemo(
|
|
804
|
+
() => delegatedCurationsByReference(cyclotronPreviewFixtures ? [...messages, ...cyclotronPreviewFixtures.messages] : messages),
|
|
805
|
+
[cyclotronPreviewFixtures, messages],
|
|
806
|
+
);
|
|
807
|
+
const humanCuratedReferenceLineageIds = useMemo(
|
|
808
|
+
() => referencesWithHumanLatestCuration(cyclotronPreviewFixtures ? [...messages, ...cyclotronPreviewFixtures.messages] : messages),
|
|
809
|
+
[cyclotronPreviewFixtures, messages],
|
|
810
|
+
);
|
|
788
811
|
const relevanceCyclotronId = useMemo(
|
|
789
812
|
() => [...relevanceDecisions.values()].find((decision) => decision.cyclotronId)?.cyclotronId ?? null,
|
|
790
813
|
[relevanceDecisions],
|
|
791
814
|
);
|
|
815
|
+
const primingCyclotronId = relevanceCyclotronId ?? DEFAULT_RELEVANCE_CYCLOTRON.id;
|
|
816
|
+
const primingLabels = useMemo(
|
|
817
|
+
() => [...relevanceDecisions.values()].find((decision) => decision.classes.length)?.classes
|
|
818
|
+
?? DEFAULT_RELEVANCE_CYCLOTRON.labels,
|
|
819
|
+
[relevanceDecisions],
|
|
820
|
+
);
|
|
821
|
+
const [primingRequests, setPrimingRequests] = useState<PrimingRequestView[]>([]);
|
|
822
|
+
useEffect(() => {
|
|
823
|
+
if (dashboard.isDemo) return;
|
|
824
|
+
let cancelled = false;
|
|
825
|
+
const refresh = () => loadCyclotronPrimingRequests(primingCyclotronId)
|
|
826
|
+
.then((views) => { if (!cancelled) setPrimingRequests(views); })
|
|
827
|
+
.catch(() => { if (!cancelled) setPrimingRequests([]); });
|
|
828
|
+
void refresh();
|
|
829
|
+
const timer = window.setInterval(() => { void refresh(); }, 30_000);
|
|
830
|
+
return () => { cancelled = true; window.clearInterval(timer); };
|
|
831
|
+
}, [dashboard.isDemo, primingCyclotronId]);
|
|
792
832
|
const [cyclotronStatus, setCyclotronStatus] = useState<CyclotronStatus | null>(null);
|
|
793
833
|
useEffect(() => {
|
|
794
834
|
if (!relevanceCyclotronId) return;
|
|
@@ -802,6 +842,19 @@ function NewsDeskDashboard({
|
|
|
802
842
|
.catch(() => { if (!cancelled) setCyclotronStatus(null); });
|
|
803
843
|
return () => { cancelled = true; };
|
|
804
844
|
}, [dashboard.isDemo, relevanceCyclotronId]);
|
|
845
|
+
const [shadowAgreement, setShadowAgreement] = useState<ShadowAgreement | null>(null);
|
|
846
|
+
useEffect(() => {
|
|
847
|
+
if (cyclotronPreviewFixtures) {
|
|
848
|
+
setShadowAgreement(cyclotronPreviewFixtures.shadowAgreement);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (!relevanceCyclotronId || dashboard.isDemo) return;
|
|
852
|
+
let cancelled = false;
|
|
853
|
+
loadCyclotronShadowAgreement(relevanceCyclotronId)
|
|
854
|
+
.then((loaded) => { if (!cancelled) setShadowAgreement(loaded); })
|
|
855
|
+
.catch(() => { if (!cancelled) setShadowAgreement(null); });
|
|
856
|
+
return () => { cancelled = true; };
|
|
857
|
+
}, [cyclotronPreviewFixtures, dashboard.isDemo, relevanceCyclotronId]);
|
|
805
858
|
const [semanticNodes, setSemanticNodes] = useState(dashboard.semanticNodes);
|
|
806
859
|
const [assignments, setAssignments] = useState(dashboard.assignments);
|
|
807
860
|
const [assignmentEvents, setAssignmentEvents] = useState(dashboard.assignmentEvents);
|
|
@@ -812,6 +865,35 @@ function NewsDeskDashboard({
|
|
|
812
865
|
[dashboard.newsroomSections],
|
|
813
866
|
);
|
|
814
867
|
const [newsroomSections, setNewsroomSections] = useState(fallbackNewsroomSections);
|
|
868
|
+
const sectionChoices = useMemo(
|
|
869
|
+
() => newsroomSections
|
|
870
|
+
.filter((section) => section.enabled !== false && section.enabledStatus !== "disabled")
|
|
871
|
+
.sort((left, right) => left.sortOrder - right.sortOrder)
|
|
872
|
+
.map((section) => ({ id: section.id, title: section.title })),
|
|
873
|
+
[newsroomSections],
|
|
874
|
+
);
|
|
875
|
+
const sectionPreviewFixtures = useMemo(
|
|
876
|
+
() => (isLocalPreviewEnabled() ? createLocalPreviewSectionFixtures(references, sectionChoices.map((section) => section.id)) : null),
|
|
877
|
+
[references, sectionChoices],
|
|
878
|
+
);
|
|
879
|
+
const sectionSuggestions = useMemo(
|
|
880
|
+
() => currentSectionSuggestions(sectionPreviewFixtures ? [...semanticRelations, ...sectionPreviewFixtures.relations] : semanticRelations),
|
|
881
|
+
[sectionPreviewFixtures, semanticRelations],
|
|
882
|
+
);
|
|
883
|
+
const siteCyclotronId = useMemo(() => siteCyclotronIdOf(sectionSuggestions), [sectionSuggestions]);
|
|
884
|
+
const [siteSectionAgreement, setSiteSectionAgreement] = useState<SiteSectionAgreement | null>(null);
|
|
885
|
+
useEffect(() => {
|
|
886
|
+
if (sectionPreviewFixtures) {
|
|
887
|
+
setSiteSectionAgreement(sectionPreviewFixtures.agreement);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (!siteCyclotronId || dashboard.isDemo) return;
|
|
891
|
+
let cancelled = false;
|
|
892
|
+
loadSiteSectionAgreement(siteCyclotronId)
|
|
893
|
+
.then((loaded) => { if (!cancelled) setSiteSectionAgreement(loaded); })
|
|
894
|
+
.catch(() => { if (!cancelled) setSiteSectionAgreement(null); });
|
|
895
|
+
return () => { cancelled = true; };
|
|
896
|
+
}, [dashboard.isDemo, sectionPreviewFixtures, siteCyclotronId]);
|
|
815
897
|
const [procedureDefinitions, setProcedureDefinitions] = useState(dashboard.procedureDefinitions ?? []);
|
|
816
898
|
const [procedureVersions, setProcedureVersions] = useState(dashboard.procedureVersions ?? []);
|
|
817
899
|
const [procedureRuns, setProcedureRuns] = useState(dashboard.procedureRuns ?? []);
|
|
@@ -902,9 +984,6 @@ function NewsDeskDashboard({
|
|
|
902
984
|
overview: 0,
|
|
903
985
|
desks: summaryCountFromRecord(summary, "categories"),
|
|
904
986
|
messages: summaryCountFromRecord(summary, "messages"),
|
|
905
|
-
insights: summary?.facets?.messages?.byKind?.insight
|
|
906
|
-
?? summary?.messageKindCounts?.insight
|
|
907
|
-
?? null,
|
|
908
987
|
assignments: summaryCountFromRecord(summary, "assignments"),
|
|
909
988
|
references: summaryCountFromRecord(summary, "references")
|
|
910
989
|
?? (hasHydratedReferences ? references.length : null),
|
|
@@ -1539,6 +1618,7 @@ function NewsDeskDashboard({
|
|
|
1539
1618
|
note?: string,
|
|
1540
1619
|
reasonCode?: ReferenceRejectionReasonCode | null,
|
|
1541
1620
|
relevance?: { decisionRelationId: string; shareable: boolean },
|
|
1621
|
+
sectionId?: string | null,
|
|
1542
1622
|
) {
|
|
1543
1623
|
const nextStatus = referenceCurationStatusForAction(action);
|
|
1544
1624
|
setActionState({ id: reference.id, message: `${action} pending`, tone: "pending" });
|
|
@@ -1568,6 +1648,7 @@ function NewsDeskDashboard({
|
|
|
1568
1648
|
curationStatus: nextStatus,
|
|
1569
1649
|
reasonCode: action === "reject" ? reasonCode ?? null : null,
|
|
1570
1650
|
...(relevance ? { decisionRelationId: relevance.decisionRelationId, shareable: relevance.shareable } : {}),
|
|
1651
|
+
...(action === "accept" && sectionId ? { sectionId } : {}),
|
|
1571
1652
|
},
|
|
1572
1653
|
}, ...current]);
|
|
1573
1654
|
setSemanticRelations((current) => [{
|
|
@@ -1610,6 +1691,7 @@ function NewsDeskDashboard({
|
|
|
1610
1691
|
reasonCode: action === "reject" ? reasonCode ?? undefined : undefined,
|
|
1611
1692
|
decisionRelationId: relevance?.decisionRelationId,
|
|
1612
1693
|
shareable: relevance ? relevance.shareable : undefined,
|
|
1694
|
+
sectionId: action === "accept" ? sectionId ?? undefined : undefined,
|
|
1613
1695
|
},
|
|
1614
1696
|
{ authMode: USER_POOL_AUTH_MODE },
|
|
1615
1697
|
);
|
|
@@ -1870,11 +1952,11 @@ function NewsDeskDashboard({
|
|
|
1870
1952
|
});
|
|
1871
1953
|
}
|
|
1872
1954
|
|
|
1873
|
-
async function
|
|
1955
|
+
async function createNote(target: NoteTarget, summary: string, body: string): Promise<void> {
|
|
1874
1956
|
const cleanSummary = summary.trim();
|
|
1875
1957
|
const cleanBody = body.trim();
|
|
1876
|
-
if (!cleanSummary) throw new Error("
|
|
1877
|
-
if (!cleanBody) throw new Error("
|
|
1958
|
+
if (!cleanSummary) throw new Error("Note summary is required.");
|
|
1959
|
+
if (!cleanBody) throw new Error("Note body is required.");
|
|
1878
1960
|
const now = new Date().toISOString();
|
|
1879
1961
|
const subjectSeed = [
|
|
1880
1962
|
target.kind,
|
|
@@ -1883,10 +1965,10 @@ function NewsDeskDashboard({
|
|
|
1883
1965
|
cleanBody,
|
|
1884
1966
|
now,
|
|
1885
1967
|
];
|
|
1886
|
-
const messageId = `message-
|
|
1968
|
+
const messageId = `message-note-${safeUiId(target.kind)}-${safeUiId(target.lineageId)}-${hashUiKey(subjectSeed)}`;
|
|
1887
1969
|
const message: MessageRecord = {
|
|
1888
1970
|
id: messageId,
|
|
1889
|
-
messageKind: "
|
|
1971
|
+
messageKind: "note",
|
|
1890
1972
|
messageDomain: "knowledge",
|
|
1891
1973
|
status: "active",
|
|
1892
1974
|
summary: cleanSummary,
|
|
@@ -1904,18 +1986,18 @@ function NewsDeskDashboard({
|
|
|
1904
1986
|
targetLineageId: target.lineageId,
|
|
1905
1987
|
},
|
|
1906
1988
|
};
|
|
1907
|
-
const relation =
|
|
1908
|
-
setActionState({ id: messageId, message: "
|
|
1989
|
+
const relation = buildUiNoteRelation(message, target, now);
|
|
1990
|
+
setActionState({ id: messageId, message: "note pending", tone: "pending" });
|
|
1909
1991
|
|
|
1910
1992
|
if (dashboard.isDemo) {
|
|
1911
1993
|
setMessages((current) => [message, ...current.filter((entry) => entry.id !== message.id)]);
|
|
1912
1994
|
setSemanticRelations((current) => [relation, ...current.filter((entry) => entry.id !== relation.id)]);
|
|
1913
|
-
setActionState({ id: messageId, message: "
|
|
1995
|
+
setActionState({ id: messageId, message: "note saved", tone: "ok" });
|
|
1914
1996
|
return;
|
|
1915
1997
|
}
|
|
1916
1998
|
|
|
1917
1999
|
if (target.kind === "reference") {
|
|
1918
|
-
const response = await dataClient.mutations.
|
|
2000
|
+
const response = await dataClient.mutations.createReferenceNote(
|
|
1919
2001
|
{
|
|
1920
2002
|
referenceId: target.id,
|
|
1921
2003
|
summary: cleanSummary,
|
|
@@ -1924,7 +2006,7 @@ function NewsDeskDashboard({
|
|
|
1924
2006
|
},
|
|
1925
2007
|
{ authMode: USER_POOL_AUTH_MODE },
|
|
1926
2008
|
);
|
|
1927
|
-
const created =
|
|
2009
|
+
const created = assertReferenceNoteMutationSucceeded(response as ReferenceNoteResponse, target.id);
|
|
1928
2010
|
const persistedMessageId = created.messageId ?? messageId;
|
|
1929
2011
|
const persistedRelationId = created.relationId ?? relation.id;
|
|
1930
2012
|
const persistedMessage: MessageRecord = {
|
|
@@ -1941,7 +2023,7 @@ function NewsDeskDashboard({
|
|
|
1941
2023
|
};
|
|
1942
2024
|
setMessages((current) => [persistedMessage, ...current.filter((entry) => entry.id !== persistedMessage.id)]);
|
|
1943
2025
|
setSemanticRelations((current) => [persistedRelation, ...current.filter((entry) => entry.id !== persistedRelation.id)]);
|
|
1944
|
-
setActionState({ id: persistedMessageId, message: "
|
|
2026
|
+
setActionState({ id: persistedMessageId, message: "note saved", tone: "ok" });
|
|
1945
2027
|
return;
|
|
1946
2028
|
}
|
|
1947
2029
|
|
|
@@ -1979,7 +2061,7 @@ function NewsDeskDashboard({
|
|
|
1979
2061
|
assertNoGraphQLErrors(relationResponse.errors);
|
|
1980
2062
|
setMessages((current) => [message, ...current.filter((entry) => entry.id !== message.id)]);
|
|
1981
2063
|
setSemanticRelations((current) => [relation, ...current.filter((entry) => entry.id !== relation.id)]);
|
|
1982
|
-
setActionState({ id: messageId, message: "
|
|
2064
|
+
setActionState({ id: messageId, message: "note saved", tone: "ok" });
|
|
1983
2065
|
}
|
|
1984
2066
|
|
|
1985
2067
|
function createLexicalSteeringRule(draft: LexicalRuleDraft) {
|
|
@@ -3048,7 +3130,7 @@ function NewsDeskDashboard({
|
|
|
3048
3130
|
|
|
3049
3131
|
const shellTabCounts = useMemo(() => {
|
|
3050
3132
|
const entries: Partial<Record<string, NewsroomNavCount>> = {};
|
|
3051
|
-
for (const tab of ["overview", "messages", "
|
|
3133
|
+
for (const tab of ["overview", "messages", "assignments", "references", "topics", "concepts", "administration"] as NewsDeskTab[]) {
|
|
3052
3134
|
if (tab === "administration") continue;
|
|
3053
3135
|
entries[tab] = {
|
|
3054
3136
|
count: tabCounts[tab],
|
|
@@ -3062,7 +3144,9 @@ function NewsDeskDashboard({
|
|
|
3062
3144
|
? mastheadTitle
|
|
3063
3145
|
: activeTab === "overview"
|
|
3064
3146
|
? "Overview"
|
|
3065
|
-
:
|
|
3147
|
+
: activeTab === "administration" && administrationPanel === "sections"
|
|
3148
|
+
? "Sections"
|
|
3149
|
+
: formatDeskSectionHeadline(activeTab);
|
|
3066
3150
|
|
|
3067
3151
|
return (
|
|
3068
3152
|
<>
|
|
@@ -3099,8 +3183,8 @@ function NewsDeskDashboard({
|
|
|
3099
3183
|
>
|
|
3100
3184
|
{activeTab !== "overview" && activeTab !== "assignments" && activeTab !== "messages" && activeTab !== "references" && activeTab !== "topics" && activeTab !== "concepts" && activeTab !== "search" ? (
|
|
3101
3185
|
<NewsroomOpsSectionIntro
|
|
3102
|
-
description={formatDeskSectionLede(activeTab)}
|
|
3103
|
-
title={formatDeskSectionHeadline(activeTab)}
|
|
3186
|
+
description={activeTab === "administration" && administrationPanel === "sections" ? "Organize the publication's sections and the ontology that classifies its coverage." : formatDeskSectionLede(activeTab)}
|
|
3187
|
+
title={activeTab === "administration" && administrationPanel === "sections" ? "Sections" : formatDeskSectionHeadline(activeTab)}
|
|
3104
3188
|
/>
|
|
3105
3189
|
) : null}
|
|
3106
3190
|
|
|
@@ -3120,7 +3204,7 @@ function NewsDeskDashboard({
|
|
|
3120
3204
|
<DeepNewsroomSectionView
|
|
3121
3205
|
demo={Boolean(dashboard.isDemo)}
|
|
3122
3206
|
disabled={controlsDisabled}
|
|
3123
|
-
|
|
3207
|
+
onCreateNote={createNote}
|
|
3124
3208
|
section={activeNewsroomSection}
|
|
3125
3209
|
sectionId={sectionPageId ?? ""}
|
|
3126
3210
|
sectionsLoaded={loadedSections.sections}
|
|
@@ -3146,6 +3230,12 @@ function NewsDeskDashboard({
|
|
|
3146
3230
|
semanticNodes={semanticNodes}
|
|
3147
3231
|
/>
|
|
3148
3232
|
) : null}
|
|
3233
|
+
{!isSectionPage && (activeTab === "desks" || activeTab === "topics") ? (
|
|
3234
|
+
<nav aria-label="Taxonomy views" className="taxonomy-view-switch" data-taxonomy-view-switch>
|
|
3235
|
+
<Link aria-current={activeTab === "desks" ? "page" : undefined} href={getNewsroomNavHref("/newsroom/desks", Boolean(dashboard.isDemo))}>Categories</Link>
|
|
3236
|
+
<Link aria-current={activeTab === "topics" ? "page" : undefined} href={getNewsroomNavHref("/newsroom/topics", Boolean(dashboard.isDemo))}>Topic proposals</Link>
|
|
3237
|
+
</nav>
|
|
3238
|
+
) : null}
|
|
3149
3239
|
{!isSectionPage && activeTab === "desks" ? (
|
|
3150
3240
|
<DesksDeskView
|
|
3151
3241
|
assignments={assignments}
|
|
@@ -3173,14 +3263,10 @@ function NewsDeskDashboard({
|
|
|
3173
3263
|
) : null}
|
|
3174
3264
|
{!isSectionPage && activeTab === "concepts" ? (
|
|
3175
3265
|
<ConceptsDeskView
|
|
3176
|
-
categories={mergeCategoryRecords(categorys, activeCategoryTreeNodes)}
|
|
3177
|
-
disabled={controlsDisabled}
|
|
3178
3266
|
graph={graph}
|
|
3179
|
-
initialCategoryLineageId={initialSelection.category}
|
|
3180
3267
|
initialNodeLineageId={initialSelection.node}
|
|
3181
|
-
|
|
3268
|
+
isDemo={Boolean(dashboard.isDemo)}
|
|
3182
3269
|
semanticNodes={semanticNodes}
|
|
3183
|
-
summary={summary}
|
|
3184
3270
|
/>
|
|
3185
3271
|
) : null}
|
|
3186
3272
|
{!isSectionPage && activeTab === "references" ? (
|
|
@@ -3189,7 +3275,19 @@ function NewsDeskDashboard({
|
|
|
3189
3275
|
disabled={controlsDisabled}
|
|
3190
3276
|
initialReferenceLineageId={initialSelection.reference ?? pathnameReferenceLineageId}
|
|
3191
3277
|
cyclotronStatus={cyclotronStatus}
|
|
3278
|
+
cyclotronPriming={{
|
|
3279
|
+
labels: primingLabels,
|
|
3280
|
+
requests: primingRequests,
|
|
3281
|
+
onRequest: async (input) => {
|
|
3282
|
+
const request = buildPrimingRequest(input, authState.label, primingLabels);
|
|
3283
|
+
if (!dashboard.isDemo) await requestCyclotronPriming(primingCyclotronId, request);
|
|
3284
|
+
setPrimingRequests((current) => [{ request, outcome: null }, ...current]);
|
|
3285
|
+
},
|
|
3286
|
+
}}
|
|
3192
3287
|
decisions={relevanceDecisions}
|
|
3288
|
+
humanCuratedReferenceLineageIds={humanCuratedReferenceLineageIds}
|
|
3289
|
+
delegatedCurations={delegatedCurations}
|
|
3290
|
+
shadowAgreement={shadowAgreement}
|
|
3193
3291
|
onRequestReviewRate={relevanceCyclotronId ? async (choice) => {
|
|
3194
3292
|
const request = buildReviewRateRequest(choice, authState.label);
|
|
3195
3293
|
if (dashboard.isDemo) return;
|
|
@@ -3202,20 +3300,14 @@ function NewsDeskDashboard({
|
|
|
3202
3300
|
normalizeReferenceRejectionReasonCode(curation.reasonCode),
|
|
3203
3301
|
{ decisionRelationId: curation.decisionRelationId, shareable: curation.shareable },
|
|
3204
3302
|
)}
|
|
3205
|
-
|
|
3303
|
+
sections={sectionChoices}
|
|
3304
|
+
sectionSuggestions={sectionSuggestions}
|
|
3305
|
+
sectionAgreement={siteSectionAgreement}
|
|
3306
|
+
onReview={(reference, action, options) => runReferenceCurationAction(reference, action, undefined, undefined, undefined, options?.sectionId)}
|
|
3206
3307
|
referenceAttachments={referenceAttachments}
|
|
3207
3308
|
references={references}
|
|
3208
3309
|
/>
|
|
3209
3310
|
) : null}
|
|
3210
|
-
{!isSectionPage && activeTab === "insights" ? (
|
|
3211
|
-
<InsightsDeskView
|
|
3212
|
-
graph={graph}
|
|
3213
|
-
initialThreadId={initialSelection.forumThread}
|
|
3214
|
-
isDemo={Boolean(dashboard.isDemo)}
|
|
3215
|
-
messages={messages}
|
|
3216
|
-
summary={summary}
|
|
3217
|
-
/>
|
|
3218
|
-
) : null}
|
|
3219
3311
|
{!isSectionPage && activeTab === "messages" ? (
|
|
3220
3312
|
<MessagesDeskView
|
|
3221
3313
|
assignments={assignments}
|
|
@@ -4159,20 +4251,20 @@ function RotatingSectionTriangleIcon({ expanded }: { expanded: boolean }) {
|
|
|
4159
4251
|
function DeepNewsroomSectionView({
|
|
4160
4252
|
demo,
|
|
4161
4253
|
disabled,
|
|
4162
|
-
|
|
4254
|
+
onCreateNote,
|
|
4163
4255
|
section,
|
|
4164
4256
|
sectionId,
|
|
4165
4257
|
sectionsLoaded,
|
|
4166
4258
|
}: {
|
|
4167
4259
|
demo: boolean;
|
|
4168
4260
|
disabled: boolean;
|
|
4169
|
-
|
|
4261
|
+
onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
|
|
4170
4262
|
section: NewsroomSectionRecord | null;
|
|
4171
4263
|
sectionId: string;
|
|
4172
4264
|
sectionsLoaded: boolean;
|
|
4173
4265
|
}) {
|
|
4174
|
-
const
|
|
4175
|
-
const
|
|
4266
|
+
const noteTarget = section ? noteTargetForNewsroomSection(section) : null;
|
|
4267
|
+
const noteComposer = useNewsroomNoteComposer(noteTarget, disabled, onCreateNote);
|
|
4176
4268
|
|
|
4177
4269
|
if (!section) {
|
|
4178
4270
|
return (
|
|
@@ -4201,13 +4293,13 @@ function DeepNewsroomSectionView({
|
|
|
4201
4293
|
<h2 data-newsroom-deep-section-title>{section.title}</h2>
|
|
4202
4294
|
<p>{displayNewsroomSectionShortTitle(section)}</p>
|
|
4203
4295
|
<div className="newsroom-deep-section__header-actions">
|
|
4204
|
-
<button type="button" disabled={
|
|
4205
|
-
{
|
|
4296
|
+
<button type="button" disabled={noteComposer.action.disabled} onClick={noteComposer.action.onSelect}>
|
|
4297
|
+
{noteComposer.loading ? "Saving Note" : "Add Note"}
|
|
4206
4298
|
</button>
|
|
4207
4299
|
<Link href={getNewsDeskTabHref("/newsroom", demo)}>Back to Newsroom</Link>
|
|
4208
4300
|
</div>
|
|
4209
4301
|
</header>
|
|
4210
|
-
{
|
|
4302
|
+
{noteComposer.error ? <div className="category-steering-alert" role="status">{noteComposer.error}</div> : null}
|
|
4211
4303
|
<div className="newsroom-deep-section__body">
|
|
4212
4304
|
<article className="newsroom-deep-section__field newsroom-deep-section__field--lead">
|
|
4213
4305
|
<p className="story-label">Mission</p>
|
|
@@ -4224,7 +4316,7 @@ function DeepNewsroomSectionView({
|
|
|
4224
4316
|
</article>
|
|
4225
4317
|
))}
|
|
4226
4318
|
</div>
|
|
4227
|
-
{
|
|
4319
|
+
{noteComposer.dialog}
|
|
4228
4320
|
</section>
|
|
4229
4321
|
);
|
|
4230
4322
|
}
|
|
@@ -4415,9 +4507,9 @@ function AdministrationDeskView({
|
|
|
4415
4507
|
<div className="news-desk-columns news-desk-columns--administration" data-news-desk-section="administration">
|
|
4416
4508
|
<div className="news-desk-main-column">
|
|
4417
4509
|
<section className="category-steering-section category-steering-section--lead" aria-labelledby="newsroom-administration-title">
|
|
4418
|
-
<SectionHeader title="Administration" detail="Settings for users, policies,
|
|
4419
|
-
<div className="news-desk-settings-shell">
|
|
4420
|
-
<nav className="news-desk-settings-nav" aria-label="Administration settings">
|
|
4510
|
+
<SectionHeader title={administrationPanel === "sections" ? "Sections" : "Administration"} detail={administrationPanel === "sections" ? "The publication's sections and ontology" : "Settings for users, policies, and procedures"} />
|
|
4511
|
+
<div className="news-desk-settings-shell" data-settings-nav={administrationPanel === "sections" ? "hidden" : undefined}>
|
|
4512
|
+
{administrationPanel === "sections" ? null : <nav className="news-desk-settings-nav" aria-label="Administration settings">
|
|
4421
4513
|
<Link
|
|
4422
4514
|
href={administrationPanelHref("users", isDemo)}
|
|
4423
4515
|
data-news-desk-admin-nav="users"
|
|
@@ -4436,15 +4528,6 @@ function AdministrationDeskView({
|
|
|
4436
4528
|
<strong>Policies</strong>
|
|
4437
4529
|
<span>Publication and category doctrine</span>
|
|
4438
4530
|
</Link>
|
|
4439
|
-
<Link
|
|
4440
|
-
href={administrationPanelHref("sections", isDemo)}
|
|
4441
|
-
data-news-desk-admin-nav="sections"
|
|
4442
|
-
data-active={administrationPanel === "sections" || undefined}
|
|
4443
|
-
onClick={() => onPanelChange("sections")}
|
|
4444
|
-
>
|
|
4445
|
-
<strong>Sections</strong>
|
|
4446
|
-
<span>Canonical and floating newspaper sections</span>
|
|
4447
|
-
</Link>
|
|
4448
4531
|
<Link
|
|
4449
4532
|
href={administrationPanelHref("procedures", isDemo)}
|
|
4450
4533
|
data-news-desk-admin-nav="procedures"
|
|
@@ -4454,6 +4537,15 @@ function AdministrationDeskView({
|
|
|
4454
4537
|
<strong>Procedures</strong>
|
|
4455
4538
|
<span>Procedure registry and versioned Tactus code</span>
|
|
4456
4539
|
</Link>
|
|
4540
|
+
<Link
|
|
4541
|
+
href={administrationPanelHref("delegations", isDemo)}
|
|
4542
|
+
data-news-desk-admin-nav="delegations"
|
|
4543
|
+
data-active={administrationPanel === "delegations" || undefined}
|
|
4544
|
+
onClick={() => onPanelChange("delegations")}
|
|
4545
|
+
>
|
|
4546
|
+
<strong>Agent delegations</strong>
|
|
4547
|
+
<span>Let an agent curate References for you</span>
|
|
4548
|
+
</Link>
|
|
4457
4549
|
{signupPluginEnabled() ? (
|
|
4458
4550
|
<Link
|
|
4459
4551
|
href={administrationPanelHref("signups", isDemo)}
|
|
@@ -4465,9 +4557,10 @@ function AdministrationDeskView({
|
|
|
4465
4557
|
<span>Addresses from the site's signup forms</span>
|
|
4466
4558
|
</Link>
|
|
4467
4559
|
) : null}
|
|
4468
|
-
</nav>
|
|
4560
|
+
</nav>}
|
|
4469
4561
|
<div className="news-desk-settings-panel">
|
|
4470
4562
|
{administrationPanel === "signups" ? <SignupEntriesPanel /> : null}
|
|
4563
|
+
{administrationPanel === "delegations" ? <AdministrationDelegationsPanel disabled={disabled} /> : null}
|
|
4471
4564
|
{administrationPanel === "users" ? (
|
|
4472
4565
|
<AdministrationUsersPanel
|
|
4473
4566
|
canManageUsers={canManageUsers}
|
|
@@ -4953,7 +5046,7 @@ function AdministrationSectionsPanel({
|
|
|
4953
5046
|
|
|
4954
5047
|
return (
|
|
4955
5048
|
<div data-news-desk-admin-panel="sections">
|
|
4956
|
-
<SectionHeader title="
|
|
5049
|
+
<SectionHeader title="Sections" detail={`${sortedSections.length} configured sections`} />
|
|
4957
5050
|
<div className="news-desk-administration-category-layout">
|
|
4958
5051
|
<div className="news-desk-administration-category-list" data-news-desk-section-list>
|
|
4959
5052
|
{sortedSections.length ? (
|
|
@@ -5790,7 +5883,7 @@ function DesksDeskView({
|
|
|
5790
5883
|
}, [initialRootKey, roots, selectedRoot, selectedRootKey]);
|
|
5791
5884
|
|
|
5792
5885
|
return (
|
|
5793
|
-
<div className="news-desk-columns" data-news-desk-section="desks">
|
|
5886
|
+
<div className="news-desk-columns news-desk-columns--administration news-desk-columns--taxonomy" data-news-desk-section="desks">
|
|
5794
5887
|
<div className="news-desk-main-column">
|
|
5795
5888
|
<section className="category-steering-section category-steering-section--lead" aria-labelledby="newsroom-desks-title">
|
|
5796
5889
|
<SectionHeader title="News Desks" detail={`${roots.length} root topic desks`} />
|
|
@@ -6340,148 +6433,27 @@ function TopicProposalEditModal({
|
|
|
6340
6433
|
}
|
|
6341
6434
|
|
|
6342
6435
|
function ConceptsDeskView({
|
|
6343
|
-
categories,
|
|
6344
|
-
disabled,
|
|
6345
6436
|
graph,
|
|
6346
|
-
initialCategoryLineageId,
|
|
6347
6437
|
initialNodeLineageId,
|
|
6348
|
-
|
|
6438
|
+
isDemo,
|
|
6349
6439
|
semanticNodes,
|
|
6350
|
-
summary,
|
|
6351
6440
|
}: {
|
|
6352
|
-
categories: CategorySteeringCategory[];
|
|
6353
|
-
disabled: boolean;
|
|
6354
6441
|
graph: SemanticGraph;
|
|
6355
|
-
initialCategoryLineageId?: string | null;
|
|
6356
6442
|
initialNodeLineageId?: string | null;
|
|
6357
|
-
|
|
6443
|
+
isDemo: boolean;
|
|
6358
6444
|
semanticNodes: SemanticNodeRecord[];
|
|
6359
|
-
summary?: NewsroomSummaryRecord | null;
|
|
6360
6445
|
}) {
|
|
6361
|
-
const [nodeKindFilter, setNodeKindFilter] = useState("");
|
|
6362
|
-
const [nodeStatusFilter, setNodeStatusFilter] = useState("");
|
|
6363
|
-
const [selectedNodeLineageId, setSelectedNodeLineageId] = useState(initialNodeLineageId ?? "");
|
|
6364
|
-
const [isConceptDetailOpen, setIsConceptDetailOpen] = useState(Boolean(initialNodeLineageId));
|
|
6365
|
-
const categoryContext = useMemo(() => buildCategoryDrilldownContext(categories, initialCategoryLineageId), [categories, initialCategoryLineageId]);
|
|
6366
6446
|
const feed = useNewsroomPagedRows({
|
|
6367
6447
|
initialItems: semanticNodes,
|
|
6368
|
-
enabled:
|
|
6369
|
-
resetKey:
|
|
6370
|
-
loadPage: (nextToken) => loadNewsroomSemanticNodePage({
|
|
6371
|
-
nodeKind: nodeKindFilter,
|
|
6372
|
-
status: nodeStatusFilter,
|
|
6373
|
-
nextToken,
|
|
6374
|
-
}),
|
|
6448
|
+
enabled: true,
|
|
6449
|
+
resetKey: "semanticNodes:wiki",
|
|
6450
|
+
loadPage: (nextToken) => loadNewsroomSemanticNodePage({ nodeKind: "", status: "", nextToken }),
|
|
6375
6451
|
});
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
), [categoryContext, graph]);
|
|
6382
|
-
const feedNodes = useMemo(() => (
|
|
6383
|
-
feed.items.length
|
|
6384
|
-
? feed.items
|
|
6385
|
-
: (feed.isLoading || Boolean(feed.error))
|
|
6386
|
-
? semanticNodes
|
|
6387
|
-
: feed.items
|
|
6388
|
-
), [feed.error, feed.isLoading, feed.items, semanticNodes]);
|
|
6389
|
-
const visibleNodes = useMemo(() => (
|
|
6390
|
-
categoryConceptLineages
|
|
6391
|
-
? semanticNodes.filter((node) => categoryConceptLineages.has(node.lineageId ?? node.id))
|
|
6392
|
-
: feedNodes
|
|
6393
|
-
), [categoryConceptLineages, feedNodes, semanticNodes]);
|
|
6394
|
-
const fallbackConceptCategories = useMemo(() => (
|
|
6395
|
-
visibleNodes.length
|
|
6396
|
-
? []
|
|
6397
|
-
: categories.filter((category) => category.status !== "deprecated" && category.status !== "archived")
|
|
6398
|
-
), [categories, visibleNodes.length]);
|
|
6399
|
-
const requestedNodeLineageId = selectedNodeLineageId || initialNodeLineageId || "";
|
|
6400
|
-
const selected = selectSemanticNodeSummary(graph, visibleNodes, requestedNodeLineageId)
|
|
6401
|
-
?? (requestedNodeLineageId ? selectSemanticNodeSummary(graph, semanticNodes, requestedNodeLineageId) : null)
|
|
6402
|
-
?? (requestedNodeLineageId ? selectCategorySummary(graph, categories, requestedNodeLineageId) : null)
|
|
6403
|
-
?? categoryFilter
|
|
6404
|
-
?? selectSemanticNodeSummary(graph, visibleNodes)
|
|
6405
|
-
?? selectCategorySummary(graph, fallbackConceptCategories);
|
|
6406
|
-
const conceptKnowledgeQuery = useNewsroomKnowledgeContext(selected ? {
|
|
6407
|
-
anchor: {
|
|
6408
|
-
kind: selected.kind === "category" ? "category" : "semanticNode",
|
|
6409
|
-
id: selected.id ?? selected.lineageId,
|
|
6410
|
-
lineageId: selected.lineageId,
|
|
6411
|
-
},
|
|
6412
|
-
title: selected.label,
|
|
6413
|
-
subtitle: selected.subtitle,
|
|
6414
|
-
} : null);
|
|
6415
|
-
const conceptInsight = useNewsroomInsightComposer(
|
|
6416
|
-
selected && (selected.kind === "category" || selected.kind === "semanticNode") ? insightTargetForSemanticObject(selected) : null,
|
|
6417
|
-
disabled,
|
|
6418
|
-
onCreateInsight,
|
|
6419
|
-
);
|
|
6420
|
-
const detail = categoryFilter
|
|
6421
|
-
? `${visibleNodes.length} graph nodes associated with ${categoryFilter.label}`
|
|
6422
|
-
: visibleNodes.length
|
|
6423
|
-
? `${summaryCountFromRecord(summary, "semanticNodes") || visibleNodes.length} graph nodes`
|
|
6424
|
-
: `${fallbackConceptCategories.length} taxonomy concepts`;
|
|
6425
|
-
const nodeKindCounts = categoryFilter ? countSemanticNodesByKind(visibleNodes) : summary?.facets?.semanticNodes?.byNodeKind ?? countSemanticNodesByKind(visibleNodes);
|
|
6426
|
-
const nodeStatusCounts = categoryFilter ? countSemanticNodesByStatus(visibleNodes) : summary?.facets?.semanticNodes?.byStatus ?? countSemanticNodesByStatus(visibleNodes);
|
|
6427
|
-
const nodeKinds = sortedCountOptions(nodeKindCounts).slice(0, 8);
|
|
6428
|
-
const cards = visibleNodes.length
|
|
6429
|
-
? visibleNodes.map((node, index) => semanticNodeToNewsroomCard(node, index))
|
|
6430
|
-
: fallbackConceptCategories.map((category, index) => categoryToConceptNewsroomCard(category, index));
|
|
6431
|
-
const cardIds = useMemo(() => new Set(cards.map((card) => card.id)), [cards]);
|
|
6432
|
-
const selectedLineageId = selected && cardIds.has(selected.lineageId) ? selected.lineageId : null;
|
|
6433
|
-
const selectNode = (lineageId: string) => {
|
|
6434
|
-
setSelectedNodeLineageId(lineageId);
|
|
6435
|
-
setIsConceptDetailOpen(true);
|
|
6436
|
-
pushNewsroomDetailUrl("concepts", lineageId, false);
|
|
6437
|
-
};
|
|
6438
|
-
return (
|
|
6439
|
-
<>
|
|
6440
|
-
<NewsroomListDetailShell
|
|
6441
|
-
animatedDetail
|
|
6442
|
-
sectionKey="concepts"
|
|
6443
|
-
canExpandDetail={Boolean(selected)}
|
|
6444
|
-
detailOpen={isConceptDetailOpen}
|
|
6445
|
-
selectionScrollKey={selectedLineageId}
|
|
6446
|
-
actions={[conceptInsight.action]}
|
|
6447
|
-
utilityActions={[conceptKnowledgeQuery.action]}
|
|
6448
|
-
lede={(
|
|
6449
|
-
<NewsroomDeskSectionLede headingId="semantic-concepts-title" section="concepts" />
|
|
6450
|
-
)}
|
|
6451
|
-
list={(
|
|
6452
|
-
<section className="category-steering-section category-steering-section--lead" aria-label={detail}>
|
|
6453
|
-
<NewsroomCardGrid
|
|
6454
|
-
cards={cards}
|
|
6455
|
-
emptyLabel={feed.isLoading ? "Loading semantic concepts" : feed.error ?? "No semantic nodes imported"}
|
|
6456
|
-
filterLabel="Concept kind"
|
|
6457
|
-
filterOptions={[
|
|
6458
|
-
{ key: "", label: "All concept kinds", count: summaryCountFromRecord(summary, "semanticNodes") || visibleNodes.length },
|
|
6459
|
-
...nodeKinds.map((option) => ({ key: option.key, label: formatAssignmentTypeLabel(option.key), count: option.count })),
|
|
6460
|
-
]}
|
|
6461
|
-
filterValue={nodeKindFilter}
|
|
6462
|
-
footerLabel={feed.error ?? undefined}
|
|
6463
|
-
hasMore={!categoryFilter && feed.hasMore}
|
|
6464
|
-
isLoadingMore={feed.isLoadingMore}
|
|
6465
|
-
metricValue={nodeStatusFilter}
|
|
6466
|
-
metrics={[
|
|
6467
|
-
{ key: "", label: "All", count: summaryCountFromRecord(summary, "semanticNodes") || visibleNodes.length },
|
|
6468
|
-
...sortedCountOptions(nodeStatusCounts).map((option) => ({ key: option.key, label: option.key, count: option.count })),
|
|
6469
|
-
]}
|
|
6470
|
-
onFilterChange={setNodeKindFilter}
|
|
6471
|
-
onLoadMore={feed.loadMore}
|
|
6472
|
-
onMetricChange={setNodeStatusFilter}
|
|
6473
|
-
onSelect={selectNode}
|
|
6474
|
-
selectedId={selectedLineageId}
|
|
6475
|
-
/>
|
|
6476
|
-
</section>
|
|
6477
|
-
)}
|
|
6478
|
-
onCloseDetail={() => setIsConceptDetailOpen(false)}
|
|
6479
|
-
detail={<SemanticDetailPanel graph={graph} selected={selected} knowledgeQuery={conceptKnowledgeQuery} />}
|
|
6480
|
-
/>
|
|
6481
|
-
{conceptKnowledgeQuery.dialog}
|
|
6482
|
-
{conceptInsight.dialog}
|
|
6483
|
-
</>
|
|
6484
|
-
);
|
|
6452
|
+
useEffect(() => {
|
|
6453
|
+
if (feed.hasMore && !feed.isLoadingMore) feed.loadMore();
|
|
6454
|
+
}, [feed]);
|
|
6455
|
+
const nodes = feed.items.length ? feed.items : semanticNodes;
|
|
6456
|
+
return <WikiConceptsView demo={isDemo} graph={graph} initialNodeLineageId={initialNodeLineageId} nodes={nodes} />;
|
|
6485
6457
|
}
|
|
6486
6458
|
|
|
6487
6459
|
function ReferencesDeskView({
|
|
@@ -6495,7 +6467,7 @@ function ReferencesDeskView({
|
|
|
6495
6467
|
initialReferenceLineageId,
|
|
6496
6468
|
isDemo,
|
|
6497
6469
|
deepLinkFetchEnabled = true,
|
|
6498
|
-
|
|
6470
|
+
onCreateNote,
|
|
6499
6471
|
onHydrateReference,
|
|
6500
6472
|
onMoveCorpus,
|
|
6501
6473
|
onReview,
|
|
@@ -6520,7 +6492,7 @@ function ReferencesDeskView({
|
|
|
6520
6492
|
initialReferenceLineageId?: string | null;
|
|
6521
6493
|
isDemo?: boolean;
|
|
6522
6494
|
deepLinkFetchEnabled?: boolean;
|
|
6523
|
-
|
|
6495
|
+
onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
|
|
6524
6496
|
onHydrateReference?: (reference: ReferenceRecord) => void;
|
|
6525
6497
|
onMoveCorpus: (reference: ReferenceRecord, corpusId: string) => void;
|
|
6526
6498
|
onReview: (reference: ReferenceRecord, action: ReferenceCurationAction, note?: string, reasonCode?: ReferenceRejectionReasonCode | null) => void;
|
|
@@ -6924,7 +6896,7 @@ function ReferencesDeskView({
|
|
|
6924
6896
|
corpora={corpora}
|
|
6925
6897
|
disabled={disabled}
|
|
6926
6898
|
graph={graph}
|
|
6927
|
-
|
|
6899
|
+
onCreateNote={onCreateNote}
|
|
6928
6900
|
onMoveCorpus={onMoveCorpus}
|
|
6929
6901
|
onReview={runReferenceAction}
|
|
6930
6902
|
onReviewTopicLabel={onReviewTopicLabel}
|
|
@@ -6976,365 +6948,6 @@ function NewsroomDeskSectionLede({
|
|
|
6976
6948
|
);
|
|
6977
6949
|
}
|
|
6978
6950
|
|
|
6979
|
-
function InsightsDeskView({
|
|
6980
|
-
graph,
|
|
6981
|
-
initialThreadId = null,
|
|
6982
|
-
isDemo = false,
|
|
6983
|
-
messages,
|
|
6984
|
-
summary,
|
|
6985
|
-
}: {
|
|
6986
|
-
graph: SemanticGraph;
|
|
6987
|
-
initialThreadId?: string | null;
|
|
6988
|
-
isDemo?: boolean;
|
|
6989
|
-
messages: MessageRecord[];
|
|
6990
|
-
summary?: NewsroomSummaryRecord | null;
|
|
6991
|
-
}) {
|
|
6992
|
-
const pathname = usePathname();
|
|
6993
|
-
const pathnameThreadId = useMemo(
|
|
6994
|
-
() => parseInsightThreadIdFromNewsroomPathname(pathname),
|
|
6995
|
-
[pathname],
|
|
6996
|
-
);
|
|
6997
|
-
const resolvedInitialThreadId = initialThreadId ?? pathnameThreadId ?? "";
|
|
6998
|
-
const forumMessageAnchorId = useForumMessageAnchorId();
|
|
6999
|
-
const [domainFilter, setDomainFilter] = useState(() => {
|
|
7000
|
-
if (typeof window === "undefined") return "";
|
|
7001
|
-
return readInsightsIndexFilters(new URLSearchParams(window.location.search)).domain;
|
|
7002
|
-
});
|
|
7003
|
-
const [threads, setThreads] = useState<ForumThreadWithMessages[]>([]);
|
|
7004
|
-
const [threadsLoading, setThreadsLoading] = useState(false);
|
|
7005
|
-
const [threadsError, setThreadsError] = useState<string | null>(null);
|
|
7006
|
-
const [nextToken, setNextToken] = useState<string | null>(null);
|
|
7007
|
-
const [selectedThreadId, setSelectedThreadId] = useState(resolvedInitialThreadId);
|
|
7008
|
-
const [forumView, setForumView] = useState<ForumViewState>(
|
|
7009
|
-
resolvedInitialThreadId ? { mode: "thread", threadId: resolvedInitialThreadId } : { mode: "index" },
|
|
7010
|
-
);
|
|
7011
|
-
const [replyParentId, setReplyParentId] = useState("");
|
|
7012
|
-
const [composeSummary, setComposeSummary] = useState("");
|
|
7013
|
-
const [composeContent, setComposeContent] = useState("");
|
|
7014
|
-
const [composeError, setComposeError] = useState<string | null>(null);
|
|
7015
|
-
const [deletingMessageId, setDeletingMessageId] = useState("");
|
|
7016
|
-
|
|
7017
|
-
const syncInsightsIndexUrl = useCallback((nextDomain: string, replace = true) => {
|
|
7018
|
-
if (isDemo || forumView.mode === "thread") return;
|
|
7019
|
-
syncBrowserNewsroomIndexUrl("insights", effectiveInsightsIndexFilters({ domain: nextDomain }), { replace });
|
|
7020
|
-
}, [forumView.mode, isDemo]);
|
|
7021
|
-
|
|
7022
|
-
useEffect(() => {
|
|
7023
|
-
if (isDemo || forumView.mode === "thread") return;
|
|
7024
|
-
syncInsightsIndexUrl(domainFilter, true);
|
|
7025
|
-
}, [domainFilter, forumView.mode, isDemo, syncInsightsIndexUrl]);
|
|
7026
|
-
|
|
7027
|
-
const refreshThreads = useCallback(async (options?: { append?: boolean; token?: string | null }) => {
|
|
7028
|
-
if (isDemo) {
|
|
7029
|
-
const demoThreads = messages
|
|
7030
|
-
.filter((message) => message.messageKind === "insight")
|
|
7031
|
-
.filter((message) => !domainFilter || message.messageDomain === domainFilter)
|
|
7032
|
-
.map((message) => buildDemoInsightForumThread(message, messages));
|
|
7033
|
-
setThreads(demoThreads);
|
|
7034
|
-
setThreadsError(null);
|
|
7035
|
-
setNextToken(null);
|
|
7036
|
-
return;
|
|
7037
|
-
}
|
|
7038
|
-
setThreadsLoading(true);
|
|
7039
|
-
try {
|
|
7040
|
-
const result = await loadInsightForumThreads({
|
|
7041
|
-
domain: domainFilter,
|
|
7042
|
-
nextToken: options?.token ?? null,
|
|
7043
|
-
includeMessages: true,
|
|
7044
|
-
});
|
|
7045
|
-
setThreads((current) => (options?.append ? [...current, ...result.threads] : result.threads));
|
|
7046
|
-
setNextToken(result.nextToken);
|
|
7047
|
-
setThreadsError(null);
|
|
7048
|
-
setSelectedThreadId((current) => (current && result.threads.some((thread) => thread.id === current) ? current : current));
|
|
7049
|
-
} catch (error) {
|
|
7050
|
-
setThreads([]);
|
|
7051
|
-
setThreadsError(error instanceof Error ? error.message : "Could not load insight threads.");
|
|
7052
|
-
setNextToken(null);
|
|
7053
|
-
} finally {
|
|
7054
|
-
setThreadsLoading(false);
|
|
7055
|
-
}
|
|
7056
|
-
}, [domainFilter, isDemo, messages]);
|
|
7057
|
-
|
|
7058
|
-
useEffect(() => {
|
|
7059
|
-
void refreshThreads();
|
|
7060
|
-
}, [refreshThreads]);
|
|
7061
|
-
|
|
7062
|
-
useEffect(() => {
|
|
7063
|
-
if (!resolvedInitialThreadId || threadsLoading) return;
|
|
7064
|
-
if (!threads.some((thread) => thread.id === resolvedInitialThreadId)) return;
|
|
7065
|
-
if (selectedThreadId === resolvedInitialThreadId && forumView.mode === "thread") return;
|
|
7066
|
-
setSelectedThreadId(resolvedInitialThreadId);
|
|
7067
|
-
setForumView({ mode: "thread", threadId: resolvedInitialThreadId });
|
|
7068
|
-
}, [forumView.mode, resolvedInitialThreadId, selectedThreadId, threads, threadsLoading]);
|
|
7069
|
-
|
|
7070
|
-
useEffect(() => {
|
|
7071
|
-
const handlePopState = () => {
|
|
7072
|
-
const route = readCurrentForumRoute();
|
|
7073
|
-
if (route.surface === "insight_forum" && route.threadId) {
|
|
7074
|
-
setSelectedThreadId(route.threadId);
|
|
7075
|
-
setForumView({ mode: "thread", threadId: route.threadId });
|
|
7076
|
-
return;
|
|
7077
|
-
}
|
|
7078
|
-
setSelectedThreadId("");
|
|
7079
|
-
setForumView({ mode: "index" });
|
|
7080
|
-
};
|
|
7081
|
-
window.addEventListener("popstate", handlePopState);
|
|
7082
|
-
return () => window.removeEventListener("popstate", handlePopState);
|
|
7083
|
-
}, []);
|
|
7084
|
-
|
|
7085
|
-
const selectedThread = threads.find((thread) => thread.id === selectedThreadId) ?? null;
|
|
7086
|
-
const domainOptions = useMemo(() => {
|
|
7087
|
-
const domains = new Set<string>();
|
|
7088
|
-
for (const message of messages) {
|
|
7089
|
-
if (message.messageKind === "insight" && message.messageDomain) domains.add(message.messageDomain);
|
|
7090
|
-
}
|
|
7091
|
-
for (const thread of threads) {
|
|
7092
|
-
if (thread.sectionKey) domains.add(thread.sectionKey);
|
|
7093
|
-
}
|
|
7094
|
-
return sortedCountOptions(
|
|
7095
|
-
Object.fromEntries([...domains].map((domain) => [domain, threads.filter((thread) => thread.sectionKey === domain).length])),
|
|
7096
|
-
);
|
|
7097
|
-
}, [messages, threads]);
|
|
7098
|
-
|
|
7099
|
-
const openThread = useCallback((threadId: string, options?: { messageId?: string | null; replace?: boolean }) => {
|
|
7100
|
-
setSelectedThreadId(threadId);
|
|
7101
|
-
setForumView({ mode: "thread", threadId });
|
|
7102
|
-
pushInsightThreadUrl(threadId, {
|
|
7103
|
-
demo: isDemo,
|
|
7104
|
-
messageId: options?.messageId,
|
|
7105
|
-
replace: options?.replace,
|
|
7106
|
-
});
|
|
7107
|
-
}, [isDemo]);
|
|
7108
|
-
|
|
7109
|
-
const closeThread = useCallback(() => {
|
|
7110
|
-
setSelectedThreadId("");
|
|
7111
|
-
setForumView({ mode: "index" });
|
|
7112
|
-
pushInsightThreadUrl(null, { demo: isDemo, replace: true });
|
|
7113
|
-
}, [isDemo]);
|
|
7114
|
-
|
|
7115
|
-
const postThreadMessage = useCallback(async () => {
|
|
7116
|
-
if (!selectedThread) return;
|
|
7117
|
-
const content = composeContent.trim();
|
|
7118
|
-
if (!content) {
|
|
7119
|
-
setComposeError("Message content is required.");
|
|
7120
|
-
return;
|
|
7121
|
-
}
|
|
7122
|
-
const summary = composeSummary.trim() || content.slice(0, 120);
|
|
7123
|
-
setComposeError(null);
|
|
7124
|
-
try {
|
|
7125
|
-
await appendInsightThreadReplyRecord({
|
|
7126
|
-
threadId: selectedThread.id,
|
|
7127
|
-
summary,
|
|
7128
|
-
content,
|
|
7129
|
-
role: "human",
|
|
7130
|
-
authorLabel: "human-editor",
|
|
7131
|
-
parentMessageId: replyParentId || undefined,
|
|
7132
|
-
});
|
|
7133
|
-
setComposeSummary("");
|
|
7134
|
-
setComposeContent("");
|
|
7135
|
-
setReplyParentId("");
|
|
7136
|
-
await refreshThreads();
|
|
7137
|
-
const reloaded = await loadInsightForumThreadById(selectedThread.id, { includeMessages: true });
|
|
7138
|
-
if (reloaded) {
|
|
7139
|
-
setThreads((current) => current.map((thread) => (thread.id === reloaded.id ? reloaded : thread)));
|
|
7140
|
-
}
|
|
7141
|
-
setForumView({ mode: "thread", threadId: selectedThread.id });
|
|
7142
|
-
} catch (error) {
|
|
7143
|
-
setComposeError(error instanceof Error ? error.message : "Could not post thread message.");
|
|
7144
|
-
}
|
|
7145
|
-
}, [composeContent, composeSummary, refreshThreads, replyParentId, selectedThread]);
|
|
7146
|
-
|
|
7147
|
-
const deleteThreadMessage = useCallback(async (threadId: string, messageId: string) => {
|
|
7148
|
-
setDeletingMessageId(messageId);
|
|
7149
|
-
try {
|
|
7150
|
-
await deleteInsightThreadMessageRecord({ threadId, messageId });
|
|
7151
|
-
if (replyParentId === messageId) setReplyParentId("");
|
|
7152
|
-
await refreshThreads();
|
|
7153
|
-
const reloaded = await loadInsightForumThreadById(threadId, { includeMessages: true });
|
|
7154
|
-
if (reloaded) {
|
|
7155
|
-
setThreads((current) => current.map((thread) => (thread.id === reloaded.id ? reloaded : thread)));
|
|
7156
|
-
}
|
|
7157
|
-
setComposeError(null);
|
|
7158
|
-
} catch (error) {
|
|
7159
|
-
setComposeError(error instanceof Error ? error.message : "Could not delete message.");
|
|
7160
|
-
} finally {
|
|
7161
|
-
setDeletingMessageId("");
|
|
7162
|
-
}
|
|
7163
|
-
}, [refreshThreads, replyParentId]);
|
|
7164
|
-
|
|
7165
|
-
const insightKnowledgeQuery = useNewsroomKnowledgeContext(selectedThread ? {
|
|
7166
|
-
anchor: { kind: "message", id: selectedThread.id, lineageId: selectedThread.id },
|
|
7167
|
-
title: selectedThread.title ?? "Insight thread",
|
|
7168
|
-
subtitle: selectedThread.sectionKey ?? "insight",
|
|
7169
|
-
} : null);
|
|
7170
|
-
|
|
7171
|
-
return (
|
|
7172
|
-
<>
|
|
7173
|
-
<NewsroomListDetailShell
|
|
7174
|
-
sectionKey="insights"
|
|
7175
|
-
canExpandDetail={forumView.mode === "thread" && Boolean(forumView.threadId)}
|
|
7176
|
-
detailOpen={forumView.mode === "thread" && Boolean(forumView.threadId)}
|
|
7177
|
-
selectionScrollKey={forumView.mode === "thread" ? forumView.threadId : null}
|
|
7178
|
-
utilityActions={[insightKnowledgeQuery.action]}
|
|
7179
|
-
lede={(
|
|
7180
|
-
<NewsroomDeskSectionLede
|
|
7181
|
-
headingId="insights-forum-title"
|
|
7182
|
-
section="insights"
|
|
7183
|
-
headline="Insights"
|
|
7184
|
-
lede="Recent research insights in reverse chronological order. Each insight opens a forum thread for discussion and refinement."
|
|
7185
|
-
controls={(
|
|
7186
|
-
<div className="news-desk-reference-sort-controls" role="group" aria-label="Insight domain filter">
|
|
7187
|
-
<span className="news-desk-reference-sort-controls__label">Domain</span>
|
|
7188
|
-
<button
|
|
7189
|
-
type="button"
|
|
7190
|
-
data-active={!domainFilter || undefined}
|
|
7191
|
-
onClick={() => {
|
|
7192
|
-
setDomainFilter("");
|
|
7193
|
-
syncInsightsIndexUrl("", true);
|
|
7194
|
-
}}
|
|
7195
|
-
>
|
|
7196
|
-
All
|
|
7197
|
-
</button>
|
|
7198
|
-
<button
|
|
7199
|
-
type="button"
|
|
7200
|
-
data-active={domainFilter === "knowledge" || undefined}
|
|
7201
|
-
onClick={() => {
|
|
7202
|
-
setDomainFilter("knowledge");
|
|
7203
|
-
syncInsightsIndexUrl("knowledge", true);
|
|
7204
|
-
}}
|
|
7205
|
-
>
|
|
7206
|
-
Knowledge
|
|
7207
|
-
</button>
|
|
7208
|
-
<button
|
|
7209
|
-
type="button"
|
|
7210
|
-
data-active={domainFilter === "assignment_work" || undefined}
|
|
7211
|
-
onClick={() => {
|
|
7212
|
-
setDomainFilter("assignment_work");
|
|
7213
|
-
syncInsightsIndexUrl("assignment_work", true);
|
|
7214
|
-
}}
|
|
7215
|
-
>
|
|
7216
|
-
Assignment work
|
|
7217
|
-
</button>
|
|
7218
|
-
</div>
|
|
7219
|
-
)}
|
|
7220
|
-
/>
|
|
7221
|
-
)}
|
|
7222
|
-
list={(
|
|
7223
|
-
<section className="category-steering-section category-steering-section--lead" aria-label="Insight forum threads">
|
|
7224
|
-
<ForumThreadIndex
|
|
7225
|
-
threads={threads}
|
|
7226
|
-
sections={[]}
|
|
7227
|
-
isLoading={threadsLoading}
|
|
7228
|
-
error={threadsError}
|
|
7229
|
-
emptyLabel="No insight threads match this filter."
|
|
7230
|
-
variant="insight"
|
|
7231
|
-
toolbar={domainOptions.length > 2 ? (
|
|
7232
|
-
<div className="news-desk-forum-toolbar">
|
|
7233
|
-
<select
|
|
7234
|
-
aria-label="Insight domain"
|
|
7235
|
-
value={domainFilter}
|
|
7236
|
-
onChange={(event) => {
|
|
7237
|
-
setDomainFilter(event.target.value);
|
|
7238
|
-
syncInsightsIndexUrl(event.target.value, true);
|
|
7239
|
-
}}
|
|
7240
|
-
>
|
|
7241
|
-
<option value="">All domains ({threads.length})</option>
|
|
7242
|
-
{domainOptions.map((option) => (
|
|
7243
|
-
<option key={option.key} value={option.key}>{option.key} ({option.count})</option>
|
|
7244
|
-
))}
|
|
7245
|
-
</select>
|
|
7246
|
-
{nextToken ? (
|
|
7247
|
-
<button type="button" disabled={threadsLoading} onClick={() => void refreshThreads({ append: true, token: nextToken })}>
|
|
7248
|
-
Load more
|
|
7249
|
-
</button>
|
|
7250
|
-
) : null}
|
|
7251
|
-
</div>
|
|
7252
|
-
) : nextToken ? (
|
|
7253
|
-
<div className="news-desk-forum-toolbar">
|
|
7254
|
-
<button type="button" disabled={threadsLoading} onClick={() => void refreshThreads({ append: true, token: nextToken })}>
|
|
7255
|
-
Load more
|
|
7256
|
-
</button>
|
|
7257
|
-
</div>
|
|
7258
|
-
) : null}
|
|
7259
|
-
onOpenThread={(threadId) => openThread(threadId)}
|
|
7260
|
-
/>
|
|
7261
|
-
</section>
|
|
7262
|
-
)}
|
|
7263
|
-
detail={forumView.mode === "thread" && selectedThread ? (
|
|
7264
|
-
<ForumThreadView
|
|
7265
|
-
thread={selectedThread}
|
|
7266
|
-
sections={[]}
|
|
7267
|
-
replyParentId={replyParentId}
|
|
7268
|
-
composeSummary={composeSummary}
|
|
7269
|
-
composeContent={composeContent}
|
|
7270
|
-
composeError={composeError}
|
|
7271
|
-
focusMessageId={forumMessageAnchorId}
|
|
7272
|
-
isDemo={isDemo}
|
|
7273
|
-
variant="insight"
|
|
7274
|
-
onBack={closeThread}
|
|
7275
|
-
onReplyTarget={(messageId) => {
|
|
7276
|
-
setReplyParentId(messageId);
|
|
7277
|
-
pushInsightThreadUrl(selectedThread.id, { demo: isDemo, messageId, replace: true });
|
|
7278
|
-
}}
|
|
7279
|
-
onClearReplyTarget={() => {
|
|
7280
|
-
setReplyParentId("");
|
|
7281
|
-
pushInsightThreadUrl(selectedThread.id, { demo: isDemo, replace: true });
|
|
7282
|
-
}}
|
|
7283
|
-
onSummaryChange={setComposeSummary}
|
|
7284
|
-
onContentChange={setComposeContent}
|
|
7285
|
-
onSubmit={() => void postThreadMessage()}
|
|
7286
|
-
onDeleteMessage={(messageId) => {
|
|
7287
|
-
if (!selectedThread || deletingMessageId === messageId) return;
|
|
7288
|
-
void deleteThreadMessage(selectedThread.id, messageId);
|
|
7289
|
-
}}
|
|
7290
|
-
deletingMessageId={deletingMessageId}
|
|
7291
|
-
/>
|
|
7292
|
-
) : forumView.mode === "thread" ? (
|
|
7293
|
-
<section className="category-steering-section">
|
|
7294
|
-
<SectionHeader title="Thread Unavailable" detail="Insights" />
|
|
7295
|
-
<EmptyRow label="That insight thread is not available. Return to the index or adjust filters." />
|
|
7296
|
-
</section>
|
|
7297
|
-
) : null}
|
|
7298
|
-
/>
|
|
7299
|
-
{insightKnowledgeQuery.dialog}
|
|
7300
|
-
</>
|
|
7301
|
-
);
|
|
7302
|
-
}
|
|
7303
|
-
|
|
7304
|
-
function buildDemoInsightForumThread(root: MessageRecord, allMessages: MessageRecord[]): ForumThreadWithMessages {
|
|
7305
|
-
const replies = allMessages.filter((message) => (
|
|
7306
|
-
message.threadId === root.id
|
|
7307
|
-
&& message.id !== root.id
|
|
7308
|
-
&& String(message.status || "active") === "active"
|
|
7309
|
-
));
|
|
7310
|
-
const messages = [root, ...replies].sort(
|
|
7311
|
-
(left, right) => Number(left.sequenceNumber ?? 0) - Number(right.sequenceNumber ?? 0),
|
|
7312
|
-
);
|
|
7313
|
-
const lastMessage = messages.at(-1) ?? root;
|
|
7314
|
-
return {
|
|
7315
|
-
id: root.id,
|
|
7316
|
-
threadKind: "insight_forum",
|
|
7317
|
-
status: "active",
|
|
7318
|
-
title: root.summary ?? "Insight",
|
|
7319
|
-
summary: root.messageDomain ?? null,
|
|
7320
|
-
primaryAnchorKind: "message",
|
|
7321
|
-
primaryAnchorId: root.id,
|
|
7322
|
-
primaryAnchorLineageId: root.id,
|
|
7323
|
-
primaryAnchorKey: `message#${root.id}`,
|
|
7324
|
-
createdByLabel: root.authorLabel ?? null,
|
|
7325
|
-
messageCount: messages.length,
|
|
7326
|
-
lastMessageId: lastMessage.id,
|
|
7327
|
-
lastMessageAt: lastMessage.updatedAt ?? lastMessage.createdAt,
|
|
7328
|
-
metadata: metadataRecord(root.metadata),
|
|
7329
|
-
createdAt: root.createdAt,
|
|
7330
|
-
updatedAt: lastMessage.updatedAt ?? lastMessage.createdAt,
|
|
7331
|
-
newsroomFeedKey: "messages",
|
|
7332
|
-
messages,
|
|
7333
|
-
scope: "insight",
|
|
7334
|
-
sectionKey: root.messageDomain ?? null,
|
|
7335
|
-
};
|
|
7336
|
-
}
|
|
7337
|
-
|
|
7338
6951
|
function MessagesDeskView({
|
|
7339
6952
|
assignments,
|
|
7340
6953
|
graph,
|
|
@@ -7941,7 +7554,6 @@ function ForumThreadIndex({
|
|
|
7941
7554
|
emptyLabel,
|
|
7942
7555
|
toolbar,
|
|
7943
7556
|
composer,
|
|
7944
|
-
variant = "edition",
|
|
7945
7557
|
onOpenThread,
|
|
7946
7558
|
}: {
|
|
7947
7559
|
threads: ForumThreadWithMessages[];
|
|
@@ -7951,7 +7563,6 @@ function ForumThreadIndex({
|
|
|
7951
7563
|
emptyLabel: string;
|
|
7952
7564
|
toolbar?: ReactNode;
|
|
7953
7565
|
composer?: ReactNode;
|
|
7954
|
-
variant?: "edition" | "insight";
|
|
7955
7566
|
onOpenThread: (threadId: string) => void;
|
|
7956
7567
|
}) {
|
|
7957
7568
|
return (
|
|
@@ -7963,7 +7574,7 @@ function ForumThreadIndex({
|
|
|
7963
7574
|
<thead>
|
|
7964
7575
|
<tr>
|
|
7965
7576
|
<th>Topic</th>
|
|
7966
|
-
<th>
|
|
7577
|
+
<th>Scope</th>
|
|
7967
7578
|
<th>Replies</th>
|
|
7968
7579
|
<th>Last Post</th>
|
|
7969
7580
|
</tr>
|
|
@@ -7976,11 +7587,9 @@ function ForumThreadIndex({
|
|
|
7976
7587
|
);
|
|
7977
7588
|
const lastMessage = sortedMessages.at(-1) ?? null;
|
|
7978
7589
|
const replies = Math.max(activeMessages.length - 1, 0);
|
|
7979
|
-
const scopeLabel = thread.scope === "
|
|
7980
|
-
?
|
|
7981
|
-
: thread
|
|
7982
|
-
? "Edition"
|
|
7983
|
-
: `Section: ${normalizeForumThreadSectionLabel(thread, sections)}`;
|
|
7590
|
+
const scopeLabel = thread.scope === "edition"
|
|
7591
|
+
? "Edition"
|
|
7592
|
+
: `Section: ${normalizeForumThreadSectionLabel(thread, sections)}`;
|
|
7984
7593
|
return (
|
|
7985
7594
|
<tr key={thread.id}>
|
|
7986
7595
|
<td>
|
|
@@ -8094,7 +7703,6 @@ function ForumThreadView({
|
|
|
8094
7703
|
deletingMessageId,
|
|
8095
7704
|
focusMessageId = null,
|
|
8096
7705
|
isDemo = false,
|
|
8097
|
-
variant = "edition",
|
|
8098
7706
|
onBack,
|
|
8099
7707
|
onReplyTarget,
|
|
8100
7708
|
onDeleteMessage,
|
|
@@ -8112,7 +7720,6 @@ function ForumThreadView({
|
|
|
8112
7720
|
deletingMessageId?: string;
|
|
8113
7721
|
focusMessageId?: string | null;
|
|
8114
7722
|
isDemo?: boolean;
|
|
8115
|
-
variant?: "edition" | "insight";
|
|
8116
7723
|
onBack: () => void;
|
|
8117
7724
|
onReplyTarget: (messageId: string) => void;
|
|
8118
7725
|
onDeleteMessage: (messageId: string) => void;
|
|
@@ -8125,9 +7732,7 @@ function ForumThreadView({
|
|
|
8125
7732
|
(left, right) => Number(left.sequenceNumber ?? 0) - Number(right.sequenceNumber ?? 0),
|
|
8126
7733
|
);
|
|
8127
7734
|
const visibleCount = messages.length;
|
|
8128
|
-
const threadShareUrl =
|
|
8129
|
-
? buildInsightThreadUrl(thread.id, { demo: isDemo })
|
|
8130
|
-
: buildForumThreadUrl(thread.id, { demo: isDemo });
|
|
7735
|
+
const threadShareUrl = buildForumThreadUrl(thread.id, { demo: isDemo });
|
|
8131
7736
|
|
|
8132
7737
|
useEffect(() => {
|
|
8133
7738
|
if (!focusMessageId || typeof window === "undefined") return;
|
|
@@ -8156,11 +7761,9 @@ function ForumThreadView({
|
|
|
8156
7761
|
Back to Threads
|
|
8157
7762
|
</button>
|
|
8158
7763
|
<p className="story-label">
|
|
8159
|
-
{thread.scope === "
|
|
8160
|
-
?
|
|
8161
|
-
: thread
|
|
8162
|
-
? "Edition Thread"
|
|
8163
|
-
: `Section Thread: ${normalizeForumThreadSectionLabel(thread, sections)}`}
|
|
7764
|
+
{thread.scope === "edition"
|
|
7765
|
+
? "Edition Thread"
|
|
7766
|
+
: `Section Thread: ${normalizeForumThreadSectionLabel(thread, sections)}`}
|
|
8164
7767
|
</p>
|
|
8165
7768
|
<h3>{thread.title}</h3>
|
|
8166
7769
|
</div>
|
|
@@ -8200,9 +7803,7 @@ function ForumThreadView({
|
|
|
8200
7803
|
</DropdownMenuItem>
|
|
8201
7804
|
<DropdownMenuItem
|
|
8202
7805
|
onSelect={() => {
|
|
8203
|
-
const url =
|
|
8204
|
-
? buildInsightThreadUrl(thread.id, { messageId: message.id, demo: isDemo })
|
|
8205
|
-
: buildForumThreadUrl(thread.id, { messageId: message.id, demo: isDemo });
|
|
7806
|
+
const url = buildForumThreadUrl(thread.id, { messageId: message.id, demo: isDemo });
|
|
8206
7807
|
void copyShareLink(url);
|
|
8207
7808
|
}}
|
|
8208
7809
|
>
|
|
@@ -8505,7 +8106,7 @@ function NewsroomListDetailShell({
|
|
|
8505
8106
|
list: ReactNode;
|
|
8506
8107
|
onCloseDetail?: () => void;
|
|
8507
8108
|
selectionScrollKey?: string | null;
|
|
8508
|
-
sectionKey: "assignments" | "concepts" | "
|
|
8109
|
+
sectionKey: "assignments" | "concepts" | "messages" | "references" | "topics";
|
|
8509
8110
|
utilityActions?: NewsroomDetailAction[];
|
|
8510
8111
|
}) {
|
|
8511
8112
|
const [detailMode, setDetailMode] = useState<"split" | "full">("split");
|
|
@@ -9017,7 +8618,7 @@ function LibraryBigIcon() {
|
|
|
9017
8618
|
);
|
|
9018
8619
|
}
|
|
9019
8620
|
|
|
9020
|
-
function
|
|
8621
|
+
function NoteIcon() {
|
|
9021
8622
|
return (
|
|
9022
8623
|
<svg
|
|
9023
8624
|
aria-hidden="true"
|
|
@@ -9478,7 +9079,7 @@ function SemanticDetailPanel({
|
|
|
9478
9079
|
}
|
|
9479
9080
|
|
|
9480
9081
|
const messages = graph.messagesFor(selected.kind, selected.lineageId);
|
|
9481
|
-
const
|
|
9082
|
+
const notes = graph.notesFor(selected.kind, selected.lineageId);
|
|
9482
9083
|
const attachments = selected.kind === "reference" ? graph.attachmentsForReference(selected.lineageId) : [];
|
|
9483
9084
|
const neighborGroups = graph.neighbors(selected.kind, selected.lineageId);
|
|
9484
9085
|
const selectedReference = selected.kind === "reference" ? selected.record as ReferenceRecord : null;
|
|
@@ -9552,8 +9153,8 @@ function SemanticDetailPanel({
|
|
|
9552
9153
|
) : null}
|
|
9553
9154
|
</div>
|
|
9554
9155
|
) : null}
|
|
9555
|
-
{
|
|
9556
|
-
<
|
|
9156
|
+
{notes.length ? (
|
|
9157
|
+
<NoteMessageBlock notes={notes} />
|
|
9557
9158
|
) : null}
|
|
9558
9159
|
{messages.length ? (
|
|
9559
9160
|
<div className="news-desk-detail-block">
|
|
@@ -9757,11 +9358,11 @@ function useNewsroomKnowledgeContext(target: KnowledgeQueryTarget | null) {
|
|
|
9757
9358
|
};
|
|
9758
9359
|
}
|
|
9759
9360
|
|
|
9760
|
-
function
|
|
9761
|
-
target:
|
|
9361
|
+
function useNewsroomNoteComposer(
|
|
9362
|
+
target: NoteTarget | null,
|
|
9762
9363
|
disabled: boolean,
|
|
9763
|
-
onCreate: (target:
|
|
9764
|
-
):
|
|
9364
|
+
onCreate: (target: NoteTarget, summary: string, body: string) => Promise<void>,
|
|
9365
|
+
): NoteComposerControl {
|
|
9765
9366
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
9766
9367
|
const [summary, setSummary] = useState("");
|
|
9767
9368
|
const [body, setBody] = useState("");
|
|
@@ -9770,7 +9371,7 @@ function useNewsroomInsightComposer(
|
|
|
9770
9371
|
loading: false,
|
|
9771
9372
|
targetKey: null,
|
|
9772
9373
|
});
|
|
9773
|
-
const targetKey = target ?
|
|
9374
|
+
const targetKey = target ? noteTargetKey(target) : null;
|
|
9774
9375
|
|
|
9775
9376
|
useEffect(() => {
|
|
9776
9377
|
setDialogOpen(false);
|
|
@@ -9784,7 +9385,7 @@ function useNewsroomInsightComposer(
|
|
|
9784
9385
|
const cleanSummary = summary.trim();
|
|
9785
9386
|
const cleanBody = body.trim();
|
|
9786
9387
|
if (!cleanSummary || !cleanBody) {
|
|
9787
|
-
setState({ error: "
|
|
9388
|
+
setState({ error: "Note summary and body are required.", loading: false, targetKey });
|
|
9788
9389
|
return;
|
|
9789
9390
|
}
|
|
9790
9391
|
setState({ error: null, loading: true, targetKey });
|
|
@@ -9796,7 +9397,7 @@ function useNewsroomInsightComposer(
|
|
|
9796
9397
|
setBody("");
|
|
9797
9398
|
} catch (error) {
|
|
9798
9399
|
setState({
|
|
9799
|
-
error: error instanceof Error ? error.message : "Could not save
|
|
9400
|
+
error: error instanceof Error ? error.message : "Could not save note.",
|
|
9800
9401
|
loading: false,
|
|
9801
9402
|
targetKey,
|
|
9802
9403
|
});
|
|
@@ -9808,15 +9409,15 @@ function useNewsroomInsightComposer(
|
|
|
9808
9409
|
|
|
9809
9410
|
return {
|
|
9810
9411
|
action: {
|
|
9811
|
-
ariaLabel: "Add
|
|
9412
|
+
ariaLabel: "Add note",
|
|
9812
9413
|
disabled: disabled || !target || loading,
|
|
9813
|
-
icon: <
|
|
9814
|
-
key: "add-
|
|
9815
|
-
label: loading ? "Saving
|
|
9414
|
+
icon: <NoteIcon />,
|
|
9415
|
+
key: "add-note",
|
|
9416
|
+
label: loading ? "Saving Note" : "Add Note",
|
|
9816
9417
|
onSelect: () => setDialogOpen(true),
|
|
9817
9418
|
} satisfies NewsroomDetailAction,
|
|
9818
9419
|
dialog: target ? (
|
|
9819
|
-
<
|
|
9420
|
+
<NoteComposerDialog
|
|
9820
9421
|
body={body}
|
|
9821
9422
|
disabled={disabled || loading}
|
|
9822
9423
|
error={error}
|
|
@@ -10155,7 +9756,7 @@ function KnowledgeQueryDialog({
|
|
|
10155
9756
|
);
|
|
10156
9757
|
}
|
|
10157
9758
|
|
|
10158
|
-
function
|
|
9759
|
+
function NoteComposerDialog({
|
|
10159
9760
|
body,
|
|
10160
9761
|
disabled,
|
|
10161
9762
|
error,
|
|
@@ -10176,7 +9777,7 @@ function InsightComposerDialog({
|
|
|
10176
9777
|
onSummaryChange: (value: string) => void;
|
|
10177
9778
|
open: boolean;
|
|
10178
9779
|
summary: string;
|
|
10179
|
-
target:
|
|
9780
|
+
target: NoteTarget;
|
|
10180
9781
|
}) {
|
|
10181
9782
|
useEffect(() => {
|
|
10182
9783
|
if (!open) return;
|
|
@@ -10193,16 +9794,16 @@ function InsightComposerDialog({
|
|
|
10193
9794
|
return (
|
|
10194
9795
|
<div
|
|
10195
9796
|
className="news-desk-modal"
|
|
10196
|
-
data-news-desk-
|
|
9797
|
+
data-news-desk-note-modal
|
|
10197
9798
|
onMouseDown={(event) => {
|
|
10198
9799
|
if (event.target === event.currentTarget && !disabled) onClose();
|
|
10199
9800
|
}}
|
|
10200
9801
|
>
|
|
10201
|
-
<div className="news-desk-modal__dialog news-desk-modal__dialog--knowledge-query" role="dialog" aria-modal="true" aria-labelledby="
|
|
9802
|
+
<div className="news-desk-modal__dialog news-desk-modal__dialog--knowledge-query" role="dialog" aria-modal="true" aria-labelledby="note-composer-title">
|
|
10202
9803
|
<header className="news-desk-modal__header">
|
|
10203
9804
|
<div>
|
|
10204
|
-
<p className="story-label">
|
|
10205
|
-
<h3 id="
|
|
9805
|
+
<p className="story-label">Note</p>
|
|
9806
|
+
<h3 id="note-composer-title">{target.title}</h3>
|
|
10206
9807
|
<span>{target.kind} / {target.lineageId}</span>
|
|
10207
9808
|
</div>
|
|
10208
9809
|
<button type="button" disabled={disabled} onClick={onClose}>Close</button>
|
|
@@ -10217,7 +9818,7 @@ function InsightComposerDialog({
|
|
|
10217
9818
|
/>
|
|
10218
9819
|
</label>
|
|
10219
9820
|
<label>
|
|
10220
|
-
<span>
|
|
9821
|
+
<span>Note Body</span>
|
|
10221
9822
|
<textarea
|
|
10222
9823
|
disabled={disabled}
|
|
10223
9824
|
rows={8}
|
|
@@ -10230,7 +9831,7 @@ function InsightComposerDialog({
|
|
|
10230
9831
|
<footer className="news-desk-modal__actions">
|
|
10231
9832
|
<button type="button" disabled={disabled} onClick={onClose}>Cancel</button>
|
|
10232
9833
|
<button type="button" className="news-desk-assignment-create-button" disabled={disabled || !summary.trim() || !body.trim()} onClick={onRun}>
|
|
10233
|
-
{disabled ? "Saving" : "Save
|
|
9834
|
+
{disabled ? "Saving" : "Save Note"}
|
|
10234
9835
|
</button>
|
|
10235
9836
|
</footer>
|
|
10236
9837
|
</div>
|
|
@@ -10977,14 +10578,14 @@ function PayloadDetailBlock({
|
|
|
10977
10578
|
);
|
|
10978
10579
|
}
|
|
10979
10580
|
|
|
10980
|
-
function
|
|
10581
|
+
function NoteMessageBlock({ notes }: { notes: MessageRecord[] }) {
|
|
10981
10582
|
return (
|
|
10982
10583
|
<div className="news-desk-detail-block">
|
|
10983
|
-
<p className="story-label">
|
|
10984
|
-
{
|
|
10584
|
+
<p className="story-label">Notes</p>
|
|
10585
|
+
{notes.slice(0, 6).map((message) => (
|
|
10985
10586
|
<div className="news-desk-detail-line" key={message.id}>
|
|
10986
10587
|
<span>{message.authorLabel ?? "knowledge"}</span>
|
|
10987
|
-
<strong>{message.summary ?? "Stored
|
|
10588
|
+
<strong>{message.summary ?? "Stored note"}</strong>
|
|
10988
10589
|
</div>
|
|
10989
10590
|
))}
|
|
10990
10591
|
</div>
|
|
@@ -10998,7 +10599,7 @@ function ReferenceDetailPanel({
|
|
|
10998
10599
|
curation,
|
|
10999
10600
|
disabled,
|
|
11000
10601
|
graph,
|
|
11001
|
-
|
|
10602
|
+
onCreateNote,
|
|
11002
10603
|
onMoveCorpus,
|
|
11003
10604
|
onReview,
|
|
11004
10605
|
onReviewTopicLabel,
|
|
@@ -11016,7 +10617,7 @@ function ReferenceDetailPanel({
|
|
|
11016
10617
|
curation: ReferenceCurationDisplayState | null;
|
|
11017
10618
|
disabled: boolean;
|
|
11018
10619
|
graph: SemanticGraph;
|
|
11019
|
-
|
|
10620
|
+
onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
|
|
11020
10621
|
onMoveCorpus: (reference: ReferenceRecord, corpusId: string) => void;
|
|
11021
10622
|
onReview: (action: ReferenceCurationAction) => void;
|
|
11022
10623
|
onReviewTopicLabel: (input: { action: TopicLabelAction; category: CategorySteeringCategory; note?: string | null; reference: ReferenceRecord; sourceRelationId?: string | null }) => void;
|
|
@@ -11295,7 +10896,7 @@ function ReferenceDetailPanel({
|
|
|
11295
10896
|
|
|
11296
10897
|
const lineageId = referenceLineageId;
|
|
11297
10898
|
const messages = graph.messagesFor("reference", lineageId);
|
|
11298
|
-
const
|
|
10899
|
+
const notes = graph.notesFor("reference", lineageId);
|
|
11299
10900
|
const neighborGroups = graph.neighbors("reference", lineageId);
|
|
11300
10901
|
const fallbackOutgoingCitationGroup = neighborGroups.find((group) => group.direction === "outgoing" && group.predicate === "cites") ?? null;
|
|
11301
10902
|
const fallbackIncomingCitationGroup = neighborGroups.find((group) => group.direction === "incoming" && group.predicate === "cites") ?? null;
|
|
@@ -11414,10 +11015,10 @@ function ReferenceDetailPanel({
|
|
|
11414
11015
|
reference={reference}
|
|
11415
11016
|
semanticRelations={semanticRelations}
|
|
11416
11017
|
/>
|
|
11417
|
-
<
|
|
11018
|
+
<ReferenceNotePanel
|
|
11418
11019
|
disabled={disabled}
|
|
11419
|
-
|
|
11420
|
-
|
|
11020
|
+
notes={notes}
|
|
11021
|
+
onCreateNote={onCreateNote}
|
|
11421
11022
|
reference={reference}
|
|
11422
11023
|
/>
|
|
11423
11024
|
<div data-news-desk-reference-metadata-expander>
|
|
@@ -12307,49 +11908,49 @@ function ReferenceCurationStatusPanel({ status }: { status: ReferenceCurationRun
|
|
|
12307
11908
|
);
|
|
12308
11909
|
}
|
|
12309
11910
|
|
|
12310
|
-
function
|
|
11911
|
+
function ReferenceNotePanel({
|
|
12311
11912
|
disabled,
|
|
12312
|
-
|
|
12313
|
-
|
|
11913
|
+
notes,
|
|
11914
|
+
onCreateNote,
|
|
12314
11915
|
reference,
|
|
12315
11916
|
}: {
|
|
12316
11917
|
disabled: boolean;
|
|
12317
|
-
|
|
12318
|
-
|
|
11918
|
+
notes: MessageRecord[];
|
|
11919
|
+
onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
|
|
12319
11920
|
reference: ReferenceRecord;
|
|
12320
11921
|
}) {
|
|
12321
11922
|
const [composerOpen, setComposerOpen] = useState(false);
|
|
12322
11923
|
const [body, setBody] = useState("");
|
|
12323
11924
|
const [error, setError] = useState<string | null>(null);
|
|
12324
11925
|
const [saving, setSaving] = useState(false);
|
|
12325
|
-
const target = useMemo(() =>
|
|
12326
|
-
const
|
|
11926
|
+
const target = useMemo(() => noteTargetForReference(reference), [reference]);
|
|
11927
|
+
const hasNotes = notes.length > 0;
|
|
12327
11928
|
|
|
12328
11929
|
return (
|
|
12329
|
-
<section className="news-desk-reference-workflow" data-reference-
|
|
11930
|
+
<section className="news-desk-reference-workflow" data-reference-note-workflow>
|
|
12330
11931
|
<header className="news-desk-reference-workflow__header">
|
|
12331
|
-
<p className="story-label">
|
|
12332
|
-
{!
|
|
11932
|
+
<p className="story-label">Notes</p>
|
|
11933
|
+
{!hasNotes && !composerOpen ? (
|
|
12333
11934
|
<button
|
|
12334
11935
|
type="button"
|
|
12335
|
-
data-news-desk-reference-
|
|
11936
|
+
data-news-desk-reference-note-trigger
|
|
12336
11937
|
disabled={disabled || saving}
|
|
12337
11938
|
onClick={() => {
|
|
12338
11939
|
setComposerOpen(true);
|
|
12339
11940
|
setError(null);
|
|
12340
11941
|
}}
|
|
12341
11942
|
>
|
|
12342
|
-
Add
|
|
11943
|
+
Add note
|
|
12343
11944
|
</button>
|
|
12344
11945
|
) : null}
|
|
12345
11946
|
</header>
|
|
12346
|
-
<div className="news-desk-reference-
|
|
12347
|
-
{
|
|
12348
|
-
<div className="news-desk-reference-
|
|
12349
|
-
{
|
|
11947
|
+
<div className="news-desk-reference-note-shell" data-reference-note-input>
|
|
11948
|
+
{hasNotes ? (
|
|
11949
|
+
<div className="news-desk-reference-note-list" data-reference-note-state>
|
|
11950
|
+
{notes.map((message) => (
|
|
12350
11951
|
<article className="news-desk-reference-topic-state-row" key={message.id}>
|
|
12351
11952
|
<div className="news-desk-reference-topic-state-row__headline">
|
|
12352
|
-
<strong>{message.summary ?? "Stored
|
|
11953
|
+
<strong>{message.summary ?? "Stored note"}</strong>
|
|
12353
11954
|
<span>{message.authorLabel ?? message.source ?? "knowledge"}</span>
|
|
12354
11955
|
</div>
|
|
12355
11956
|
<div className="news-desk-reference-topic-state-row__meta">
|
|
@@ -12359,35 +11960,35 @@ function ReferenceInsightPanel({
|
|
|
12359
11960
|
))}
|
|
12360
11961
|
</div>
|
|
12361
11962
|
) : null}
|
|
12362
|
-
{
|
|
11963
|
+
{hasNotes && !composerOpen ? (
|
|
12363
11964
|
<button
|
|
12364
11965
|
type="button"
|
|
12365
|
-
data-news-desk-reference-
|
|
11966
|
+
data-news-desk-reference-note-trigger
|
|
12366
11967
|
disabled={disabled || saving}
|
|
12367
11968
|
onClick={() => {
|
|
12368
11969
|
setComposerOpen(true);
|
|
12369
11970
|
setError(null);
|
|
12370
11971
|
}}
|
|
12371
11972
|
>
|
|
12372
|
-
Add
|
|
11973
|
+
Add note
|
|
12373
11974
|
</button>
|
|
12374
11975
|
) : null}
|
|
12375
11976
|
{composerOpen ? (
|
|
12376
11977
|
<form
|
|
12377
|
-
className="news-desk-reference-
|
|
12378
|
-
data-news-desk-reference-
|
|
11978
|
+
className="news-desk-reference-note-form"
|
|
11979
|
+
data-news-desk-reference-note-form
|
|
12379
11980
|
onSubmit={(event) => {
|
|
12380
11981
|
event.preventDefault();
|
|
12381
11982
|
if (disabled || saving) return;
|
|
12382
11983
|
const cleanBody = body.trim();
|
|
12383
11984
|
if (!cleanBody) {
|
|
12384
|
-
setError("
|
|
11985
|
+
setError("Note text is required.");
|
|
12385
11986
|
return;
|
|
12386
11987
|
}
|
|
12387
|
-
const cleanSummary =
|
|
11988
|
+
const cleanSummary = summarizeNoteForStorage(cleanBody);
|
|
12388
11989
|
setSaving(true);
|
|
12389
11990
|
setError(null);
|
|
12390
|
-
void
|
|
11991
|
+
void onCreateNote(target, cleanSummary, cleanBody)
|
|
12391
11992
|
.then(() => {
|
|
12392
11993
|
setSaving(false);
|
|
12393
11994
|
setBody("");
|
|
@@ -12395,12 +11996,12 @@ function ReferenceInsightPanel({
|
|
|
12395
11996
|
})
|
|
12396
11997
|
.catch((submitError) => {
|
|
12397
11998
|
setSaving(false);
|
|
12398
|
-
setError(submitError instanceof Error ? submitError.message : "Could not save
|
|
11999
|
+
setError(submitError instanceof Error ? submitError.message : "Could not save note.");
|
|
12399
12000
|
});
|
|
12400
12001
|
}}
|
|
12401
12002
|
>
|
|
12402
12003
|
<label className="news-desk-reference-curation-note">
|
|
12403
|
-
<span>
|
|
12004
|
+
<span>Note</span>
|
|
12404
12005
|
<textarea
|
|
12405
12006
|
disabled={disabled || saving}
|
|
12406
12007
|
value={body}
|
|
@@ -12408,9 +12009,9 @@ function ReferenceInsightPanel({
|
|
|
12408
12009
|
/>
|
|
12409
12010
|
</label>
|
|
12410
12011
|
{error ? <p className="news-desk-detail-copy">{error}</p> : null}
|
|
12411
|
-
<div className="news-desk-reference-
|
|
12012
|
+
<div className="news-desk-reference-note-form__actions">
|
|
12412
12013
|
<button type="submit" disabled={disabled || saving || !body.trim()}>
|
|
12413
|
-
{saving ? "Saving" : "Save
|
|
12014
|
+
{saving ? "Saving" : "Save note"}
|
|
12414
12015
|
</button>
|
|
12415
12016
|
<button
|
|
12416
12017
|
type="button"
|
|
@@ -12431,9 +12032,9 @@ function ReferenceInsightPanel({
|
|
|
12431
12032
|
);
|
|
12432
12033
|
}
|
|
12433
12034
|
|
|
12434
|
-
function
|
|
12035
|
+
function summarizeNoteForStorage(body: string): string {
|
|
12435
12036
|
const normalized = body.trim().replace(/\s+/g, " ");
|
|
12436
|
-
if (!normalized) return "
|
|
12037
|
+
if (!normalized) return "Note";
|
|
12437
12038
|
const firstSentenceMatch = normalized.match(/(.+?[.!?])(?:\s|$)/);
|
|
12438
12039
|
const candidate = (firstSentenceMatch?.[1] ?? normalized).trim();
|
|
12439
12040
|
if (candidate.length <= 120) return candidate;
|
|
@@ -12767,7 +12368,7 @@ function ReportingStoryBudgetBoard({
|
|
|
12767
12368
|
section={section}
|
|
12768
12369
|
selectedAssignmentId={selectedAssignmentId}
|
|
12769
12370
|
/>
|
|
12770
|
-
)) : <EmptyRow label="No reporting
|
|
12371
|
+
)) : <EmptyRow label="No reporting candidate assignments found" />}
|
|
12771
12372
|
</div>
|
|
12772
12373
|
);
|
|
12773
12374
|
}
|
|
@@ -13263,7 +12864,7 @@ function buildUiAnalysisReindexAssignmentPlan({
|
|
|
13263
12864
|
createdBy: actorLabel,
|
|
13264
12865
|
createdAt: now,
|
|
13265
12866
|
updatedAt: now,
|
|
13266
|
-
newsroomFeedKey: "
|
|
12867
|
+
newsroomFeedKey: "operations",
|
|
13267
12868
|
metadata,
|
|
13268
12869
|
};
|
|
13269
12870
|
const event: AssignmentEventRecord = {
|
|
@@ -13298,7 +12899,7 @@ function buildUiAnalysisReindexAssignmentPlan({
|
|
|
13298
12899
|
return { assignment, event, relation, metadata };
|
|
13299
12900
|
}
|
|
13300
12901
|
|
|
13301
|
-
function
|
|
12902
|
+
function noteTargetForReference(reference: ReferenceRecord): NoteTarget {
|
|
13302
12903
|
return {
|
|
13303
12904
|
kind: "reference",
|
|
13304
12905
|
id: reference.id,
|
|
@@ -13309,7 +12910,7 @@ function insightTargetForReference(reference: ReferenceRecord): InsightTarget {
|
|
|
13309
12910
|
};
|
|
13310
12911
|
}
|
|
13311
12912
|
|
|
13312
|
-
function
|
|
12913
|
+
function noteTargetForNewsroomSection(section: NewsroomSectionRecord): NoteTarget {
|
|
13313
12914
|
return {
|
|
13314
12915
|
kind: "newsroomSection",
|
|
13315
12916
|
id: section.id,
|
|
@@ -13320,7 +12921,7 @@ function insightTargetForNewsroomSection(section: NewsroomSectionRecord): Insigh
|
|
|
13320
12921
|
};
|
|
13321
12922
|
}
|
|
13322
12923
|
|
|
13323
|
-
function
|
|
12924
|
+
function noteTargetForSemanticObject(object: SemanticObjectSummary): NoteTarget | null {
|
|
13324
12925
|
if (object.kind !== "category" && object.kind !== "semanticNode") return null;
|
|
13325
12926
|
return {
|
|
13326
12927
|
kind: object.kind,
|
|
@@ -13332,21 +12933,21 @@ function insightTargetForSemanticObject(object: SemanticObjectSummary): InsightT
|
|
|
13332
12933
|
};
|
|
13333
12934
|
}
|
|
13334
12935
|
|
|
13335
|
-
function
|
|
12936
|
+
function noteTargetKey(target: NoteTarget): string {
|
|
13336
12937
|
return `${target.kind}#${target.lineageId}`;
|
|
13337
12938
|
}
|
|
13338
12939
|
|
|
13339
|
-
function
|
|
12940
|
+
function buildUiNoteRelation(message: MessageRecord, target: NoteTarget, now: string): SemanticRelationRecord {
|
|
13340
12941
|
const subjectStateKey = semanticStateKey("message", message.id);
|
|
13341
12942
|
const objectStateKey = semanticStateKey(target.kind, target.lineageId);
|
|
13342
12943
|
const subjectVersionKey = semanticVersionKey("message", message.id);
|
|
13343
12944
|
const objectVersionKey = semanticVersionKey(target.kind, target.id);
|
|
13344
12945
|
return {
|
|
13345
|
-
id: `semantic-relation-${hashUiKey([subjectVersionKey, "
|
|
12946
|
+
id: `semantic-relation-${hashUiKey([subjectVersionKey, "note_about", objectVersionKey])}`,
|
|
13346
12947
|
relationState: "current",
|
|
13347
|
-
predicate: "
|
|
13348
|
-
relationTypeId: "semantic-relation-type-
|
|
13349
|
-
relationTypeKey: "
|
|
12948
|
+
predicate: "note_about",
|
|
12949
|
+
relationTypeId: "semantic-relation-type-note-about",
|
|
12950
|
+
relationTypeKey: "note_about",
|
|
13350
12951
|
relationDomain: "knowledge",
|
|
13351
12952
|
subjectKind: "message",
|
|
13352
12953
|
subjectId: message.id,
|
|
@@ -13359,7 +12960,7 @@ function buildUiInsightRelation(message: MessageRecord, target: InsightTarget, n
|
|
|
13359
12960
|
subjectStateKey,
|
|
13360
12961
|
objectStateKey,
|
|
13361
12962
|
objectSubjectStateKey: `${objectStateKey}#message`,
|
|
13362
|
-
predicateObjectStateKey: `
|
|
12963
|
+
predicateObjectStateKey: `note_about#${objectStateKey}`,
|
|
13363
12964
|
subjectVersionKey,
|
|
13364
12965
|
objectVersionKey,
|
|
13365
12966
|
score: null,
|
|
@@ -13664,7 +13265,7 @@ function signupPluginEnabled(): boolean {
|
|
|
13664
13265
|
}
|
|
13665
13266
|
|
|
13666
13267
|
function normalizeAdministrationPanel(value: string | null | undefined): AdministrationPanel {
|
|
13667
|
-
if (value === "policies" || value === "sections" || value === "procedures") return value;
|
|
13268
|
+
if (value === "policies" || value === "sections" || value === "procedures" || value === "delegations") return value;
|
|
13668
13269
|
if (value === "signups" && signupPluginEnabled()) return value;
|
|
13669
13270
|
return "users";
|
|
13670
13271
|
}
|
|
@@ -13856,10 +13457,10 @@ function compareDoctrineCategories(left: DoctrineCategory, right: DoctrineCatego
|
|
|
13856
13457
|
|
|
13857
13458
|
function formatDeskSectionHeadline(section: NewsDeskTab): string {
|
|
13858
13459
|
if (section === "search") return "Search";
|
|
13859
|
-
if (section === "
|
|
13460
|
+
if (section === "desks") return "Taxonomy";
|
|
13461
|
+
if (section === "topics") return "Topic proposals";
|
|
13860
13462
|
if (section === "concepts") return "Concepts";
|
|
13861
13463
|
if (section === "references") return "References";
|
|
13862
|
-
if (section === "insights") return "Insights";
|
|
13863
13464
|
if (section === "messages") return "Messages";
|
|
13864
13465
|
if (section === "assignments") return "Assignments";
|
|
13865
13466
|
if (section === "administration") return "Administration";
|
|
@@ -13868,13 +13469,13 @@ function formatDeskSectionHeadline(section: NewsDeskTab): string {
|
|
|
13868
13469
|
|
|
13869
13470
|
function formatDeskSectionLede(section: NewsDeskTab): string {
|
|
13870
13471
|
if (section === "search") return "Search the newsroom knowledge base with semantic and ontology context.";
|
|
13871
|
-
if (section === "
|
|
13472
|
+
if (section === "desks") return "The desks and topic categories that organize coverage, with each desk's mission and policies.";
|
|
13473
|
+
if (section === "topics") return "Review suggested changes to the subject areas the newsroom covers.";
|
|
13872
13474
|
if (section === "concepts") return "Browse people, organizations, places, and ideas found in the source material.";
|
|
13873
13475
|
if (section === "references") return "Review source materials before they become usable evidence.";
|
|
13874
|
-
if (section === "insights") return "Browse recent research insights and discuss each one in a forum thread to refine the synthesis.";
|
|
13875
13476
|
if (section === "messages") return "Read notes, rationales, forum threads, curation decisions, and other work products from people and agents.";
|
|
13876
13477
|
if (section === "assignments") return "Create, filter, claim, and complete newsroom work.";
|
|
13877
|
-
if (section === "administration") return "Manage users, roles, doctrine,
|
|
13478
|
+
if (section === "administration") return "Manage users, roles, doctrine, and newsroom procedures.";
|
|
13878
13479
|
return "";
|
|
13879
13480
|
}
|
|
13880
13481
|
|
|
@@ -14967,7 +14568,7 @@ function buildUiReportingPacketReviewPlan({
|
|
|
14967
14568
|
event: AssignmentEventRecord;
|
|
14968
14569
|
relations: SemanticRelationRecord[];
|
|
14969
14570
|
} {
|
|
14970
|
-
if (assignment.assignmentTypeKey !== "reporting.
|
|
14571
|
+
if (assignment.assignmentTypeKey !== "reporting.candidate") throw new Error("Only reporting candidate assignments can review reporting packets.");
|
|
14971
14572
|
if (message.messageKind !== "reporting_context_packet") throw new Error("Only reporting context packets can be reviewed.");
|
|
14972
14573
|
if (decision === "merge" && !targetItem?.id && !targetItemId.trim()) throw new Error("Merge Packet requires a target Item ID.");
|
|
14973
14574
|
const resolvedTargetItem = targetItem ?? (targetItemId.trim() ? { id: targetItemId.trim(), lineageId: targetItemId.trim(), versionNumber: null } : null);
|
|
@@ -16503,21 +16104,21 @@ function assertReferenceQualityMutationSucceeded(
|
|
|
16503
16104
|
return response.data;
|
|
16504
16105
|
}
|
|
16505
16106
|
|
|
16506
|
-
function
|
|
16507
|
-
response:
|
|
16107
|
+
function assertReferenceNoteMutationSucceeded(
|
|
16108
|
+
response: ReferenceNoteResponse,
|
|
16508
16109
|
referenceId: string,
|
|
16509
|
-
): NonNullable<
|
|
16110
|
+
): NonNullable<ReferenceNoteResponse["data"]> {
|
|
16510
16111
|
if (response.errors?.length) {
|
|
16511
16112
|
throw new Error(response.errors.map(formatGraphQLError).join("; "));
|
|
16512
16113
|
}
|
|
16513
16114
|
if (!response.data?.ok) {
|
|
16514
|
-
throw new Error(`Reference
|
|
16115
|
+
throw new Error(`Reference note was not saved for ${referenceId}.`);
|
|
16515
16116
|
}
|
|
16516
16117
|
if (response.data.referenceId && response.data.referenceId !== referenceId) {
|
|
16517
|
-
throw new Error(`Reference
|
|
16118
|
+
throw new Error(`Reference note response did not match ${referenceId}.`);
|
|
16518
16119
|
}
|
|
16519
16120
|
if (!response.data.messageId || !response.data.relationId) {
|
|
16520
|
-
throw new Error(`Reference
|
|
16121
|
+
throw new Error(`Reference note saved without required audit relations for ${referenceId}.`);
|
|
16521
16122
|
}
|
|
16522
16123
|
return response.data;
|
|
16523
16124
|
}
|