@anthusai/papyrus 1.4.0-next.3 → 1.4.0-next.4
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/news-desk-taxonomy-client.ts +3 -2
- package/components/newsroom-article-editor.tsx +3 -1
- package/components/newsroom-assignment-detail.tsx +45 -0
- package/components/newsroom-board-view.tsx +16 -3
- package/components/newsroom-organizer.css +21 -2
- package/components/newsroom-topics-view.tsx +1 -1
- package/components/ui/sheet.tsx +9 -4
- package/lib/approved-priming-proof-reports.ts +6 -3
- package/lib/approved-priming-proofs/papyrus.json +90 -0
- package/lib/content-actions-demo.ts +3 -0
- package/lib/front-mosaic.ts +2 -1
- package/lib/layout-plan.ts +3 -2
- package/lib/layout-warnings.ts +8 -0
- package/lib/local-preview-seed.ts +20 -0
- package/lib/newsroom-assignments.ts +13 -0
- package/package.json +3 -1
- package/routes.manifest.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { isEditorialWorkAssignment } from "../lib/newsroom-assignments";
|
|
3
4
|
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
4
5
|
import { createLocalPreviewAssignments, createLocalPreviewContentItemVersions, createLocalPreviewReferences } from "../lib/local-preview-seed";
|
|
5
6
|
import { generateClient } from "aws-amplify/data";
|
|
@@ -2156,10 +2157,10 @@ export async function loadNewsroomAssignmentPage(options: NewsroomAssignmentPage
|
|
|
2156
2157
|
newsroomFeedKey: "assignments",
|
|
2157
2158
|
limit: options.limit,
|
|
2158
2159
|
nextToken: options.nextToken,
|
|
2159
|
-
matches: equalityMatcher({
|
|
2160
|
+
matches: (assignment) => isEditorialWorkAssignment(assignment) && (equalityMatcher<AssignmentRecord>({
|
|
2160
2161
|
assignmentTypeKey: options.type,
|
|
2161
2162
|
status: options.status,
|
|
2162
|
-
}),
|
|
2163
|
+
})?.(assignment) ?? true),
|
|
2163
2164
|
});
|
|
2164
2165
|
}
|
|
2165
2166
|
|
|
@@ -91,7 +91,7 @@ function failureMessage(error: unknown): string {
|
|
|
91
91
|
return error instanceof Error ? error.message : "The request failed.";
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export function NewsroomArticleEditor({ articleId, demo }: { articleId: string; demo: boolean }) {
|
|
94
|
+
export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { articleId: string; demo: boolean; embedded?: boolean }) {
|
|
95
95
|
const session = useOptionalNewsDeskClient();
|
|
96
96
|
const blocked = !demo && articlesAccessBlocked(session?.shell.phase);
|
|
97
97
|
const backend: ArticlesBackend = useMemo(() => resolveArticlesBackend(demo), [demo]);
|
|
@@ -725,6 +725,8 @@ export function NewsroomArticleEditor({ articleId, demo }: { articleId: string;
|
|
|
725
725
|
);
|
|
726
726
|
})();
|
|
727
727
|
|
|
728
|
+
if (embedded) return <div className="newsroom-embedded-article">{body}</div>;
|
|
729
|
+
|
|
728
730
|
return (
|
|
729
731
|
<NewsroomOpsShell
|
|
730
732
|
activeTab="articles"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import type { AssignmentRecord } from "../lib/category-repository";
|
|
5
|
+
|
|
6
|
+
const assignmentDateFormat = new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" });
|
|
7
|
+
|
|
8
|
+
function formattedTimestamp(value: string | null | undefined): string | null {
|
|
9
|
+
if (!value) return null;
|
|
10
|
+
const parsed = new Date(value);
|
|
11
|
+
return Number.isNaN(parsed.getTime()) ? value : assignmentDateFormat.format(parsed);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type DetailField = { label: string; value: string | null | undefined };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The whole assignment, laid out by container width: one column in a narrow Board panel, labelled
|
|
18
|
+
* columns when the container is wide, such as the dedicated page on a large monitor.
|
|
19
|
+
*/
|
|
20
|
+
export function NewsroomAssignmentDetail({ assignment, pageHref, showTitle = true }: { assignment: AssignmentRecord; pageHref?: string; showTitle?: boolean }) {
|
|
21
|
+
const facts: DetailField[] = [
|
|
22
|
+
{ label: "Status", value: assignment.status },
|
|
23
|
+
{ label: "Type", value: assignment.assignmentTypeKey },
|
|
24
|
+
{ label: "Assignee", value: assignment.assigneeId ?? assignment.assigneeKey },
|
|
25
|
+
{ label: "Section", value: assignment.sectionKey },
|
|
26
|
+
{ label: "Created", value: formattedTimestamp(assignment.createdAt) },
|
|
27
|
+
{ label: "Updated", value: formattedTimestamp(assignment.updatedAt) },
|
|
28
|
+
{ label: "Completed", value: formattedTimestamp(assignment.completedAt) },
|
|
29
|
+
].filter(field => field.value);
|
|
30
|
+
const prose: DetailField[] = [
|
|
31
|
+
{ label: "Summary", value: assignment.summary },
|
|
32
|
+
{ label: "Brief", value: assignment.brief },
|
|
33
|
+
{ label: "Instructions", value: assignment.instructions },
|
|
34
|
+
].filter(field => field.value);
|
|
35
|
+
return <article className="newsroom-assignment-detail">
|
|
36
|
+
{showTitle && <h2 className="newsroom-assignment-detail-title">{assignment.title}</h2>}
|
|
37
|
+
<dl className="newsroom-assignment-detail-facts">
|
|
38
|
+
{facts.map(field => <div key={field.label}><dt>{field.label}</dt><dd>{field.value}</dd></div>)}
|
|
39
|
+
</dl>
|
|
40
|
+
<div className="newsroom-assignment-detail-prose">
|
|
41
|
+
{prose.map(field => <section key={field.label}><h3>{field.label}</h3><p>{field.value}</p></section>)}
|
|
42
|
+
</div>
|
|
43
|
+
{pageHref && <Link className="organizer-header-button" href={pageHref}>Open full page</Link>}
|
|
44
|
+
</article>;
|
|
45
|
+
}
|
|
@@ -29,6 +29,9 @@ import { usePagedContentItemVersions } from "./use-paged-content-item-versions";
|
|
|
29
29
|
import type { ContentItemVersionRecord } from "../lib/content-item-lifecycle";
|
|
30
30
|
import { useOptionalNewsDeskClient } from "./news-desk-client-provider";
|
|
31
31
|
import { NewsDeskAccessGate } from "./newsroom-access-gate";
|
|
32
|
+
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
33
|
+
import { NewsroomArticleEditor } from "./newsroom-article-editor";
|
|
34
|
+
import { NewsroomAssignmentDetail } from "./newsroom-assignment-detail";
|
|
32
35
|
import { NewsroomOpsShell, NewsroomOpsStatusBanner } from "./newsroom-ops-shell";
|
|
33
36
|
import { useResolvedPapyrusTheme } from "./use-resolved-papyrus-theme";
|
|
34
37
|
import { readLocalReaderSettings, subscribeReaderSettingsChanges } from "./reader-settings";
|
|
@@ -92,7 +95,7 @@ export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardS
|
|
|
92
95
|
if (generation.current === request) { setPublishedVersions(published); setAssignments(everyAssignment); }
|
|
93
96
|
})
|
|
94
97
|
: loadEveryNewsroomAssignment().then(everyAssignment => {
|
|
95
|
-
if (generation.current === request) setAssignmentBoardIssues(projectAssignmentBoard(everyAssignment));
|
|
98
|
+
if (generation.current === request) { setAssignments(everyAssignment); setAssignmentBoardIssues(projectAssignmentBoard(everyAssignment)); }
|
|
96
99
|
});
|
|
97
100
|
const refresh = (initial: boolean) => {
|
|
98
101
|
if (initial) setSupportingRecordsLoading(true);
|
|
@@ -158,8 +161,18 @@ export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardS
|
|
|
158
161
|
collapsedColumns={columnState.collapsedColumns} onToggleCollapse={columnState.toggleColumn} initialScroll="start"
|
|
159
162
|
selectedIssueId={selected} detailOpen={Boolean(issue)} onSelectIssue={item => setSelected(item.id)} ariaLabel={pageTitle}
|
|
160
163
|
emptyMessage={loading ? "Loading records…" : error ? "Records could not be loaded." : "No records in this selection."} />
|
|
161
|
-
{issue && <IssueDetailPanel issue={issue} onClose={() => setSelected(null)} fields={[
|
|
162
|
-
|
|
164
|
+
{issue && <IssueDetailPanel issue={issue} onClose={() => setSelected(null)} fields={[]}>
|
|
165
|
+
{issue.kind === "contentItem"
|
|
166
|
+
? <>
|
|
167
|
+
<NewsroomArticleEditor key={issue.id} articleId={decodeURIComponent(issue.href.split("/").pop() ?? "")} demo={isLocalPreviewEnabled()} embedded />
|
|
168
|
+
<Link className="organizer-header-button" href={getNewsroomNavHref(issue.href)}>Open full page</Link>
|
|
169
|
+
</>
|
|
170
|
+
: (() => {
|
|
171
|
+
const record = assignments.find(candidate => candidate.id === issue.nativeId);
|
|
172
|
+
return record
|
|
173
|
+
? <NewsroomAssignmentDetail assignment={record} pageHref={getNewsroomNavHref(issue.href)} showTitle={false} />
|
|
174
|
+
: <Link href={getNewsroomNavHref(issue.href)}>Open {issue.type}</Link>;
|
|
175
|
+
})()}
|
|
163
176
|
</IssueDetailPanel>}
|
|
164
177
|
</div>
|
|
165
178
|
</div>
|
|
@@ -43,10 +43,10 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
|
|
|
43
43
|
.organizer-board-note{font-size:11px;line-height:1.5;margin:0;color:var(--org-muted);white-space:nowrap}
|
|
44
44
|
@media(min-width:901px){.newsroom-organizer[data-sidebar-collapsed=false] .organizer-masthead{display:none}}
|
|
45
45
|
.organizer-board-body{position:relative;display:grid;grid-template-columns:minmax(0,1fr);flex:1;min-width:0;min-height:0;gap:16px}
|
|
46
|
-
.organizer-board-body[data-detail-open=true]{grid-template-columns:minmax(0,1fr)
|
|
46
|
+
.organizer-board-body[data-detail-open=true]{grid-template-columns:minmax(0,1fr) clamp(320px,34vw,1000px)}
|
|
47
47
|
.organizer-board-body>.kb-issue-collection{height:100%;min-height:0;overflow:auto}
|
|
48
48
|
.organizer-board-body>.kb-issue-detail{overflow:auto;min-height:0}
|
|
49
|
-
.organizer-board-body .kb-issue-detail a{display:block;min-height:44px;text-decoration:underline;padding:12px 0}
|
|
49
|
+
.organizer-board-body .kb-issue-detail a:not(.organizer-header-button){display:block;min-height:44px;text-decoration:underline;padding:12px 0}
|
|
50
50
|
.organizer-menu{height:min(85dvh,760px);max-height:85dvh;display:flex;flex-direction:column;padding:16px;border-radius:24px 24px 0 0;gap:20px}
|
|
51
51
|
.organizer-menu .organizer-destinations{overflow-y:auto}.organizer-menu .organizer-destinations section{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.organizer-menu .organizer-destinations h3{grid-column:1/-1;font-size:12px;text-transform:none;margin:0 0 4px}
|
|
52
52
|
.organizer-menu .organizer-destinations a{border:1px solid var(--org-line);padding:12px;color:var(--org-ink)}
|
|
@@ -63,3 +63,22 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
|
|
|
63
63
|
.organizer-board-body>.kb-issue-detail{position:absolute;inset:0;z-index:2;overflow:auto}
|
|
64
64
|
}
|
|
65
65
|
@media(prefers-reduced-motion:reduce){.newsroom-organizer,.newsroom-organizer *,.organizer-menu,.organizer-menu *,.organizer-account-popup,.organizer-account-popup *{scroll-behavior:auto;animation:none!important;transition:none!important}}
|
|
66
|
+
.newsroom-organizer :is(button,input,select,textarea,[role=button],[role=tab],[role=combobox],[role=option],[role=menuitem],a[class]),.organizer-menu :is(button,a),.organizer-account-popup :is(button,a){border-radius:0 !important}
|
|
67
|
+
.newsroom-assignment-detail,.newsroom-embedded-article{container-type:inline-size;min-width:0}
|
|
68
|
+
.newsroom-assignment-detail{display:grid;gap:16px;padding:12px}
|
|
69
|
+
.newsroom-assignment-detail-title{margin:0;font-size:1.25rem;line-height:1.25;overflow-wrap:anywhere}
|
|
70
|
+
.newsroom-assignment-detail-facts{display:grid;gap:8px 16px;grid-template-columns:minmax(0,1fr);margin:0}
|
|
71
|
+
.newsroom-assignment-detail-facts>div{display:grid;grid-template-columns:minmax(80px,max-content) minmax(0,1fr);gap:8px;border-top:1px solid var(--org-line);padding-top:6px}
|
|
72
|
+
.newsroom-assignment-detail-facts dt{color:var(--org-muted);font-size:12px;text-transform:uppercase;letter-spacing:.04em}
|
|
73
|
+
.newsroom-assignment-detail-facts dd{margin:0;overflow-wrap:anywhere}
|
|
74
|
+
.newsroom-assignment-detail-prose{display:grid;gap:12px}
|
|
75
|
+
.newsroom-assignment-detail-prose h3{margin:0 0 4px;font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--org-muted)}
|
|
76
|
+
.newsroom-assignment-detail-prose p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.5}
|
|
77
|
+
@container (min-width:560px){.newsroom-assignment-detail-facts{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
78
|
+
@container (min-width:900px){.newsroom-assignment-detail{grid-template-columns:minmax(0,2fr) minmax(280px,1fr);align-items:start;column-gap:32px}.newsroom-assignment-detail-title{grid-column:1/-1}.newsroom-assignment-detail-facts{grid-template-columns:minmax(0,1fr)}}
|
|
79
|
+
@container (min-width:1500px){.newsroom-assignment-detail-prose{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
80
|
+
.newsroom-embedded-article{padding:0 8px 12px}
|
|
81
|
+
.newsroom-embedded-article :is(textarea,input,select){max-width:100%}
|
|
82
|
+
.newsroom-embedded-article [class*="grid-cols-2"],.newsroom-embedded-article [class*="lg:grid"]{grid-template-columns:minmax(0,1fr) !important}
|
|
83
|
+
@container (min-width:1100px){.newsroom-embedded-article [class*="lg:grid"]{grid-template-columns:repeat(2,minmax(0,1fr)) !important}}
|
|
84
|
+
.newsroom-organizer [class*="rounded"]:not([data-slot=avatar]):not([class*="animate-spin"]):not([role=progressbar]),.organizer-menu,.organizer-account-popup,[data-slot=dialog-content],[data-slot=popover-content],[data-slot=dropdown-menu-content],[data-slot=select-content],[data-slot=tooltip-content]{border-radius:0 !important}
|
|
@@ -403,7 +403,7 @@ export function NewsroomTopicsView({
|
|
|
403
403
|
}}
|
|
404
404
|
open={mobileDetailOpen && Boolean(selectedProposal)}
|
|
405
405
|
>
|
|
406
|
-
<SheetContent className="gap-0 p-0 md:hidden" side="bottom">
|
|
406
|
+
<SheetContent backdropClassName="md:hidden" className="gap-0 p-0 md:hidden" side="bottom">
|
|
407
407
|
<SheetHeader className="border-b border-border px-4 py-3">
|
|
408
408
|
<SheetTitle className="text-left text-base">Topic proposal</SheetTitle>
|
|
409
409
|
</SheetHeader>
|
package/components/ui/sheet.tsx
CHANGED
|
@@ -11,9 +11,9 @@ type SheetSide = "bottom" | "left" | "right";
|
|
|
11
11
|
|
|
12
12
|
const sideClasses: Record<SheetSide, string> = {
|
|
13
13
|
bottom:
|
|
14
|
-
"inset-x-0 bottom-0 top-auto max-h-[85dvh] w-full
|
|
15
|
-
left: "inset-y-0 left-0 h-full w-[min(100vw,20rem)]
|
|
16
|
-
right: "inset-y-0 right-0 h-full w-[min(100vw,20rem)]
|
|
14
|
+
"inset-x-0 bottom-0 top-auto max-h-[85dvh] w-full border-t data-open:slide-in-from-bottom data-closed:slide-out-to-bottom",
|
|
15
|
+
left: "inset-y-0 left-0 h-full w-[min(100vw,20rem)] border-r data-open:slide-in-from-left data-closed:slide-out-to-left",
|
|
16
|
+
right: "inset-y-0 right-0 h-full w-[min(100vw,20rem)] border-l data-open:slide-in-from-right data-closed:slide-out-to-right",
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
function Sheet({ ...props }: DialogPrimitive.Root.Props) {
|
|
@@ -31,17 +31,22 @@ function SheetClose({ ...props }: DialogPrimitive.Close.Props) {
|
|
|
31
31
|
function SheetContent({
|
|
32
32
|
children,
|
|
33
33
|
className,
|
|
34
|
+
backdropClassName,
|
|
34
35
|
showCloseButton = true,
|
|
35
36
|
side = "bottom",
|
|
36
37
|
...props
|
|
37
38
|
}: DialogPrimitive.Popup.Props & {
|
|
39
|
+
backdropClassName?: string;
|
|
38
40
|
showCloseButton?: boolean;
|
|
39
41
|
side?: SheetSide;
|
|
40
42
|
}) {
|
|
41
43
|
return (
|
|
42
44
|
<DialogPrimitive.Portal>
|
|
43
45
|
<DialogPrimitive.Backdrop
|
|
44
|
-
className=
|
|
46
|
+
className={cn(
|
|
47
|
+
"fixed inset-0 z-50 bg-black/40 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
|
48
|
+
backdropClassName,
|
|
49
|
+
)}
|
|
45
50
|
/>
|
|
46
51
|
<DialogPrimitive.Popup
|
|
47
52
|
className={cn(
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import approvedReport0 from "./approved-priming-proofs/papyrus.json";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Priming proof reports a publication owner approved for visitors, keyed by site brand id.
|
|
3
|
-
* A report reaches this map only through a reviewed commit
|
|
4
|
-
* `publications/priming-proofs/<brand id>.json` file; see docs/cyclotron-priming.md.
|
|
5
|
+
* A report reaches this map only through a reviewed commit made by scripts/approve-priming-proof-report.ts.
|
|
5
6
|
*/
|
|
6
|
-
export const APPROVED_PRIMING_PROOF_REPORTS: Record<string, unknown> = {
|
|
7
|
+
export const APPROVED_PRIMING_PROOF_REPORTS: Record<string, unknown> = {
|
|
8
|
+
"papyrus": approvedReport0,
|
|
9
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "cyclotron-priming-proof/v1",
|
|
3
|
+
"proofId": "ti-2026-10",
|
|
4
|
+
"publication": "Anthus Threat Intelligence",
|
|
5
|
+
"cyclotronId": "papyrus-relevance",
|
|
6
|
+
"question": "Should this candidate source become a reference for this publication?",
|
|
7
|
+
"labels": [
|
|
8
|
+
"include",
|
|
9
|
+
"exclude"
|
|
10
|
+
],
|
|
11
|
+
"positiveLabel": "include",
|
|
12
|
+
"decisionModel": "jev:jev-1.13.0",
|
|
13
|
+
"ranAt": "2026-10-18T13:34:13.426310Z",
|
|
14
|
+
"heldOut": {
|
|
15
|
+
"size": 10,
|
|
16
|
+
"byLabel": {
|
|
17
|
+
"include": 5,
|
|
18
|
+
"exclude": 5
|
|
19
|
+
},
|
|
20
|
+
"fingerprint": "b12fe63012c925bd6def343a13b94ccedafd5184ddb93c8312998486045da779",
|
|
21
|
+
"frozenAt": "2026-10-18T13:02:20.090030Z",
|
|
22
|
+
"source": "References editors reviewed in the Newsroom, labeled by the scope-training rule"
|
|
23
|
+
},
|
|
24
|
+
"priming": {
|
|
25
|
+
"acceptedExamples": 13,
|
|
26
|
+
"excludedBecauseInHeldOut": 0,
|
|
27
|
+
"fingerprint": null,
|
|
28
|
+
"source": "editor reviews of References",
|
|
29
|
+
"order": "reviews in review order, then priming examples in acceptance order"
|
|
30
|
+
},
|
|
31
|
+
"conditions": {
|
|
32
|
+
"freshStorePerPoint": true,
|
|
33
|
+
"rubric": "publication doctrine seed",
|
|
34
|
+
"optimizer": false,
|
|
35
|
+
"reviewProgram": false
|
|
36
|
+
},
|
|
37
|
+
"points": [
|
|
38
|
+
{
|
|
39
|
+
"labels": 0,
|
|
40
|
+
"labelsByClass": {
|
|
41
|
+
"include": 0,
|
|
42
|
+
"exclude": 0
|
|
43
|
+
},
|
|
44
|
+
"decided": 10,
|
|
45
|
+
"agreement": 1,
|
|
46
|
+
"precision": 1,
|
|
47
|
+
"recall": 1,
|
|
48
|
+
"confusion": {
|
|
49
|
+
"truePositives": 5,
|
|
50
|
+
"falsePositives": 0,
|
|
51
|
+
"trueNegatives": 5,
|
|
52
|
+
"falseNegatives": 0
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"labels": 5,
|
|
57
|
+
"labelsByClass": {
|
|
58
|
+
"include": 4,
|
|
59
|
+
"exclude": 1
|
|
60
|
+
},
|
|
61
|
+
"decided": 10,
|
|
62
|
+
"agreement": 1,
|
|
63
|
+
"precision": 1,
|
|
64
|
+
"recall": 1,
|
|
65
|
+
"confusion": {
|
|
66
|
+
"truePositives": 5,
|
|
67
|
+
"falsePositives": 0,
|
|
68
|
+
"trueNegatives": 5,
|
|
69
|
+
"falseNegatives": 0
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"labels": 13,
|
|
74
|
+
"labelsByClass": {
|
|
75
|
+
"include": 4,
|
|
76
|
+
"exclude": 9
|
|
77
|
+
},
|
|
78
|
+
"decided": 10,
|
|
79
|
+
"agreement": 0.9,
|
|
80
|
+
"precision": 1,
|
|
81
|
+
"recall": 0.8,
|
|
82
|
+
"confusion": {
|
|
83
|
+
"truePositives": 4,
|
|
84
|
+
"falsePositives": 0,
|
|
85
|
+
"trueNegatives": 5,
|
|
86
|
+
"falseNegatives": 1
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
demoContentHash,
|
|
13
13
|
type DemoArticleRecord,
|
|
14
14
|
} from "./newsroom-demo-articles";
|
|
15
|
+
import { createLocalPreviewEditableArticle } from "./local-preview-seed";
|
|
15
16
|
import { sourceLineOffset, type ArticlesBackend, type EditableArticle, type NewsroomArticleRow } from "./newsroom-articles";
|
|
16
17
|
|
|
17
18
|
function titleFromYaml(frontMatterYaml: string | null, fallback: string): string {
|
|
@@ -171,6 +172,8 @@ export function createDemoArticlesBackend(): ArticlesBackend {
|
|
|
171
172
|
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
172
173
|
},
|
|
173
174
|
async loadArticle(id: string): Promise<EditableArticle> {
|
|
175
|
+
const previewRecord = records.get(id) ? null : createLocalPreviewEditableArticle(id);
|
|
176
|
+
if (previewRecord) return previewRecord;
|
|
174
177
|
const record = requireRecord(id);
|
|
175
178
|
return {
|
|
176
179
|
id: record.id,
|
package/lib/front-mosaic.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { warnAboutMissingMedia } from "./layout-warnings";
|
|
1
2
|
import {
|
|
2
3
|
type ArticleFrameBlockSpec,
|
|
3
4
|
type ArticleFrameCompositionSlotSpec,
|
|
@@ -605,7 +606,7 @@ function solveComposedFrontArticleFrame(
|
|
|
605
606
|
flow.usedImageAssetIds,
|
|
606
607
|
);
|
|
607
608
|
if (!image) {
|
|
608
|
-
if (mediaSpec?.required)
|
|
609
|
+
if (mediaSpec?.required) warnAboutMissingMedia(`${blockSpec.id} requires media slot ${slot.mediaIndex ?? 0} but no image could be solved and renders without it`);
|
|
609
610
|
continue;
|
|
610
611
|
}
|
|
611
612
|
const stackTop = getOccupiedColumnBottom(columnBottoms, image.columnStart, image.columnSpan);
|
package/lib/layout-plan.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { warnAboutMissingMedia } from "./layout-warnings";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import {
|
|
3
4
|
type PublicationItem,
|
|
@@ -894,7 +895,7 @@ function validateBlock(
|
|
|
894
895
|
if (block.type === "mediaCluster") {
|
|
895
896
|
const assets = findAssetsByRole(item, block.assetRole);
|
|
896
897
|
if (block.required && assets.length < block.count.min) {
|
|
897
|
-
|
|
898
|
+
warnAboutMissingMedia(`${label}.${block.id} requires at least ${block.count.min} media assets`);
|
|
898
899
|
}
|
|
899
900
|
}
|
|
900
901
|
}
|
|
@@ -902,7 +903,7 @@ function validateBlock(
|
|
|
902
903
|
function validateMediaSpec(item: PublicationItem, media: LayoutMediaSpec, label: string): void {
|
|
903
904
|
const matchingAssets = findAssetsByRole(item, media.assetRole);
|
|
904
905
|
if (media.required && matchingAssets.length === 0) {
|
|
905
|
-
|
|
906
|
+
warnAboutMissingMedia(`${label} requires an image asset${media.assetRole ? ` with role ${media.assetRole}` : ""} and renders without it`);
|
|
906
907
|
}
|
|
907
908
|
}
|
|
908
909
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const missingMediaWarningsAlreadyLogged = new Set<string>();
|
|
2
|
+
|
|
3
|
+
/** A story without its lead image must still publish; the layout renders without the slot. */
|
|
4
|
+
export function warnAboutMissingMedia(message: string): void {
|
|
5
|
+
if (missingMediaWarningsAlreadyLogged.has(message)) return;
|
|
6
|
+
missingMediaWarningsAlreadyLogged.add(message);
|
|
7
|
+
console.warn(`[layout-plan] ${message}`);
|
|
8
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AssignmentRecord, ReferenceRecord } from "./category-repository";
|
|
2
|
+
import type { EditableArticle } from "./newsroom-articles";
|
|
2
3
|
import { contentItemStageKeys, type ContentItemVersionRecord } from "./content-item-lifecycle";
|
|
3
4
|
import { getContentItemStageDefinitions } from "./site-brand";
|
|
4
5
|
|
|
@@ -93,3 +94,22 @@ export function createLocalPreviewAssignments(): AssignmentRecord[] {
|
|
|
93
94
|
}));
|
|
94
95
|
return [...assignmentsOnContentItems, ...assignmentsWithoutContentItem];
|
|
95
96
|
}
|
|
97
|
+
|
|
98
|
+
export function createLocalPreviewEditableArticle(id: string): EditableArticle | null {
|
|
99
|
+
const version = createLocalPreviewContentItemVersions().find(candidate => candidate.id === id);
|
|
100
|
+
if (!version) return null;
|
|
101
|
+
const headline = version.headline || version.title || version.slug;
|
|
102
|
+
return {
|
|
103
|
+
id: version.id,
|
|
104
|
+
type: version.type,
|
|
105
|
+
section: version.section ?? null,
|
|
106
|
+
slug: version.slug,
|
|
107
|
+
frontMatterYaml: `title: ${headline}\nsection: ${version.section ?? ""}`,
|
|
108
|
+
bodyMarkus: `# ${headline}\n\nThe local preview shows this story's record. Its full body text is loaded from the live backend.`,
|
|
109
|
+
contentHash: null,
|
|
110
|
+
status: version.status === "published" ? "published" : "draft",
|
|
111
|
+
versionNumber: version.versionNumber,
|
|
112
|
+
aliases: [],
|
|
113
|
+
everPublished: version.status === "published",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -18,6 +18,19 @@ export type AssignmentMetrics = {
|
|
|
18
18
|
canceled: number;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export const BOOKKEEPING_ASSIGNMENT_TYPE_KEYS: ReadonlySet<string> = new Set([
|
|
22
|
+
"analysis.reindex",
|
|
23
|
+
"curation.reference-intake",
|
|
24
|
+
"reference.corpus-accession",
|
|
25
|
+
"reference.identifier-backfill",
|
|
26
|
+
"reference.doi-backfill",
|
|
27
|
+
"reference.text-extraction",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
export function isEditorialWorkAssignment(assignment: Pick<AssignmentRecord, "assignmentTypeKey">): boolean {
|
|
31
|
+
return !BOOKKEEPING_ASSIGNMENT_TYPE_KEYS.has(assignment.assignmentTypeKey?.trim() ?? "");
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
export function assignmentTypeKeyForFilter(assignment: AssignmentRecord): string {
|
|
22
35
|
return assignment.assignmentTypeKey?.trim() || "unknown";
|
|
23
36
|
}
|
package/package.json
CHANGED
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"./components/newsroom-access-gate": "./components/newsroom-access-gate.tsx",
|
|
85
85
|
"./components/newsroom-article-editor": "./components/newsroom-article-editor.tsx",
|
|
86
86
|
"./components/newsroom-articles-view": "./components/newsroom-articles-view.tsx",
|
|
87
|
+
"./components/newsroom-assignment-detail": "./components/newsroom-assignment-detail.tsx",
|
|
87
88
|
"./components/newsroom-assignments-view": "./components/newsroom-assignments-view.tsx",
|
|
88
89
|
"./components/newsroom-board-view": "./components/newsroom-board-view.tsx",
|
|
89
90
|
"./components/newsroom-client-shell": "./components/newsroom-client-shell.tsx",
|
|
@@ -182,6 +183,7 @@
|
|
|
182
183
|
"./lib/layout-plan": "./lib/layout-plan.ts",
|
|
183
184
|
"./lib/layout-scenarios": "./lib/layout-scenarios.ts",
|
|
184
185
|
"./lib/layout-types": "./lib/layout-types.ts",
|
|
186
|
+
"./lib/layout-warnings": "./lib/layout-warnings.ts",
|
|
185
187
|
"./lib/local-preview-seed": "./lib/local-preview-seed.ts",
|
|
186
188
|
"./lib/local-preview": "./lib/local-preview.ts",
|
|
187
189
|
"./lib/markus-body": "./lib/markus-body.ts",
|
|
@@ -352,7 +354,7 @@
|
|
|
352
354
|
"optional": true
|
|
353
355
|
}
|
|
354
356
|
},
|
|
355
|
-
"version": "1.4.0-next.
|
|
357
|
+
"version": "1.4.0-next.4",
|
|
356
358
|
"dependencies": {
|
|
357
359
|
"@aws-amplify/adapter-nextjs": "^1.7.3",
|
|
358
360
|
"@aws-crypto/sha256-js": "5.2.0",
|
package/routes.manifest.json
CHANGED