@anthusai/papyrus 2.0.0-next.86 → 2.0.0-next.88
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/components/editorial-checks-admin-panel.tsx +2 -2
- package/components/newsroom-access-gate.tsx +1 -1
- package/components/newsroom-article-editor.tsx +5 -5
- package/components/newsroom-assignment-detail.tsx +6 -6
- package/components/newsroom-ops-overview.tsx +5 -5
- package/components/newsroom-organizer.css +2 -2
- package/components/notification-center.tsx +11 -7
- package/components/topic-steering-workspace.tsx +14 -14
- package/lib/category-repository.ts +3 -3
- package/lib/console-web-context.ts +1 -1
- package/lib/newsroom-demo-dashboard.ts +1 -1
- package/lib/newsroom-nav.ts +1 -1
- package/lib/review-client.ts +18 -0
- package/lib/review-requests.ts +6 -0
- package/package.json +1 -1
- package/routes.manifest.json +1 -1
|
@@ -218,7 +218,7 @@ export function EditorialChecksAdminPanel() {
|
|
|
218
218
|
{policies.map((policy) => (
|
|
219
219
|
<tr data-editorial-policy-row={policy.id} key={policy.id}>
|
|
220
220
|
<td>{policy.title}<br /><small>{policy.policyKey} · v{policy.version}</small></td>
|
|
221
|
-
<td>{policy.scope === "publication" ? "Whole publication" : `
|
|
221
|
+
<td>{policy.scope === "publication" ? "Whole publication" : `Section: ${policy.scope}`}</td>
|
|
222
222
|
<td>{policy.severity}</td>
|
|
223
223
|
<td>{policy.detectors.length === 0 ? "Guidance only" : policy.detectors.length}</td>
|
|
224
224
|
<td>
|
|
@@ -251,7 +251,7 @@ export function EditorialChecksAdminPanel() {
|
|
|
251
251
|
<label>Key <Input disabled={!draft.isNew} onChange={(event) => patchDraft({ policyKey: event.target.value })} value={draft.record.policyKey} /></label>
|
|
252
252
|
<label>Title <Input onChange={(event) => patchDraft({ title: event.target.value })} value={draft.record.title} /></label>
|
|
253
253
|
<label>
|
|
254
|
-
Applies to (publication, or a
|
|
254
|
+
Applies to (publication, or a section key){" "}
|
|
255
255
|
<Input disabled={!draft.isNew} onChange={(event) => patchDraft({ scope: event.target.value.trim() || "publication" })} value={draft.record.scope} />
|
|
256
256
|
</label>
|
|
257
257
|
<label>
|
|
@@ -57,7 +57,7 @@ export function NewsDeskAccessGate({ shell, showSectionTabs = false }: { shell:
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function formatAccessTitle(state: NewsDeskShellState | null): string {
|
|
60
|
-
if (!state || state.phase === "checkingAccess") return "Checking
|
|
60
|
+
if (!state || state.phase === "checkingAccess") return "Checking Credentials";
|
|
61
61
|
if (state.phase === "loadingDesk") return "Loading Newsroom Records";
|
|
62
62
|
if (state.phase === "forbidden") return "Editor Role Required";
|
|
63
63
|
if (state.phase === "error") return "Newsroom Unavailable";
|
|
@@ -670,21 +670,21 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
670
670
|
</Select>
|
|
671
671
|
</label>
|
|
672
672
|
<label className="space-y-1 text-sm font-medium">
|
|
673
|
-
<span>
|
|
673
|
+
<span>Section</span>
|
|
674
674
|
<Select
|
|
675
675
|
onValueChange={(value) => {
|
|
676
676
|
if (typeof value === "string") patchEditor({ section: value === NO_DESK_VALUE ? "" : value });
|
|
677
677
|
}}
|
|
678
678
|
value={deskOptions.some(option => option.value === editor.section) ? editor.section : NO_DESK_VALUE}
|
|
679
679
|
>
|
|
680
|
-
<SelectTrigger aria-label="
|
|
681
|
-
<SelectValue>{deskOptions.find(option => option.value === editor.section)?.label ?? "No
|
|
680
|
+
<SelectTrigger aria-label="Section" className="w-full" data-newsroom-article-section>
|
|
681
|
+
<SelectValue>{deskOptions.find(option => option.value === editor.section)?.label ?? "No section"}</SelectValue>
|
|
682
682
|
</SelectTrigger>
|
|
683
683
|
<SelectContent>
|
|
684
|
-
<SelectItem value={NO_DESK_VALUE}>No
|
|
684
|
+
<SelectItem value={NO_DESK_VALUE}>No section</SelectItem>
|
|
685
685
|
{(["core", "rotating"] as const).map((kind) => deskOptions.some(option => option.kind === kind) ? (
|
|
686
686
|
<SelectGroup key={kind}>
|
|
687
|
-
<SelectLabel>{kind === "core" ? "Core
|
|
687
|
+
<SelectLabel>{kind === "core" ? "Core sections" : "Rotating sections"}</SelectLabel>
|
|
688
688
|
{deskOptions.filter(option => option.kind === kind).map((option) => (
|
|
689
689
|
<SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>
|
|
690
690
|
))}
|
|
@@ -36,7 +36,7 @@ export function NewsroomAssignmentDetail({ assignment, deskOptions = [], onDeskC
|
|
|
36
36
|
const changeDesk = (value: string) => {
|
|
37
37
|
if (!onDeskChange) return;
|
|
38
38
|
setSavingDesk(true); setDeskError(null);
|
|
39
|
-
onDeskChange(value === NO_DESK_VALUE ? null : value).catch((reason: unknown) => setDeskError(reason instanceof Error ? reason.message : "Could not save the
|
|
39
|
+
onDeskChange(value === NO_DESK_VALUE ? null : value).catch((reason: unknown) => setDeskError(reason instanceof Error ? reason.message : "Could not save the section.")).finally(() => setSavingDesk(false));
|
|
40
40
|
};
|
|
41
41
|
const facts: DetailField[] = [
|
|
42
42
|
{ label: "Status", value: assignment.status },
|
|
@@ -58,14 +58,14 @@ export function NewsroomAssignmentDetail({ assignment, deskOptions = [], onDeskC
|
|
|
58
58
|
{facts.map(field => <div key={field.label}><dt>{field.label}</dt><dd>{field.value}</dd></div>)}
|
|
59
59
|
</dl>
|
|
60
60
|
{onDeskChange && <label className="newsroom-assignment-detail-desk">
|
|
61
|
-
<span>
|
|
61
|
+
<span>Section</span>
|
|
62
62
|
<Select onValueChange={(value) => { if (typeof value === "string") changeDesk(value); }} value={deskChoices.some(option => option.value === currentDesk) ? currentDesk : NO_DESK_VALUE} disabled={savingDesk}>
|
|
63
|
-
<SelectTrigger aria-label="
|
|
64
|
-
<SelectValue>{deskChoices.find(option => option.value === currentDesk)?.label ?? "No
|
|
63
|
+
<SelectTrigger aria-label="Section" className="w-full" data-newsroom-assignment-desk>
|
|
64
|
+
<SelectValue>{deskChoices.find(option => option.value === currentDesk)?.label ?? "No section"}</SelectValue>
|
|
65
65
|
</SelectTrigger>
|
|
66
66
|
<SelectContent>
|
|
67
|
-
<SelectItem value={NO_DESK_VALUE}>No
|
|
68
|
-
{(["core", "rotating"] as const).map(kind => deskChoices.some(option => option.kind === kind) ? <SelectGroup key={kind}><SelectLabel>{kind === "core" ? "Core
|
|
67
|
+
<SelectItem value={NO_DESK_VALUE}>No section</SelectItem>
|
|
68
|
+
{(["core", "rotating"] as const).map(kind => deskChoices.some(option => option.kind === kind) ? <SelectGroup key={kind}><SelectLabel>{kind === "core" ? "Core sections" : "Rotating sections"}</SelectLabel>{deskChoices.filter(option => option.kind === kind).map(option => <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>)}</SelectGroup> : null)}
|
|
69
69
|
</SelectContent>
|
|
70
70
|
</Select>
|
|
71
71
|
{currentDesk && !deskChoices.some(option => option.value === currentDesk) ? <small className="newsroom-desk-legacy-value">Old section value: {currentDesk}</small> : null}
|
|
@@ -76,7 +76,7 @@ export function NewsroomOpsOverview({
|
|
|
76
76
|
data-newsroom-ops-home="true"
|
|
77
77
|
>
|
|
78
78
|
<div className="space-y-8" data-newsroom-overview-feeds>
|
|
79
|
-
<div aria-label="Notification Center feed" className="newsroom-overview-feed-frame
|
|
79
|
+
<div aria-label="Notification Center feed" className="newsroom-overview-feed-frame rounded-xl border bg-card p-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" data-newsroom-overview-notifications role="region" tabIndex={0}>
|
|
80
80
|
<NotificationCenter
|
|
81
81
|
actorLabel={actorLabel}
|
|
82
82
|
decisions={decisions}
|
|
@@ -145,9 +145,9 @@ export function NewsroomOpsOverview({
|
|
|
145
145
|
</section>
|
|
146
146
|
|
|
147
147
|
{desks.length ? (
|
|
148
|
-
<section aria-label="
|
|
149
|
-
<h2 className="text-sm font-medium text-muted-foreground">
|
|
150
|
-
{coreDesks.length ? <h3 className="newsroom-desk-group-heading" data-newsroom-ops-desk-group="core">Core
|
|
148
|
+
<section aria-label="Sections" className="space-y-3" data-newsroom-ops-desks>
|
|
149
|
+
<h2 className="text-sm font-medium text-muted-foreground">Sections</h2>
|
|
150
|
+
{coreDesks.length ? <h3 className="newsroom-desk-group-heading" data-newsroom-ops-desk-group="core">Core sections</h3> : null}
|
|
151
151
|
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
152
152
|
{coreDesks.map((section) => (
|
|
153
153
|
<Link
|
|
@@ -167,7 +167,7 @@ export function NewsroomOpsOverview({
|
|
|
167
167
|
</Link>
|
|
168
168
|
))}
|
|
169
169
|
</div>
|
|
170
|
-
{rotatingDesks.length ? <h3 className="newsroom-desk-group-heading" data-newsroom-ops-desk-group="rotating">Rotating
|
|
170
|
+
{rotatingDesks.length ? <h3 className="newsroom-desk-group-heading" data-newsroom-ops-desk-group="rotating">Rotating sections <small>appear for a while, then rotate out</small></h3> : null}
|
|
171
171
|
{rotatingDesks.length ? <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
172
172
|
{rotatingDesks.map((section) => (
|
|
173
173
|
<Link
|
|
@@ -23,8 +23,8 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
|
|
|
23
23
|
.organizer-sidebar-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.organizer-sidebar-head .organizer-brand{flex:1;min-width:0}
|
|
24
24
|
.organizer-header-actions{display:flex;align-items:center;gap:8px;min-width:0;max-width:45%;overflow-x:auto}
|
|
25
25
|
.organizer-main{min-width:0;min-height:0;overflow:auto;overscroll-behavior:contain;flex:1;padding:16px 0}
|
|
26
|
-
.newsroom-overview-feed-frame{height:
|
|
27
|
-
@media(min-width:640px){.newsroom-overview-feed-frame{height:38rem}}
|
|
26
|
+
.newsroom-overview-feed-frame{height:auto}
|
|
27
|
+
@media(min-width:640px){.newsroom-overview-feed-frame{height:38rem;overflow-y:auto}}
|
|
28
28
|
.organizer-main>[data-category-steering]{min-height:0}.organizer-demo-notice{font-size:11px;color:var(--org-muted);margin-bottom:12px}
|
|
29
29
|
.organizer-account{display:flex;align-items:center;gap:10px;min-height:44px;flex-shrink:0;border:1px solid var(--org-line);padding:8px;text-align:left;border-radius:3px}
|
|
30
30
|
.organizer-account span{min-width:0}.organizer-account strong{display:block;font:600 11px var(--kb-theme-ui-font);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.organizer-account small{display:block;font-size:9px;color:var(--org-muted);margin-top:2px}
|
|
@@ -7,7 +7,7 @@ import { Button } from "./ui/button";
|
|
|
7
7
|
import { getNewsroomNavHref } from "../lib/newsroom-nav";
|
|
8
8
|
import {
|
|
9
9
|
cyclotronAutoDecided, cyclotronHeldForReview, formatWhen, isUnread, modeLabel, modeMark, nextText, sectionReviews,
|
|
10
|
-
steeringText, subjectHref, summarize, summaryText,
|
|
10
|
+
steeringText, subjectHref, subjectLabel, summarize, summaryText,
|
|
11
11
|
type AnswerInput, type CyclotronEntry, type ReviewRequest,
|
|
12
12
|
} from "../lib/review-requests";
|
|
13
13
|
import type { ReferenceRecord } from "../lib/category-repository";
|
|
@@ -22,6 +22,7 @@ type ViewProps = {
|
|
|
22
22
|
readIds: ReadonlySet<string>;
|
|
23
23
|
held: CyclotronEntry[];
|
|
24
24
|
autoDecided: CyclotronEntry[];
|
|
25
|
+
storyTitles?: Readonly<Record<string, string>>;
|
|
25
26
|
demo?: boolean;
|
|
26
27
|
busyId?: string | null;
|
|
27
28
|
error?: string | null;
|
|
@@ -30,8 +31,8 @@ type ViewProps = {
|
|
|
30
31
|
onMarkAllRead?: () => void;
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
function Entry({ request, unread, demo, busy, onAnswer, onMarkRead }: {
|
|
34
|
-
request: ReviewRequest; unread: boolean; demo: boolean; busy: boolean;
|
|
34
|
+
function Entry({ request, unread, demo, busy, titles, onAnswer, onMarkRead }: {
|
|
35
|
+
request: ReviewRequest; unread: boolean; demo: boolean; busy: boolean; titles: Readonly<Record<string, string>>;
|
|
35
36
|
onAnswer?: ViewProps["onAnswer"]; onMarkRead?: ViewProps["onMarkRead"];
|
|
36
37
|
}) {
|
|
37
38
|
const [note, setNote] = useState("");
|
|
@@ -48,7 +49,7 @@ function Entry({ request, unread, demo, busy, onAnswer, onMarkRead }: {
|
|
|
48
49
|
</div>
|
|
49
50
|
<p className="text-xs text-muted-foreground">
|
|
50
51
|
Raised by {request.raisedBy} on {formatWhen(request.raisedAt)}
|
|
51
|
-
{" · "}About: {href ? <Link href={href} onClick={() => onMarkRead?.(request.id)}>{request.subject
|
|
52
|
+
{" · "}About: {href ? <Link href={href} onClick={() => onMarkRead?.(request.id)}>{subjectLabel(request.subject, titles)}</Link> : <span>{subjectLabel(request.subject, titles)}</span>}
|
|
52
53
|
</p>
|
|
53
54
|
{request.ask ? <p className="text-sm">{request.ask}</p> : null}
|
|
54
55
|
{request.links.length ? (
|
|
@@ -124,12 +125,12 @@ function Section({ id, title, count, children, empty }: { id: string; title: str
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
/** The CMS console home: what needs a person, what will proceed on its own, and what the system decided. Ignore-safe by design. */
|
|
127
|
-
export function NotificationCenterView({ loadState, requests, readIds, held, autoDecided, demo = false, busyId = null, error = null, onAnswer, onMarkRead, onMarkAllRead }: ViewProps) {
|
|
128
|
+
export function NotificationCenterView({ loadState, requests, readIds, held, autoDecided, demo = false, storyTitles = {}, busyId = null, error = null, onAnswer, onMarkRead, onMarkAllRead }: ViewProps) {
|
|
128
129
|
const sections = useMemo(() => sectionReviews(requests), [requests]);
|
|
129
130
|
const unreadCount = requests.filter((request) => isUnread(request, readIds)).length;
|
|
130
131
|
const summary = summarize(sections, held, autoDecided, unreadCount);
|
|
131
132
|
const entry = (request: ReviewRequest) => (
|
|
132
|
-
<Entry busy={busyId === request.id} demo={demo} key={request.id} onAnswer={onAnswer} onMarkRead={onMarkRead} request={request} unread={isUnread(request, readIds)} />
|
|
133
|
+
<Entry busy={busyId === request.id} demo={demo} key={request.id} titles={storyTitles} onAnswer={onAnswer} onMarkRead={onMarkRead} request={request} unread={isUnread(request, readIds)} />
|
|
133
134
|
);
|
|
134
135
|
if (loadState === "loading") return (
|
|
135
136
|
<section aria-label="Loading Notification Center" className="space-y-4" data-notification-state="loading" role="status">
|
|
@@ -204,6 +205,7 @@ export function NotificationCenter({ references, decisions, humanCuratedReferenc
|
|
|
204
205
|
const [userKey, setUserKey] = useState<string | null>(null);
|
|
205
206
|
const [busyId, setBusyId] = useState<string | null>(null);
|
|
206
207
|
const [error, setError] = useState<string | null>(null);
|
|
208
|
+
const [storyTitles, setStoryTitles] = useState<Record<string, string>>({});
|
|
207
209
|
|
|
208
210
|
const refresh = useCallback(async () => {
|
|
209
211
|
if (demo) {
|
|
@@ -223,6 +225,8 @@ export function NotificationCenter({ references, decisions, humanCuratedReferenc
|
|
|
223
225
|
setReadIds(new Set(loaded.readIds));
|
|
224
226
|
setUserKey(loaded.userKey);
|
|
225
227
|
setLoadState("ready");
|
|
228
|
+
const { loadStoryTitles } = await import("../lib/review-client");
|
|
229
|
+
setStoryTitles(await loadStoryTitles(loaded.requests.filter((request) => request.subject.type === "story").map((request) => request.subject.id)).catch(() => ({})));
|
|
226
230
|
} catch {
|
|
227
231
|
setLoadState("unavailable");
|
|
228
232
|
}
|
|
@@ -266,7 +270,7 @@ export function NotificationCenter({ references, decisions, humanCuratedReferenc
|
|
|
266
270
|
|
|
267
271
|
return (
|
|
268
272
|
<NotificationCenterView
|
|
269
|
-
autoDecided={autoDecided} busyId={busyId} demo={demo} error={error} held={held} loadState={loadState}
|
|
273
|
+
autoDecided={autoDecided} busyId={busyId} storyTitles={storyTitles} demo={demo} error={error} held={held} loadState={loadState}
|
|
270
274
|
onAnswer={answer} onMarkAllRead={markAllRead} onMarkRead={markRead} readIds={readIds} requests={requests}
|
|
271
275
|
/>
|
|
272
276
|
);
|
|
@@ -667,7 +667,7 @@ export function NewsDeskWorkspace({
|
|
|
667
667
|
editorShellReady
|
|
668
668
|
initialSelection={initialSelection}
|
|
669
669
|
initialTab={initialTab}
|
|
670
|
-
authState={{ status: "signedIn", label: "Demo
|
|
670
|
+
authState={{ status: "signedIn", label: "Demo section" }}
|
|
671
671
|
isRefreshing={false}
|
|
672
672
|
shellError={null}
|
|
673
673
|
/>
|
|
@@ -3903,7 +3903,7 @@ function NewsroomSectionRail({
|
|
|
3903
3903
|
<aside className="newsroom-section-rail" data-newsroom-section-rail aria-labelledby="newsroom-section-rail-title" ref={railRef}>
|
|
3904
3904
|
<header className="newsroom-section-rail__header">
|
|
3905
3905
|
<p className="story-label">Sections</p>
|
|
3906
|
-
<h2 id="newsroom-section-rail-title">Section
|
|
3906
|
+
<h2 id="newsroom-section-rail-title">Section</h2>
|
|
3907
3907
|
</header>
|
|
3908
3908
|
<div className="newsroom-section-rail__body">
|
|
3909
3909
|
{canonicalSections.length ? (
|
|
@@ -5743,7 +5743,7 @@ function DesksDeskView({
|
|
|
5743
5743
|
<div className="news-desk-columns news-desk-columns--administration news-desk-columns--taxonomy" data-news-desk-section="desks">
|
|
5744
5744
|
<div className="news-desk-main-column">
|
|
5745
5745
|
<section className="category-steering-section category-steering-section--lead" aria-labelledby="newsroom-desks-title">
|
|
5746
|
-
<SectionHeader title="News
|
|
5746
|
+
<SectionHeader title="News sections" detail={`${roots.length} root topic sections`} />
|
|
5747
5747
|
<div className="news-desk-desk-list">
|
|
5748
5748
|
{roots.length ? roots.map((root) => {
|
|
5749
5749
|
const rootNode = root.node ?? categoryToCategoryTreeNode(root.category);
|
|
@@ -5763,11 +5763,11 @@ function DesksDeskView({
|
|
|
5763
5763
|
>
|
|
5764
5764
|
<span>{rootNode.shortTitle ?? deriveShortTitle(rootNode.displayName)}</span>
|
|
5765
5765
|
<strong>{rootNode.displayName}</strong>
|
|
5766
|
-
<p>{rootNode.description ?? "No
|
|
5766
|
+
<p>{rootNode.description ?? "No section description yet."}</p>
|
|
5767
5767
|
<small>{referenceCount} refs / {assignmentCount} assignments / {root.subcategorys.length} subtopics / {doctrineStatus.savedCount} of 2 doctrine slots</small>
|
|
5768
5768
|
</Link>
|
|
5769
5769
|
);
|
|
5770
|
-
}) : <EmptyRow label="No root topic
|
|
5770
|
+
}) : <EmptyRow label="No root topic sections are available in the canonical category set" />}
|
|
5771
5771
|
</div>
|
|
5772
5772
|
</section>
|
|
5773
5773
|
|
|
@@ -5788,18 +5788,18 @@ function DesksDeskView({
|
|
|
5788
5788
|
|
|
5789
5789
|
<aside className="news-desk-rail-column">
|
|
5790
5790
|
<section className="category-steering-section" aria-labelledby="desk-index-notes-title">
|
|
5791
|
-
<SectionHeader title="
|
|
5791
|
+
<SectionHeader title="Section rules" detail="Root topics only" />
|
|
5792
5792
|
<div className="news-desk-ledger-list">
|
|
5793
5793
|
<article className="news-desk-ledger-item">
|
|
5794
5794
|
<header>
|
|
5795
|
-
<strong>
|
|
5795
|
+
<strong>Section identity</strong>
|
|
5796
5796
|
<span>Category</span>
|
|
5797
5797
|
</header>
|
|
5798
5798
|
<p>Official name, short title, subtitle, and description stay on the accepted root Category record.</p>
|
|
5799
5799
|
</article>
|
|
5800
5800
|
<article className="news-desk-ledger-item">
|
|
5801
5801
|
<header>
|
|
5802
|
-
<strong>
|
|
5802
|
+
<strong>Section doctrine</strong>
|
|
5803
5803
|
<span>Private Item</span>
|
|
5804
5804
|
</header>
|
|
5805
5805
|
<p>Mission and policies are private doctrine Items tied to the root category lineage.</p>
|
|
@@ -5849,7 +5849,7 @@ function DeskDetailPanel({
|
|
|
5849
5849
|
<section className="category-steering-section news-desk-desk-detail" aria-labelledby="selected-desk-title">
|
|
5850
5850
|
<header className="news-desk-topic-detail__header">
|
|
5851
5851
|
<div>
|
|
5852
|
-
<p className="story-label">News
|
|
5852
|
+
<p className="story-label">News section</p>
|
|
5853
5853
|
<h3 id="selected-desk-title">{rootNode.displayName}</h3>
|
|
5854
5854
|
<span>{rootNode.shortTitle ?? deriveShortTitle(rootNode.displayName)}</span>
|
|
5855
5855
|
</div>
|
|
@@ -5932,7 +5932,7 @@ function CategoryDoctrineEditorCard({
|
|
|
5932
5932
|
<article className="news-desk-doctrine-card" data-news-desk-doctrine={definition.slug}>
|
|
5933
5933
|
<header className="news-desk-doctrine-card__header">
|
|
5934
5934
|
<div>
|
|
5935
|
-
<p className="story-label">
|
|
5935
|
+
<p className="story-label">Section doctrine</p>
|
|
5936
5936
|
<h3>{definition.label}</h3>
|
|
5937
5937
|
</div>
|
|
5938
5938
|
<span>{record ? "Saved record" : "Empty slot"}</span>
|
|
@@ -12297,7 +12297,7 @@ function AssignmentRow({
|
|
|
12297
12297
|
{context ? (
|
|
12298
12298
|
<>
|
|
12299
12299
|
<p className="news-desk-assignment-row__angle">
|
|
12300
|
-
<span>{context.deskTitle ?? context.deskKey ?? "
|
|
12300
|
+
<span>{context.deskTitle ?? context.deskKey ?? "Section"}</span>
|
|
12301
12301
|
{`${context.focusTitle ?? context.focusKey ?? "focus"} / ${context.targetSystemType ?? context.contextProfile ?? "context"}`}
|
|
12302
12302
|
</p>
|
|
12303
12303
|
{context.contextProfile || context.contextTokenBudget ? (
|
|
@@ -12412,7 +12412,7 @@ function normalizeAnalysisReindexMode(value: string | null | undefined): Analysi
|
|
|
12412
12412
|
function formatAnalysisReindexModeLabel(mode: AnalysisReindexMode): string {
|
|
12413
12413
|
if (mode === "online-update") return "Online update - new or changed accepted references";
|
|
12414
12414
|
if (mode === "classifier-retrain") return "Classifier retrain - rebuild topic classifier";
|
|
12415
|
-
if (mode === "scoped-topic-rebuild") return "Scoped topic rebuild - one
|
|
12415
|
+
if (mode === "scoped-topic-rebuild") return "Scoped topic rebuild - one section or focus";
|
|
12416
12416
|
if (mode === "entity-graph-rebuild") return "Entity graph rebuild - generated graph outputs";
|
|
12417
12417
|
return "Generated analysis rebuild - clear and rebuild targeted generated outputs";
|
|
12418
12418
|
}
|
|
@@ -12442,7 +12442,7 @@ function mergeAnalysisCorpora(configuredCorpora: CategorySteeringCorpus[], liveC
|
|
|
12442
12442
|
function analysisReindexModeHelp(mode: AnalysisReindexMode): string {
|
|
12443
12443
|
if (mode === "generated-analysis-rebuild") return "Use this for a full generated-output rebuild for the selected profile. It still creates an assignment only; the worker/operator executes it later.";
|
|
12444
12444
|
if (mode === "classifier-retrain") return "Use this when authoritative labels or accepted references should retrain the semi-supervised topic classifier.";
|
|
12445
|
-
if (mode === "scoped-topic-rebuild") return "Use this when proposals for a
|
|
12445
|
+
if (mode === "scoped-topic-rebuild") return "Use this when proposals for a section or focus topic should be regenerated from the accepted taxonomy plus steering feedback.";
|
|
12446
12446
|
if (mode === "entity-graph-rebuild") return "Use this when generated ontology/entity graph records should be rebuilt from accepted references.";
|
|
12447
12447
|
return "Use this for incremental processing of new or changed accepted references without clearing broader generated outputs.";
|
|
12448
12448
|
}
|
|
@@ -13092,7 +13092,7 @@ function formatDeskSectionHeadline(section: NewsDeskTab): string {
|
|
|
13092
13092
|
|
|
13093
13093
|
function formatDeskSectionLede(section: NewsDeskTab): string {
|
|
13094
13094
|
if (section === "search") return "Search the newsroom knowledge base with semantic and ontology context.";
|
|
13095
|
-
if (section === "desks") return "The
|
|
13095
|
+
if (section === "desks") return "The sections and topic categories that organize coverage, with each section's mission and policies.";
|
|
13096
13096
|
if (section === "topics") return "Review suggested changes to the subject areas the newsroom covers.";
|
|
13097
13097
|
if (section === "concepts") return "Browse people, organizations, places, and ideas found in the source material.";
|
|
13098
13098
|
if (section === "references") return "Review source materials before they become usable evidence.";
|
|
@@ -1504,7 +1504,7 @@ export function createDemoCategorySteeringDashboard(
|
|
|
1504
1504
|
riskFlags: ["Avoid promotional framing."],
|
|
1505
1505
|
coverageGaps: ["Need a second independent source."],
|
|
1506
1506
|
openQuestions: ["Which users are affected first?"],
|
|
1507
|
-
copywriterBrief: "Use accepted evidence and recent
|
|
1507
|
+
copywriterBrief: "Use accepted evidence and recent section memory before drafting.",
|
|
1508
1508
|
},
|
|
1509
1509
|
},
|
|
1510
1510
|
},
|
|
@@ -1654,8 +1654,8 @@ export function createDemoCategorySteeringDashboard(
|
|
|
1654
1654
|
slug: "editorial-doctrine-policy",
|
|
1655
1655
|
title: "Editorial Policy",
|
|
1656
1656
|
body: [
|
|
1657
|
-
"Publication doctrine is the global inclusion standard;
|
|
1658
|
-
"News reporting should prioritize current, journalistic, and official sources;
|
|
1657
|
+
"Publication doctrine is the global inclusion standard; section doctrine sets local source priorities and framing.",
|
|
1658
|
+
"News reporting should prioritize current, journalistic, and official sources; section workflows may use academic research as background unless a section policy explicitly makes research the primary evidence class.",
|
|
1659
1659
|
"High-risk domains, including health-related claims, require stricter sourcing, explicit uncertainty handling, and clear separation between verified facts and interpretation.",
|
|
1660
1660
|
],
|
|
1661
1661
|
editorial: { kind: "policy" },
|
|
@@ -41,7 +41,7 @@ export function formatConsoleWebContextMessage(
|
|
|
41
41
|
webPath ? `Web path: ${webPath}` : null,
|
|
42
42
|
objectUri ? `Focused object: ${objectUri}` : null,
|
|
43
43
|
label ? `Label: ${label}` : null,
|
|
44
|
-
tab ? `
|
|
44
|
+
tab ? `Section: ${tab}` : null,
|
|
45
45
|
viewMode ? `View: ${viewMode}` : null,
|
|
46
46
|
].filter((line): line is string => Boolean(line));
|
|
47
47
|
|
|
@@ -237,7 +237,7 @@ export function applyPilobolusDemoDashboardContent(
|
|
|
237
237
|
...record,
|
|
238
238
|
title: "Pilobolus Editorial Policy",
|
|
239
239
|
body: [
|
|
240
|
-
"Publication doctrine is the global inclusion standard for Pilobol.us;
|
|
240
|
+
"Publication doctrine is the global inclusion standard for Pilobol.us; section doctrine sets local intake priorities for field notes and source intake.",
|
|
241
241
|
"Field-notes reporting should prioritize accepted Pilobolus references and annotated intake artifacts—not the Papyrus platform knowledge base.",
|
|
242
242
|
"High-risk claims about host takeover or network control require explicit uncertainty handling and accepted source linkage.",
|
|
243
243
|
],
|
package/lib/newsroom-nav.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type NewsroomNavItem = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const NEWSROOM_OPS_NAV: NewsroomNavItem[] = [
|
|
24
|
-
{ id: "overview", label: "Overview", detail: "
|
|
24
|
+
{ id: "overview", label: "Overview", detail: "Newsroom home", href: newsroomHref() },
|
|
25
25
|
{ id: "articles", label: getContentItemName().plural, detail: "Drafts & published", href: newsroomHref("articles") },
|
|
26
26
|
{ id: "assignments", label: "Assignments", detail: "Work queue", href: newsroomHref("assignments") },
|
|
27
27
|
{ id: "references", label: "References", detail: "Knowledge base", href: newsroomHref("references") },
|
package/lib/review-client.ts
CHANGED
|
@@ -97,3 +97,21 @@ export async function markReviewRead(userKey: string, requestId: string, now: Da
|
|
|
97
97
|
await run(`mutation Mark($input: UpdateReviewReadMarkInput!) { updateReviewReadMark(input: $input) { id } }`, { input });
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
const MAX_TITLE_LOOKUPS = 40;
|
|
102
|
+
|
|
103
|
+
/** Titles of the stories that review requests are about, by lineage id. A story that cannot be read simply has no entry (the card falls back to the id). */
|
|
104
|
+
export async function loadStoryTitles(ids: string[]): Promise<Record<string, string>> {
|
|
105
|
+
const titles: Record<string, string> = {};
|
|
106
|
+
await Promise.all([...new Set(ids)].slice(0, MAX_TITLE_LOOKUPS).map(async (id) => {
|
|
107
|
+
const result = await run(
|
|
108
|
+
`query StoryTitle($lineageId: ID!) { listItemsByLineageAndVersion(lineageId: $lineageId, limit: 50) { items { versionNumber headline title } } }`,
|
|
109
|
+
{ lineageId: id },
|
|
110
|
+
);
|
|
111
|
+
const items = (result.data?.listItemsByLineageAndVersion?.items ?? []).filter(Boolean) as { versionNumber?: number; headline?: string; title?: string }[];
|
|
112
|
+
const newest = items.sort((a, b) => (b.versionNumber ?? 0) - (a.versionNumber ?? 0))[0];
|
|
113
|
+
const title = newest?.headline || newest?.title;
|
|
114
|
+
if (title) titles[id] = title;
|
|
115
|
+
}));
|
|
116
|
+
return titles;
|
|
117
|
+
}
|
package/lib/review-requests.ts
CHANGED
|
@@ -145,6 +145,12 @@ export function steeringText(request: ReviewRequest): string | null {
|
|
|
145
145
|
return `You steered ${said}. ${request.raisedBy} has not used it yet; it will on its next run.`;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/** What a request is about, in words: a story shows its title (the id when the title is not known), anything else its type and id. */
|
|
149
|
+
export function subjectLabel(subject: { type: string; id: string }, titles: Readonly<Record<string, string>> = {}): string {
|
|
150
|
+
if (subject.type === "story") return titles[subject.id] ?? `story ${subject.id}`;
|
|
151
|
+
return `${subject.type.replace("_", " ")} ${subject.id}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
148
154
|
export function subjectHref(subject: { type: string; id: string }, demo = false): string | null {
|
|
149
155
|
const id = encodeURIComponent(subject.id);
|
|
150
156
|
if (subject.type === "story") return getNewsroomNavHref(`/newsroom/articles/${id}`, demo);
|
package/package.json
CHANGED
package/routes.manifest.json
CHANGED