@anthusai/papyrus 5.43.0 → 5.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/amplify/functions/category-action/handler.ts +2 -1
- package/amplify/functions/model-attachment-upload/handler.ts +1 -0
- package/amplify/functions/monitoring-view/schema.js +16 -0
- package/amplify/functions/monitoring-view/schema.ts +16 -0
- package/amplify/functions/procedure-action/handler.ts +2 -10
- package/amplify/functions/proposal-actions/proposals.py +20 -3
- package/amplify/plugins/reader-steering/function/handler.py +1 -3
- package/amplify/plugins/reader-steering/function/moderation.py +19 -0
- package/bin/papyrus-infra.mjs +21 -1
- package/components/dev-sandbox-editor-sign-in.tsx +2 -2
- package/components/monitoring-overview-view.tsx +143 -21
- package/components/monitoring-panel.tsx +64 -4
- package/components/news-desk-client-provider.tsx +24 -3
- package/components/news-desk-page.tsx +2 -1
- package/components/news-desk-taxonomy-client.ts +2 -1
- package/components/newsroom-access-gate.tsx +7 -5
- package/components/newsroom-client-shell.tsx +2 -1
- package/components/newsroom-ops-overview.tsx +6 -1
- package/components/newsroom-ops-shell.tsx +1 -1
- package/components/newsroom-organizer.css +48 -0
- package/components/newsroom-references-view.tsx +183 -24
- package/components/notification-center.tsx +345 -63
- package/components/papyrus-console-shell.tsx +2 -1
- package/components/proposals-panel.tsx +21 -24
- package/components/proposals-view.tsx +4 -2
- package/components/reference-feed.tsx +469 -0
- package/components/reference-review-card.tsx +90 -76
- package/components/references-page-loading.tsx +61 -0
- package/components/references-prefetch.ts +47 -0
- package/components/review-rating-grid.tsx +164 -0
- package/components/topic-steering-workspace.tsx +32 -8
- package/infra/amplify-app-shell.js +18 -2
- package/infra/amplify-app-shell.ts +18 -2
- package/infra/chat-stack.js +222 -0
- package/infra/chat-stack.ts +250 -0
- package/infra/chat.test.js +142 -0
- package/infra/chat.test.ts +151 -0
- package/infra/index.js +1 -0
- package/infra/site-config.js +58 -1
- package/infra/site-config.ts +78 -1
- package/lib/category-repository.ts +4 -1
- package/lib/local-preview-feed-fixture.ts +43 -0
- package/lib/local-preview-synthetic-rejects.ts +41 -0
- package/lib/monitoring-client.ts +29 -2
- package/lib/monitoring-overview.ts +52 -1
- package/lib/monitoring-paging.ts +58 -0
- package/lib/newsroom-chat-identity.ts +2 -1
- package/lib/newsroom-organizer-nav.ts +1 -0
- package/lib/proposals-client.ts +29 -0
- package/lib/reader-subject.ts +3 -2
- package/lib/reference-feed-cache.ts +133 -0
- package/lib/reference-feed-ranking.ts +191 -0
- package/lib/reference-preview.ts +112 -0
- package/lib/review-client.ts +38 -1
- package/lib/review-drafts.ts +231 -0
- package/lib/review-fixture.ts +36 -2
- package/lib/review-requests.ts +64 -24
- package/lib/staff-groups.ts +23 -0
- package/lib/staging-gate.ts +2 -2
- package/package.json +14 -1
- package/routes.manifest.json +1 -1
|
@@ -3,13 +3,22 @@
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { useSearchParams } from "next/navigation";
|
|
5
5
|
import {
|
|
6
|
+
decideMonitoringEmailSubmission,
|
|
6
7
|
importMonitoringEmailCandidates,
|
|
7
8
|
loadMonitoringEmailSubmission,
|
|
8
9
|
loadMonitoringOverview,
|
|
10
|
+
loadMonitoringPage,
|
|
9
11
|
resolveMonitoringEmailArticleLink,
|
|
10
12
|
} from "../lib/monitoring-client";
|
|
11
13
|
import { FIXTURE_ALL_PIPELINE_OVERVIEW, FIXTURE_MONITORING_OVERVIEW } from "../lib/monitoring-overview-fixture";
|
|
12
|
-
import
|
|
14
|
+
import { withLoadedPages } from "../lib/monitoring-paging";
|
|
15
|
+
import type {
|
|
16
|
+
EmailReferenceImportItem,
|
|
17
|
+
MonitoringEmailDetails,
|
|
18
|
+
MonitoringListName,
|
|
19
|
+
MonitoringResult,
|
|
20
|
+
} from "../lib/monitoring-overview";
|
|
21
|
+
import type { MonitoringLoadedPages } from "../lib/monitoring-paging";
|
|
13
22
|
import { EmailSubmissions, MonitoringOverviewView, type MonitoringMode } from "./monitoring-overview-view";
|
|
14
23
|
|
|
15
24
|
const REFRESH_MILLISECONDS = 60_000;
|
|
@@ -39,7 +48,8 @@ function MonitoringEmailReceipt({ receiptId, isDemo }: { receiptId: string; isDe
|
|
|
39
48
|
return <div data-news-desk-admin-panel="monitoring">
|
|
40
49
|
{detail === null ? <p role="status">Loading email submission…</p> :
|
|
41
50
|
<EmailSubmissions items={"submission" in detail ? [detail.submission] : []} detail={detail}
|
|
42
|
-
onResolveArticleLink={resolveMonitoringEmailArticleLink} onImportCandidates={importMonitoringEmailCandidates}
|
|
51
|
+
onResolveArticleLink={resolveMonitoringEmailArticleLink} onImportCandidates={importMonitoringEmailCandidates}
|
|
52
|
+
onDecide={decideMonitoringEmailSubmission} />}
|
|
43
53
|
</div>;
|
|
44
54
|
}
|
|
45
55
|
|
|
@@ -48,6 +58,18 @@ function MonitoringOverviewPanel({ isAdmin, isDemo }: { isAdmin: boolean; isDemo
|
|
|
48
58
|
const [mode, setMode] = useState<MonitoringMode>("publication");
|
|
49
59
|
const [result, setResult] = useState<MonitoringResult | null>(null);
|
|
50
60
|
const [emailDetail, setEmailDetail] = useState<MonitoringEmailDetails | null>(null);
|
|
61
|
+
// Pages the editor loaded beyond the newest page of each list. The 60 second refresh re-reads only the newest
|
|
62
|
+
// page, so what was loaded stays on screen.
|
|
63
|
+
const [loaded, setLoaded] = useState<MonitoringLoadedPages>({});
|
|
64
|
+
const [heldOnly, setHeldOnly] = useState(false);
|
|
65
|
+
const [held, setHeld] = useState<MonitoringLoadedPages["emailSubmissions"] | null>(null);
|
|
66
|
+
const [loadingList, setLoadingList] = useState<MonitoringListName | null>(null);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
setLoaded({});
|
|
70
|
+
setHeldOnly(false);
|
|
71
|
+
setHeld(null);
|
|
72
|
+
}, [mode]);
|
|
51
73
|
|
|
52
74
|
useEffect(() => {
|
|
53
75
|
if (isDemo) {
|
|
@@ -78,13 +100,51 @@ function MonitoringOverviewPanel({ isAdmin, isDemo }: { isAdmin: boolean; isDemo
|
|
|
78
100
|
setEmailDetail(next);
|
|
79
101
|
};
|
|
80
102
|
|
|
103
|
+
// Appends the next page of a list, or (held filter) the next page of held messages.
|
|
104
|
+
const loadMore = async (list: MonitoringListName) => {
|
|
105
|
+
if (isDemo || !result || !("scope" in result) || loadingList) return;
|
|
106
|
+
const current = withLoadedPages(result as never, loaded, heldOnly ? held : null);
|
|
107
|
+
const cursor = current.cursors?.[list];
|
|
108
|
+
if (!cursor) return;
|
|
109
|
+
setLoadingList(list);
|
|
110
|
+
try {
|
|
111
|
+
const page = await loadMonitoringPage(list, cursor, mode, heldOnly && list === "emailSubmissions");
|
|
112
|
+
if (!("rows" in page)) return;
|
|
113
|
+
if (heldOnly && list === "emailSubmissions") {
|
|
114
|
+
setHeld((existing) => ({ rows: [...(existing?.rows ?? []), ...page.rows] as never, cursor: page.cursor }));
|
|
115
|
+
} else {
|
|
116
|
+
setLoaded((existing) => ({ ...existing, [list]: { rows: [...(existing[list]?.rows ?? []), ...page.rows], cursor: page.cursor } }) as MonitoringLoadedPages);
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
setLoadingList(null);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Held for review restarts the email list at its newest held message, however old.
|
|
124
|
+
const changeHeldOnly = async (next: boolean) => {
|
|
125
|
+
setHeldOnly(next);
|
|
126
|
+
setHeld(null);
|
|
127
|
+
if (!next || isDemo) return;
|
|
128
|
+
setLoadingList("emailSubmissions");
|
|
129
|
+
try {
|
|
130
|
+
const page = await loadMonitoringPage("emailSubmissions", null, mode, true);
|
|
131
|
+
if ("rows" in page) setHeld({ rows: page.rows as never, cursor: page.cursor });
|
|
132
|
+
else setHeldOnly(false);
|
|
133
|
+
} finally {
|
|
134
|
+
setLoadingList(null);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const shown = result && "scope" in result ? withLoadedPages(result as never, loaded, heldOnly ? held ?? { rows: [], cursor: null } : null) : result;
|
|
139
|
+
|
|
81
140
|
const resolveEmailArticleLink = (receiptId: string, candidateIndex: number) =>
|
|
82
141
|
resolveMonitoringEmailArticleLink(receiptId, candidateIndex);
|
|
83
142
|
|
|
84
143
|
const importEmailCandidates = (receiptId: string, candidates: EmailReferenceImportItem[]) =>
|
|
85
144
|
importMonitoringEmailCandidates(receiptId, candidates);
|
|
86
145
|
|
|
87
|
-
return <MonitoringOverviewView result={
|
|
146
|
+
return <MonitoringOverviewView result={shown} canViewAll={isAdmin} mode={mode} onModeChange={setMode} demo={isDemo}
|
|
88
147
|
emailDetail={emailDetail} onEmailOpen={isDemo ? () => {} : openEmail} onResolveArticleLink={isDemo ? undefined : resolveEmailArticleLink}
|
|
89
|
-
onImportCandidates={importEmailCandidates}
|
|
148
|
+
onImportCandidates={importEmailCandidates} onDecide={isDemo ? undefined : decideMonitoringEmailSubmission}
|
|
149
|
+
paging={isDemo ? undefined : { onLoadMore: (list) => void loadMore(list), loadingList, heldOnly, onHeldOnlyChange: (next) => void changeHeldOnly(next) }} />;
|
|
90
150
|
}
|
|
@@ -6,6 +6,9 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, use
|
|
|
6
6
|
import type { DevSandboxEditorAuth } from "../lib/dev-sandbox-editor-auth";
|
|
7
7
|
import type { CategorySteeringDashboard } from "../lib/category-repository";
|
|
8
8
|
import { ensureDevSandboxEditorSession } from "./dev-sandbox-editor-sign-in";
|
|
9
|
+
import { discardReferencesHeadStart, prefetchReferencesFeed } from "./references-prefetch";
|
|
10
|
+
import { SITE_BRAND } from "../lib/site-brand";
|
|
11
|
+
import { clearSavedNewsroomDisplay, loadSessionDisplay, saveSessionDisplay } from "../lib/reference-feed-cache";
|
|
9
12
|
import {
|
|
10
13
|
loadEditorAssignmentsData,
|
|
11
14
|
loadEditorDoctrineRecordsData,
|
|
@@ -75,7 +78,12 @@ export function NewsDeskClientProvider({
|
|
|
75
78
|
));
|
|
76
79
|
return;
|
|
77
80
|
}
|
|
78
|
-
|
|
81
|
+
// A recent confirmed session is shown (name, "loading" instead of "checking") while the real check runs again.
|
|
82
|
+
// Display only: the check below still decides access, and the backend authorizes every call.
|
|
83
|
+
const remembered = loadSessionDisplay(SITE_BRAND.id);
|
|
84
|
+
setShell((current) => (remembered && !current.dashboard
|
|
85
|
+
? { ...beginAccessCheck(current), phase: "loadingDesk" as const, auth: { status: "signedIn" as const, label: remembered.label } }
|
|
86
|
+
: beginAccessCheck(current)));
|
|
79
87
|
|
|
80
88
|
try {
|
|
81
89
|
await ensureDevSandboxEditorSession(devSandboxEditorAuth);
|
|
@@ -88,6 +96,8 @@ export function NewsDeskClientProvider({
|
|
|
88
96
|
if (bootstrapSequenceRef.current !== sequence) return;
|
|
89
97
|
|
|
90
98
|
if (access.status === "signedOut") {
|
|
99
|
+
clearSavedNewsroomDisplay(SITE_BRAND.id);
|
|
100
|
+
discardReferencesHeadStart();
|
|
91
101
|
setShell((current) => resolveSignedOut(current, access.auth));
|
|
92
102
|
return;
|
|
93
103
|
}
|
|
@@ -100,7 +110,10 @@ export function NewsDeskClientProvider({
|
|
|
100
110
|
return;
|
|
101
111
|
}
|
|
102
112
|
|
|
113
|
+
saveSessionDisplay(SITE_BRAND.id, { label: access.auth.label, isAdmin: access.isAdmin });
|
|
103
114
|
setShell((current) => beginDeskLoad(current, access.auth));
|
|
115
|
+
// The References reads do not depend on the summary dashboard: start them now, side by side with it.
|
|
116
|
+
if (typeof window !== "undefined" && /\/newsroom\/references(\/|$)/.test(window.location.pathname)) prefetchReferencesFeed();
|
|
104
117
|
|
|
105
118
|
try {
|
|
106
119
|
const dashboard = await loadEditorNewsDeskDashboard({ isAdmin: access.isAdmin });
|
|
@@ -113,7 +126,8 @@ export function NewsDeskClientProvider({
|
|
|
113
126
|
return;
|
|
114
127
|
}
|
|
115
128
|
if (isAuthorizationError(error)) {
|
|
116
|
-
|
|
129
|
+
// The session already has editor rights, so the backend refusing a read is not a missing role. Say what was refused.
|
|
130
|
+
setShell((current) => resolveDashboardFailure(current, access.auth, refusedReadMessage(error)));
|
|
117
131
|
return;
|
|
118
132
|
}
|
|
119
133
|
setShell((current) => resolveDashboardFailure(
|
|
@@ -182,6 +196,8 @@ export function NewsDeskClientProvider({
|
|
|
182
196
|
const unsubscribe = Hub.listen("auth", ({ payload }) => {
|
|
183
197
|
if (payload.event === "signedOut") {
|
|
184
198
|
bootstrapSequenceRef.current += 1;
|
|
199
|
+
clearSavedNewsroomDisplay(SITE_BRAND.id);
|
|
200
|
+
discardReferencesHeadStart();
|
|
185
201
|
setShell((current) => resolveSignedOut(current, { status: "signedOut", label: "Signed out" }));
|
|
186
202
|
return;
|
|
187
203
|
}
|
|
@@ -215,9 +231,14 @@ function isAuthorizationError(error: unknown): boolean {
|
|
|
215
231
|
return /not authorized|unauthorized|access denied|forbidden/i.test(message);
|
|
216
232
|
}
|
|
217
233
|
|
|
234
|
+
function refusedReadMessage(error: unknown): string {
|
|
235
|
+
const detail = error instanceof Error ? error.message : String(error ?? "");
|
|
236
|
+
return `The Newsroom backend refused a read for this signed-in account (${detail}). The account has editor or admin rights, so this is a backend authorization rule, not a missing role.`;
|
|
237
|
+
}
|
|
238
|
+
|
|
218
239
|
function resolvePrivateReadFailure(state: NewsDeskShellState, auth: NewsDeskShellState["auth"], error: unknown, fallback: string): NewsDeskShellState {
|
|
219
240
|
if (isUnauthenticatedError(error)) return resolveSignedOut(state, { status: "signedOut", label: "Signed out" });
|
|
220
|
-
if (isAuthorizationError(error)) return
|
|
241
|
+
if (isAuthorizationError(error)) return resolveDashboardFailure(state, auth, refusedReadMessage(error));
|
|
221
242
|
return resolveDashboardFailure(state, auth, error instanceof Error ? error.message : fallback);
|
|
222
243
|
}
|
|
223
244
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withReferenceFeedFixture } from "../lib/local-preview-feed-fixture";
|
|
1
2
|
import { resolveNewsDeskTab } from "../lib/newsroom-section-routing";
|
|
2
3
|
import { NewsDeskWorkspace, type NewsDeskTab } from "./topic-steering-workspace";
|
|
3
4
|
import {
|
|
@@ -67,7 +68,7 @@ export async function NewsDeskPage({ section: routeSection, selectionPath, searc
|
|
|
67
68
|
const analysisProfiles = await loadAnalysisProfileSummaries(getBrandAnalysisProfilesPath(brand));
|
|
68
69
|
const configuredCorpora = await loadConfiguredCorpusSummaries(getBrandSteeringConfigPath(brand));
|
|
69
70
|
const dashboard = useDemoDashboard
|
|
70
|
-
? createDemoCategorySteeringDashboard(brand.id, getBrandNewsroomSectionsConfigPath(brand))
|
|
71
|
+
? withReferenceFeedFixture(createDemoCategorySteeringDashboard(brand.id, getBrandNewsroomSectionsConfigPath(brand)))
|
|
71
72
|
: await loadCategorySteeringDashboard();
|
|
72
73
|
return <NewsDeskWorkspace analysisProfiles={analysisProfiles} configuredCorpora={configuredCorpora} dashboard={dashboard} initialSelection={initialSelection} initialTab={initialTab} />;
|
|
73
74
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { loadReferenceReviewDetails } from "../lib/reference-review-details";
|
|
4
|
+
import { hasEditorRights } from "../lib/staff-groups";
|
|
4
5
|
|
|
5
6
|
import { isEditorialWorkAssignment } from "../lib/newsroom-assignments";
|
|
6
7
|
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
@@ -838,7 +839,7 @@ export async function loadEditorResolvedAccessState(): Promise<EditorAccessState
|
|
|
838
839
|
if (!refreshed.hasSession || refreshed.auth.status === "signedOut") {
|
|
839
840
|
return { status: "signedOut", isEditor: false, isAdmin: false, auth: refreshed.auth, error: null };
|
|
840
841
|
}
|
|
841
|
-
const isEditor = refreshed.groups
|
|
842
|
+
const isEditor = hasEditorRights(refreshed.groups);
|
|
842
843
|
if (!isEditor) {
|
|
843
844
|
return {
|
|
844
845
|
status: "forbidden",
|
|
@@ -9,24 +9,26 @@ import { NewsroomOpsShell } from "./newsroom-ops-shell";
|
|
|
9
9
|
import { NewsroomOverviewSkeleton } from "./newsroom-overview-skeleton";
|
|
10
10
|
import { ReaderAuthControl } from "./reader-auth-control";
|
|
11
11
|
import { buttonVariants } from "./ui/button";
|
|
12
|
+
import { ReferencesPageLoading } from "./references-page-loading";
|
|
12
13
|
|
|
13
|
-
export function NewsDeskAccessGate({ shell, showSectionTabs = false }: { shell: NewsDeskShellState | null; showSectionTabs?: boolean }) {
|
|
14
|
+
export function NewsDeskAccessGate({ initialTab, shell, showSectionTabs = false }: { initialTab?: string; shell: NewsDeskShellState | null; showSectionTabs?: boolean }) {
|
|
14
15
|
const accessPhase = shell?.phase ?? "checkingAccess";
|
|
15
16
|
const pending = accessPhase === "checkingAccess" || accessPhase === "loadingDesk";
|
|
17
|
+
const referencesPage = initialTab === "references";
|
|
16
18
|
|
|
17
19
|
return (
|
|
18
20
|
<NewsroomOpsShell
|
|
19
|
-
activeTab="overview"
|
|
21
|
+
activeTab={referencesPage ? "references" : "overview"}
|
|
20
22
|
appTitle={SITE_BRAND.appTitle}
|
|
21
23
|
backHref="/"
|
|
22
24
|
backLabel={SITE_BRAND.backToHomeLabel}
|
|
23
25
|
headerActions={(
|
|
24
26
|
<Link className={cn(buttonVariants({ variant: "ghost", size: "sm" }))} href="/settings">Settings</Link>
|
|
25
27
|
)}
|
|
26
|
-
pageTitle={showSectionTabs ? "Notification Center" : "Newsroom"}
|
|
27
|
-
showNavigation={showSectionTabs}
|
|
28
|
+
pageTitle={referencesPage ? "References" : showSectionTabs ? "Notification Center" : "Newsroom"}
|
|
29
|
+
showNavigation={showSectionTabs || referencesPage}
|
|
28
30
|
>
|
|
29
|
-
{pending ? (showSectionTabs ? <NewsroomOverviewSkeleton /> : (
|
|
31
|
+
{pending && referencesPage ? <ReferencesPageLoading /> : pending ? (showSectionTabs ? <NewsroomOverviewSkeleton /> : (
|
|
30
32
|
<div aria-label="Loading newsroom" className="space-y-4" data-newsroom-page-skeleton role="status">
|
|
31
33
|
<div className="h-6 w-48 rounded bg-muted" />
|
|
32
34
|
<div className="h-4 w-3/4 rounded bg-muted" />
|
|
@@ -2,13 +2,14 @@ import { NewsDeskClientProvider } from "./news-desk-client-provider";
|
|
|
2
2
|
import { createDemoCategorySteeringDashboard } from "../lib/category-repository";
|
|
3
3
|
import { resolveDevSandboxEditorAuth } from "../lib/dev-sandbox-editor-auth";
|
|
4
4
|
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
5
|
+
import { withReferenceFeedFixture } from "../lib/local-preview-feed-fixture";
|
|
5
6
|
import { getBrandNewsroomSectionsConfigPath, resolveActiveSiteBrand } from "../lib/site-brand-tenant";
|
|
6
7
|
|
|
7
8
|
export function NewsroomClientShell({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
8
9
|
const devSandboxEditorAuth = resolveDevSandboxEditorAuth();
|
|
9
10
|
const activeBrand = resolveActiveSiteBrand();
|
|
10
11
|
const localPreviewDashboard = isLocalPreviewEnabled()
|
|
11
|
-
? createDemoCategorySteeringDashboard(activeBrand.id, getBrandNewsroomSectionsConfigPath(activeBrand))
|
|
12
|
+
? withReferenceFeedFixture(createDemoCategorySteeringDashboard(activeBrand.id, getBrandNewsroomSectionsConfigPath(activeBrand)))
|
|
12
13
|
: null;
|
|
13
14
|
|
|
14
15
|
return (
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { newsroomInboxRowClassName } from "../lib/newsroom-list-selection";
|
|
25
25
|
import type { RelevanceDecisionView } from "../lib/relevance-decisions";
|
|
26
26
|
import { NotificationCenter } from "./notification-center";
|
|
27
|
+
import { CyclotronRow, predictionFromDecision } from "./reference-review-card";
|
|
27
28
|
|
|
28
29
|
type NewsroomOpsOverviewProps = {
|
|
29
30
|
assignments: AssignmentRecord[];
|
|
@@ -105,9 +106,13 @@ export function NewsroomOpsOverview({
|
|
|
105
106
|
href={href}
|
|
106
107
|
key={lineageId}
|
|
107
108
|
>
|
|
108
|
-
<div className="min-w-0 flex-1">
|
|
109
|
+
<div className="min-w-0 flex-1 space-y-1">
|
|
109
110
|
<p className="truncate text-sm font-medium text-foreground">{referenceDisplayTitle(reference)}</p>
|
|
110
111
|
<p className="text-xs text-muted-foreground">Review source intake · {formatReferenceListDate(reference)}</p>
|
|
112
|
+
<div data-needs-attention-suggestion={lineageId}>
|
|
113
|
+
<CyclotronRow prediction={decisions.get(lineageId) ? predictionFromDecision(decisions.get(lineageId)!) : { state: "none" }} />
|
|
114
|
+
</div>
|
|
115
|
+
<p className="text-xs font-medium text-foreground">Open it to Accept or Reject in the review queue</p>
|
|
111
116
|
</div>
|
|
112
117
|
<Badge className="mt-0.5 shrink-0" variant="outline">Pending</Badge>
|
|
113
118
|
</Link>
|
|
@@ -165,7 +165,7 @@ export function NewsroomOpsShell({ activeTab, appTitle, backHref = "/", backLabe
|
|
|
165
165
|
<Link className="organizer-masthead" href={backHref} title={backLabel}>{appTitle}</Link>
|
|
166
166
|
<h1 data-newsroom-ops-title>{pageTitle}</h1><OrganizerSearch demo={demo} /><div className="organizer-header-actions">{headerActions}</div><Account placement="header" />
|
|
167
167
|
</header>
|
|
168
|
-
<main className="organizer-main">{demo && <p className="organizer-demo-notice">Demo workspace · synthetic examples</p>}{children}</main>
|
|
168
|
+
<main className="organizer-main">{demo && !session?.shell.dashboard?.realDataPreview && <p className="organizer-demo-notice">Demo workspace · synthetic examples</p>}{children}</main>
|
|
169
169
|
</div>
|
|
170
170
|
{showNavigation && <NewsroomMobileNavigation activeTab={activeTab} items={ORGANIZER_PRIMARY_NAV.map(item => ({ ...item, href: getNewsroomNavHref(item.path, demo) }))}
|
|
171
171
|
chat={consoleState?.shouldOfferConsole ? { open: consoleState.open, onOpen: consoleState.openConsole } : undefined}
|
|
@@ -347,3 +347,51 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
|
|
|
347
347
|
.news-desk-columns--administration .news-desk-newsletter__text{margin:0;padding:12px;max-height:280px;overflow:auto;border:1px solid var(--org-line);font:400 12px/1.5 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
348
348
|
.news-desk-columns--administration .news-desk-newsletter__filters{display:flex;flex-wrap:wrap;gap:12px 24px}
|
|
349
349
|
.news-desk-columns--administration .news-desk-newsletter__filters .news-desk-doctrine-card__field{min-width:180px}
|
|
350
|
+
|
|
351
|
+
/* The References feed is rounded and borderless (Ryan's design rule): flat tones and rounded rectangles instead of lines and boxes.
|
|
352
|
+
The newsroom chrome squares every "rounded" class above; inside a [data-feed-surface] the rounded classes mean what they say.
|
|
353
|
+
The repeated attribute raises specificity over the squaring rules without !important escalation. */
|
|
354
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-full"],
|
|
355
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-full"] { border-radius: 9999px !important; }
|
|
356
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-[20px]"],
|
|
357
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-[20px]"] { border-radius: 20px !important; }
|
|
358
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-2xl"],
|
|
359
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-2xl"] { border-radius: 16px !important; }
|
|
360
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-[16px]"],
|
|
361
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-[16px]"] { border-radius: 16px !important; }
|
|
362
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-3xl"],
|
|
363
|
+
.newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-3xl"] { border-radius: 24px !important; }
|
|
364
|
+
|
|
365
|
+
/* Feed tones. Light: cards a touch lighter than the page, footers between, controls a touch darker. Dark: each step lifts toward the ink.
|
|
366
|
+
Also set on the portaled mobile sheet, which carries the theme attributes itself. */
|
|
367
|
+
[data-kanbus-theme] [data-feed-surface], [data-kanbus-theme][data-feed-surface] {
|
|
368
|
+
--feed-card: color-mix(in oklab, var(--kb-theme-paper) 35%, white);
|
|
369
|
+
--feed-footer: color-mix(in oklab, var(--kb-theme-paper) 70%, white);
|
|
370
|
+
--feed-control: color-mix(in oklab, var(--kb-theme-paper) 90%, var(--kb-theme-ink));
|
|
371
|
+
}
|
|
372
|
+
[data-kanbus-theme][data-color-mode="dark"] [data-feed-surface], [data-kanbus-theme][data-color-mode="dark"][data-feed-surface] {
|
|
373
|
+
--feed-card: color-mix(in oklab, var(--kb-theme-paper) 94%, var(--kb-theme-ink));
|
|
374
|
+
--feed-footer: color-mix(in oklab, var(--kb-theme-paper) 90%, var(--kb-theme-ink));
|
|
375
|
+
--feed-control: color-mix(in oklab, var(--kb-theme-paper) 80%, var(--kb-theme-ink));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* Borderless detail pane, Add panel and mobile sheet ([data-feed-flat]): no border, rule or box anywhere inside. Regions are told apart
|
|
379
|
+
by tone (card, footer, control) and shapes are rounded rectangles. The repeated attribute outranks the newsroom chrome's own rules. */
|
|
380
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat],
|
|
381
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] * { border-width: 0 !important; }
|
|
382
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] *:not(:focus-visible) { box-shadow: none; }
|
|
383
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] :is([data-slot=separator], hr) { display: none !important; }
|
|
384
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] :is(button, [role=button], [data-slot=badge]):not([aria-pressed=true]):not([data-slot=sheet-close]):not([class*="hover:bg-muted"]) {
|
|
385
|
+
background: var(--feed-control) !important; border-radius: 9999px !important; color: var(--foreground, inherit);
|
|
386
|
+
}
|
|
387
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] :is(button, [role=button])[aria-pressed=true]:not([class*="destructive"]) {
|
|
388
|
+
background: var(--primary) !important; color: var(--primary-foreground) !important; border-radius: 9999px !important;
|
|
389
|
+
}
|
|
390
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] :is(button, [role=button])[aria-pressed=true][class*="destructive"] { border-radius: 9999px !important; }
|
|
391
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] :is(textarea, input:not([type=radio]):not([type=checkbox]), select, details, [data-cyclotron-choice]:not([data-predicted])) {
|
|
392
|
+
background: var(--feed-control) !important; border-radius: 16px !important;
|
|
393
|
+
}
|
|
394
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] [data-cyclotron-choice] { border-radius: 9999px !important; }
|
|
395
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] [data-review-queue-bar] { background: var(--feed-footer) !important; }
|
|
396
|
+
/* The rating grid keeps its own rounded rectangles in the borderless panes (not the pill shape the flat rules give other buttons). */
|
|
397
|
+
[data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] [data-rating-grid] :is(button, [data-cyclotron-choice]) { border-radius: 16px !important; }
|