@anthusai/papyrus 5.58.0 → 5.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/amplify/data/message-actions.js +68 -0
- package/amplify/data/message-actions.ts +70 -0
- package/amplify/data/resolvers/delete-own-message.js +30 -0
- package/amplify/data/resolvers/edit-message.js +33 -0
- package/amplify/data/resolvers/moderate-message.js +32 -0
- package/amplify/data/resolvers/post-message.js +68 -0
- package/amplify/data/resolvers/rename-thread.js +28 -0
- package/amplify/data/resource.js +8 -5
- package/amplify/data/resource.ts +8 -5
- package/amplify/data/schema.js +14 -7
- package/amplify/data/schema.ts +14 -7
- package/components/conversation-context.tsx +29 -2
- package/components/conversation-provider.tsx +5 -1
- package/components/conversation-thread.tsx +133 -19
- package/components/conversations-memory-source.ts +43 -6
- package/components/conversations-network-source.ts +13 -1
- package/components/conversations-source.ts +15 -3
- package/components/conversations-view.tsx +437 -76
- package/components/list-detail-layout.tsx +44 -0
- package/components/needs-me-bodies.tsx +14 -14
- package/components/needs-me-feed.tsx +8 -18
- package/components/news-desk-taxonomy-client.ts +142 -57
- package/components/newsroom-article-editor.tsx +10 -4
- package/components/newsroom-organizer.css +10 -1
- package/components/newsroom-references-view.tsx +41 -100
- package/components/notification-cards.tsx +4 -4
- package/components/notification-center.tsx +5 -7
- package/components/proposals-view.tsx +4 -4
- package/components/reference-conversation.tsx +5 -0
- package/components/reference-feed.tsx +69 -85
- package/components/reference-review-card.tsx +1 -1
- package/components/references-page-loading.tsx +12 -6
- package/components/topic-steering-workspace.tsx +4 -4
- package/components/use-conversation.ts +36 -2
- package/components/use-feed-preferences.ts +33 -0
- package/lib/category-repository.ts +7 -38
- package/lib/console-chat-client.ts +8 -178
- package/lib/conversation.ts +73 -2
- package/lib/needs-me-items.ts +4 -4
- package/lib/needs-me-ranking.ts +9 -22
- package/lib/newsroom-board.ts +10 -1
- package/lib/newsroom-demo-dashboard.ts +0 -7
- package/lib/newsroom-organizer-nav.ts +2 -2
- package/lib/post-message.ts +54 -0
- package/lib/proposals-admin.ts +2 -2
- package/lib/reference-feed-ranking.ts +77 -160
- package/lib/review-drafts.ts +2 -2
- package/lib/review-fixture.ts +1 -1
- package/lib/review-requests.ts +4 -4
- package/lib/semantic-graph.ts +1 -1
- package/package.json +4 -1
- package/routes.manifest.json +1 -1
|
@@ -54,7 +54,16 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
|
|
|
54
54
|
.organizer-board-body{position:relative;display:grid;grid-template-columns:minmax(0,1fr);flex:1;min-width:0;min-height:0;gap:16px}
|
|
55
55
|
.organizer-board-body[data-detail-open=true]{grid-template-columns:minmax(0,1fr) clamp(320px,34vw,1000px)}
|
|
56
56
|
.organizer-board-body>.kb-issue-collection{height:100%;min-height:0;overflow:auto}
|
|
57
|
-
|
|
57
|
+
/* The detail view is a solid surface from its first frame: the theme's panel token (opaque), with the card token as a fallback if
|
|
58
|
+
a theme has not defined it yet, and never any opacity animation, so the board never shows through it while it loads. */
|
|
59
|
+
.organizer-board-body>.kb-issue-detail{overflow:auto;min-height:0;position:relative;z-index:2;opacity:1;background-color:var(--kb-theme-panel,var(--card,Canvas));isolation:isolate}
|
|
60
|
+
/* One stable shape while an article loads and after: the preview card (and its skeleton) reserve the same height, so the page below it does not move. */
|
|
61
|
+
.newsroom-embedded-article :is([data-newsroom-article-preview],[data-skeleton-preview]){min-height:min(28rem,55dvh)}
|
|
62
|
+
/* The loading skeleton is the loaded editor's size block for block: the toolbar wraps onto more rows as the panel narrows (a container query,
|
|
63
|
+
because the panel's width, not the window's, decides), and the three fields stack when it is narrow. */
|
|
64
|
+
[data-skeleton-toolbar]{height:44px}[data-skeleton-fields]{height:76px}[data-skeleton-preview]{height:min(28rem,55dvh)}
|
|
65
|
+
@container (max-width:719px){[data-skeleton-toolbar]{height:80px}}
|
|
66
|
+
@container (max-width:399px){[data-skeleton-toolbar]{height:116px}[data-skeleton-fields]{height:208px}}
|
|
58
67
|
.organizer-board-body .kb-issue-detail a:not(.organizer-header-button){display:block;min-height:44px;text-decoration:underline;padding:12px 0}
|
|
59
68
|
.organizer-menu{height:min(85dvh,760px);max-height:85dvh;display:flex;flex-direction:column;padding:16px;border-radius:24px 24px 0 0;gap:20px}
|
|
60
69
|
.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}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { DETAIL_PANE_WIDTH, LIST_HIDDEN_WHEN_DETAIL_OPEN } from "./list-detail-layout";
|
|
3
4
|
import type { PrimingPanelProps } from "cyclotron/components/priming-panel";
|
|
4
5
|
import { ReferenceAddEntry } from "./reference-add-entry";
|
|
5
6
|
import { ReferenceCurationControls, useReferenceImport } from "./reference-curation-controls";
|
|
6
7
|
|
|
7
|
-
import { ExternalLinkIcon, LayoutGridIcon, ListIcon, PlusIcon } from "lucide-react";
|
|
8
|
+
import { ArrowLeftIcon, ExternalLinkIcon, LayoutGridIcon, ListIcon, PlusIcon } from "lucide-react";
|
|
8
9
|
import { usePathname } from "next/navigation";
|
|
9
10
|
import { useCallback, useEffect, useMemo, useRef, useState, type MutableRefObject } from "react";
|
|
10
11
|
import { Badge } from "./ui/badge";
|
|
@@ -13,10 +14,6 @@ import { Card, CardDescription, CardHeader, CardTitle } from "./ui/card";
|
|
|
13
14
|
import { ScrollArea } from "./ui/scroll-area";
|
|
14
15
|
import { Separator } from "./ui/separator";
|
|
15
16
|
import {
|
|
16
|
-
Sheet,
|
|
17
|
-
SheetContent,
|
|
18
|
-
SheetHeader,
|
|
19
|
-
SheetTitle,
|
|
20
17
|
} from "./ui/sheet";
|
|
21
18
|
import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
|
|
22
19
|
import type { ReferenceAttachmentRecord, ReferenceRecord } from "../lib/category-repository";
|
|
@@ -45,21 +42,19 @@ import { newsroomListRowClassName } from "../lib/newsroom-list-selection";
|
|
|
45
42
|
import { ReferenceSourcePreview } from "./reference-source-preview";
|
|
46
43
|
import { ReferenceStories } from "./reference-stories";
|
|
47
44
|
import { useReferencePreviewAttachments } from "./use-reference-preview-attachments";
|
|
48
|
-
import { useMediaQuery } from "./use-media-query";
|
|
49
45
|
import { FEED_SURFACE_PROPS, FEED_SURFACE_STYLE, ReferenceFeed } from "./reference-feed";
|
|
46
|
+
import { useFeedPreferences } from "./use-feed-preferences";
|
|
50
47
|
import { ReferenceCapturePanel } from "./reference-capture-panel";
|
|
51
48
|
import { useReferenceCapture, type CorrectCaptureAction } from "./reference-capture-client";
|
|
52
49
|
import { useRatingStore, type RatingController } from "./use-reference-ratings";
|
|
53
50
|
import { ReferenceConversation } from "./reference-conversation";
|
|
54
51
|
import { useReferenceMessageCounts } from "./use-conversation";
|
|
55
52
|
import { captureStatusesByLineage } from "../lib/reference-capture";
|
|
56
|
-
import { applyFeedOrderSnapshot,
|
|
53
|
+
import { applyFeedOrderSnapshot, buildFeedItems, feedLeadId, takeFeedOrderSnapshot, visibleFeedItems, type FeedOrderSnapshot } from "../lib/reference-feed-ranking";
|
|
57
54
|
import { referenceDek, referencePreviewsByLineage, type ReferencePreview } from "../lib/reference-preview";
|
|
58
55
|
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
59
56
|
import { chooseSyntheticRejects, withSyntheticRejects } from "../lib/local-preview-synthetic-rejects";
|
|
60
57
|
import { cn } from "../lib/utils";
|
|
61
|
-
import { getKanbusTheme, SITE_BRAND } from "../lib/site-brand";
|
|
62
|
-
import { useResolvedPapyrusTheme } from "./use-resolved-papyrus-theme";
|
|
63
58
|
import {
|
|
64
59
|
ReviewCard,
|
|
65
60
|
predictionFromDecision,
|
|
@@ -405,12 +400,8 @@ export function NewsroomReferencesView({
|
|
|
405
400
|
savedCopy = null,
|
|
406
401
|
}: NewsroomReferencesViewProps) {
|
|
407
402
|
const pathname = usePathname();
|
|
408
|
-
// The portaled mobile sheet sits outside the newsroom root, so it carries the theme attributes itself (same as useNewsroomThemeAttributes).
|
|
409
|
-
const themeMode = useResolvedPapyrusTheme();
|
|
410
|
-
const themeAttributes = { "data-kanbus-theme": getKanbusTheme(SITE_BRAND), "data-color-mode": themeMode };
|
|
411
403
|
const sessionImport = useReferenceImport(disabled);
|
|
412
404
|
const [importedCount, setImportedCount] = useState(0);
|
|
413
|
-
const isMobileDetail = useMediaQuery("(max-width: 767px)");
|
|
414
405
|
const pathnameLineageId = useMemo(
|
|
415
406
|
() => parseReferenceLineageIdFromNewsroomPathname(pathname),
|
|
416
407
|
[pathname],
|
|
@@ -449,20 +440,13 @@ export function NewsroomReferencesView({
|
|
|
449
440
|
}, [referenceAttachments, savedCopy, showingSavedCopy]);
|
|
450
441
|
const [view, setView] = useState<ReferenceView>("feed");
|
|
451
442
|
const [addOpen, setAddOpen] = useState(false);
|
|
452
|
-
const
|
|
443
|
+
const { sort: feedSort, setSort: setFeedSort, show: feedShow, setShow: setFeedShow } = useFeedPreferences();
|
|
453
444
|
const { counts: messageCounts, setCount: setMessageCount } = useReferenceMessageCounts(!loading);
|
|
454
445
|
const [visitRated, setVisitRated] = useState<ReadonlySet<string>>(() => new Set());
|
|
455
446
|
const [mountedAt] = useState(() => Date.now());
|
|
456
447
|
const markVisitRated = useCallback((lineageId: string) => {
|
|
457
448
|
setVisitRated((current) => (current.has(lineageId) ? current : new Set(current).add(lineageId)));
|
|
458
449
|
}, []);
|
|
459
|
-
// Preview only: ?feedTau=<days> stands in for the learned freshness period, to review the Earlier section on a young corpus.
|
|
460
|
-
const tauDaysOverride = useMemo(() => {
|
|
461
|
-
if (typeof window === "undefined" || !isLocalPreviewEnabled()) return null;
|
|
462
|
-
const requested = Number(new URLSearchParams(window.location.search).get("feedTau"));
|
|
463
|
-
return Number.isFinite(requested) && requested > 0 ? requested : null;
|
|
464
|
-
}, []);
|
|
465
|
-
|
|
466
450
|
// Preview only: ?feedState=loading holds the page in its loading state, to review the card skeletons.
|
|
467
451
|
const previewSkeleton = useMemo(
|
|
468
452
|
() => typeof window !== "undefined" && isLocalPreviewEnabled() && new URLSearchParams(window.location.search).get("feedState") === "loading",
|
|
@@ -470,24 +454,24 @@ export function NewsroomReferencesView({
|
|
|
470
454
|
);
|
|
471
455
|
|
|
472
456
|
const canonicalReferences = useMemo(() => selectCanonicalReferenceRecords(references), [references]);
|
|
473
|
-
const
|
|
474
|
-
() => partitionReferenceFeed(canonicalReferences, decisions, { now: mountedAt, visitRated, tauDaysOverride }),
|
|
475
|
-
[canonicalReferences, decisions, mountedAt, tauDaysOverride, visitRated],
|
|
476
|
-
);
|
|
457
|
+
const liveItems = useMemo(() => buildFeedItems(canonicalReferences, decisions), [canonicalReferences, decisions]);
|
|
477
458
|
// The feed's order is taken ONCE per visit, when the feed has loaded (References and predictions), and then left alone. Selecting an
|
|
478
459
|
// item, rating one, a background refresh or a cache revalidation changes cards' content, never their places. Items that arrive later
|
|
479
|
-
// wait behind a "N new items: show" pill.
|
|
460
|
+
// wait behind a "N new items: show" pill. The same holds for both sorts and for Include rated.
|
|
480
461
|
const orderSnapshot = useRef<FeedOrderSnapshot | null>(null);
|
|
481
462
|
const [, bumpOrder] = useState(0);
|
|
482
463
|
const orderReady = !loading && !showingSavedCopy && !previewSkeleton && predictionState !== "loading" && canonicalReferences.length > 0;
|
|
483
|
-
if (!orderSnapshot.current && orderReady) orderSnapshot.current = takeFeedOrderSnapshot(
|
|
484
|
-
const stable = orderSnapshot.current ? applyFeedOrderSnapshot(orderSnapshot.current,
|
|
485
|
-
const
|
|
486
|
-
const
|
|
464
|
+
if (!orderSnapshot.current && orderReady) orderSnapshot.current = takeFeedOrderSnapshot(liveItems);
|
|
465
|
+
const stable = orderSnapshot.current ? applyFeedOrderSnapshot(orderSnapshot.current, liveItems, feedSort, feedShow) : null;
|
|
466
|
+
const liveVisible = useMemo(() => visibleFeedItems(liveItems, feedSort, feedShow, visitRated), [liveItems, feedSort, feedShow, visitRated]);
|
|
467
|
+
const feedItems = stable?.items ?? liveVisible;
|
|
468
|
+
const feedLead = stable ? stable.leadId : feedLeadId(feedItems);
|
|
469
|
+
const ratedCount = useMemo(() => liveItems.filter((item) => item.rated).length, [liveItems]);
|
|
470
|
+
const showNewItems = () => { orderSnapshot.current = takeFeedOrderSnapshot(liveItems); bumpOrder((count) => count + 1); };
|
|
487
471
|
const [scrollTarget, setScrollTarget] = useState<{ id: string; nonce: number } | null>(null);
|
|
488
472
|
const feedOrderIds = useMemo(
|
|
489
|
-
() =>
|
|
490
|
-
[
|
|
473
|
+
() => feedItems.map((item) => item.lineageId),
|
|
474
|
+
[feedItems],
|
|
491
475
|
);
|
|
492
476
|
const statusCounts = useMemo(() => countReferencesByStatus(canonicalReferences), [canonicalReferences]);
|
|
493
477
|
|
|
@@ -498,7 +482,6 @@ export function NewsroomReferencesView({
|
|
|
498
482
|
);
|
|
499
483
|
const filters = decisions?.size ? [NEEDS_REVIEW_FILTER, ...STATUS_FILTERS] : STATUS_FILTERS;
|
|
500
484
|
const [selectedLineageId, setSelectedLineageId] = useState(routeLineageId);
|
|
501
|
-
const [mobileDetailOpen, setMobileDetailOpen] = useState(Boolean(routeLineageId) && isMobileDetail);
|
|
502
485
|
|
|
503
486
|
const filteredReferences = useMemo(
|
|
504
487
|
() => statusFilter === "needs_review"
|
|
@@ -551,18 +534,11 @@ export function NewsroomReferencesView({
|
|
|
551
534
|
|
|
552
535
|
useEffect(() => {
|
|
553
536
|
setSelectedLineageId(routeLineageId);
|
|
554
|
-
|
|
555
|
-
}, [isMobileDetail, routeLineageId]);
|
|
556
|
-
|
|
557
|
-
useEffect(() => {
|
|
558
|
-
if (isMobileDetail) return;
|
|
559
|
-
setMobileDetailOpen(false);
|
|
560
|
-
}, [isMobileDetail]);
|
|
537
|
+
}, [routeLineageId]);
|
|
561
538
|
|
|
562
539
|
const selectReference = (lineageId: string) => {
|
|
563
540
|
setCaughtUp(false);
|
|
564
541
|
setSelectedLineageId(lineageId);
|
|
565
|
-
if (isMobileDetail) setMobileDetailOpen(true);
|
|
566
542
|
if (typeof window !== "undefined") {
|
|
567
543
|
const nextPath = buildReferenceDetailPath(lineageId, demo);
|
|
568
544
|
if (`${window.location.pathname}${window.location.search}` !== nextPath) {
|
|
@@ -587,7 +563,6 @@ export function NewsroomReferencesView({
|
|
|
587
563
|
|
|
588
564
|
const closeDetail = () => {
|
|
589
565
|
setCaughtUp(false);
|
|
590
|
-
setMobileDetailOpen(false);
|
|
591
566
|
setSelectedLineageId("");
|
|
592
567
|
if (typeof window !== "undefined") {
|
|
593
568
|
const indexPath = getNewsroomNavHref("/newsroom/references", demo);
|
|
@@ -667,17 +642,20 @@ export function NewsroomReferencesView({
|
|
|
667
642
|
? "No references match this filter."
|
|
668
643
|
: "No private references imported yet.";
|
|
669
644
|
|
|
670
|
-
//
|
|
671
|
-
//
|
|
672
|
-
//
|
|
645
|
+
// Two layouts, chosen by the viewport, never by the selection. From the `lg` breakpoint (1024px, an iPad in landscape) the detail is a
|
|
646
|
+
// pane to the right of the feed, and the grid's column count follows the width it has beside that pane. Below it (phones and a tablet in
|
|
647
|
+
// portrait) there is one column: the feed, or, once a reference is open, its detail in the feed's place at the full width with a Back
|
|
648
|
+
// control. There is no overlay: an overlay would hide the cards Previous / Next scrolls to.
|
|
649
|
+
const detailOpen = Boolean(selectedReference) || caughtUp;
|
|
673
650
|
|
|
674
651
|
return (
|
|
675
652
|
<div
|
|
676
|
-
className="flex min-h-0 flex-1 flex-col gap-4
|
|
653
|
+
className="flex min-h-0 flex-1 flex-col gap-4 lg:max-h-[100dvh] lg:flex-row"
|
|
654
|
+
data-reference-layout={detailOpen ? "detail" : "feed"}
|
|
677
655
|
data-news-desk-section="references"
|
|
678
656
|
data-detail-open={selectedReference || routeLineageId ? "true" : "false"}
|
|
679
657
|
>
|
|
680
|
-
<div className="flex min-h-0 min-w-0 flex-1 flex-col gap-4
|
|
658
|
+
<div className={cn("flex min-h-0 min-w-0 flex-1 flex-col gap-4 lg:flex lg:overflow-hidden", detailOpen && LIST_HIDDEN_WHEN_DETAIL_OPEN)} data-reference-feed-column>
|
|
681
659
|
<div className="flex shrink-0 items-center justify-between gap-3" {...FEED_SURFACE_PROPS}>
|
|
682
660
|
<div className="min-w-0 space-y-0.5">
|
|
683
661
|
<h2 className="text-2xl font-bold tracking-tight">References</h2>
|
|
@@ -761,7 +739,7 @@ export function NewsroomReferencesView({
|
|
|
761
739
|
|
|
762
740
|
{view === "feed" ? (
|
|
763
741
|
<div className="relative flex min-h-0 flex-1 flex-col" {...FEED_SURFACE_PROPS}>
|
|
764
|
-
{stable && stable.newCount > 0
|
|
742
|
+
{stable && stable.newCount > 0 ? (
|
|
765
743
|
// Floats over the top of the grid, outside the scrolling content: announcing new items never moves a card.
|
|
766
744
|
<div className="pointer-events-none absolute inset-x-0 top-2 z-20 flex justify-center">
|
|
767
745
|
<button
|
|
@@ -784,19 +762,21 @@ export function NewsroomReferencesView({
|
|
|
784
762
|
now={mountedAt}
|
|
785
763
|
onOpen={selectReference}
|
|
786
764
|
onRate={rateFromFeed}
|
|
787
|
-
|
|
765
|
+
onShowChange={(next) => { setFeedShow(next); setCaughtUp(false); }}
|
|
766
|
+
onSortChange={(next) => { setFeedSort(next); setCaughtUp(false); }}
|
|
788
767
|
onVisitRated={markVisitRated}
|
|
789
|
-
|
|
768
|
+
items={feedItems}
|
|
769
|
+
ratedCount={ratedCount}
|
|
790
770
|
predictionLoading={predictionState === "loading"}
|
|
791
771
|
previews={previews}
|
|
792
772
|
captures={showCaptures ? captureStatuses : undefined}
|
|
793
|
-
|
|
794
|
-
leadId={stable?.leadId}
|
|
773
|
+
leadId={feedLead}
|
|
795
774
|
scrollTarget={scrollTarget}
|
|
796
775
|
ratings={ratings}
|
|
797
776
|
selectedLineageId={selectedLineageId}
|
|
798
777
|
stale={showingSavedCopy || (loading && references.length > 0)}
|
|
799
|
-
|
|
778
|
+
show={feedShow}
|
|
779
|
+
sort={feedSort}
|
|
800
780
|
visitRated={visitRated}
|
|
801
781
|
/>
|
|
802
782
|
</div>
|
|
@@ -886,9 +866,10 @@ export function NewsroomReferencesView({
|
|
|
886
866
|
|
|
887
867
|
<div
|
|
888
868
|
className={cn(
|
|
889
|
-
"hidden min-h-0 w-full shrink-0 flex-col overflow-hidden rounded-[20px] bg-[var(--feed-card)]
|
|
890
|
-
|
|
891
|
-
(
|
|
869
|
+
"hidden min-h-0 w-full shrink-0 flex-col overflow-hidden rounded-[20px] bg-[var(--feed-card)] lg:pb-16",
|
|
870
|
+
DETAIL_PANE_WIDTH,
|
|
871
|
+
// The detail appears once a reference is selected (or when the queue is done); until then the feed has the whole width.
|
|
872
|
+
detailOpen && "flex",
|
|
892
873
|
)}
|
|
893
874
|
data-newsroom-list-detail-shell
|
|
894
875
|
data-news-desk-section="references"
|
|
@@ -897,6 +878,10 @@ export function NewsroomReferencesView({
|
|
|
897
878
|
{...FEED_SURFACE_PROPS}
|
|
898
879
|
>
|
|
899
880
|
<div className="flex shrink-0 items-center justify-between gap-2 px-4 pt-3 pb-1">
|
|
881
|
+
<Button className="-ml-2 lg:hidden" data-reference-back onClick={closeDetail} size="sm" type="button" variant="ghost">
|
|
882
|
+
<ArrowLeftIcon aria-hidden="true" />
|
|
883
|
+
References
|
|
884
|
+
</Button>
|
|
900
885
|
<span className="text-sm font-medium text-foreground">Detail</span>
|
|
901
886
|
</div>
|
|
902
887
|
<div className="flex min-h-0 flex-1 flex-col" data-review-gestures onPointerDown={gestures.onPointerDown} onPointerUp={gestures.onPointerUp} style={gestures.style}>
|
|
@@ -931,50 +916,6 @@ export function NewsroomReferencesView({
|
|
|
931
916
|
{queueBar}
|
|
932
917
|
</div>
|
|
933
918
|
|
|
934
|
-
{isMobileDetail ? (
|
|
935
|
-
<Sheet
|
|
936
|
-
onOpenChange={(open) => {
|
|
937
|
-
if (!open) closeDetail();
|
|
938
|
-
else setMobileDetailOpen(true);
|
|
939
|
-
}}
|
|
940
|
-
open={(mobileDetailOpen && Boolean(selectedReference)) || caughtUp}
|
|
941
|
-
>
|
|
942
|
-
<SheetContent className="gap-0 rounded-t-[length:var(--feed-radius-xl)] bg-[var(--feed-card)] p-0" data-feed-flat="" data-feed-surface="" side="bottom" {...themeAttributes}>
|
|
943
|
-
<SheetHeader className="border-b border-border px-4 py-3">
|
|
944
|
-
<SheetTitle className="text-left text-base">Reference</SheetTitle>
|
|
945
|
-
</SheetHeader>
|
|
946
|
-
{selectedReference ? (
|
|
947
|
-
<div className="min-w-0" data-review-gestures onPointerDown={gestures.onPointerDown} onPointerUp={gestures.onPointerUp} style={gestures.style}><ScrollArea className="max-h-[70dvh]">
|
|
948
|
-
<ReferenceDetailPanel
|
|
949
|
-
key={referenceLineageId(selectedReference)}
|
|
950
|
-
captureTeaserLine={teaserLine}
|
|
951
|
-
correctCapture={onCorrectCapture}
|
|
952
|
-
ratings={ratings}
|
|
953
|
-
sectionChoices={sectionChoices}
|
|
954
|
-
predictionState={predictionState}
|
|
955
|
-
reviewReason={reviewReasons?.get(referenceLineageId(selectedReference))}
|
|
956
|
-
reviewSectionId={reviewSectionIds?.get(referenceLineageId(selectedReference))}
|
|
957
|
-
countsAsTrainingLabel={humanCuratedReferenceLineageIds?.has(referenceLineageId(selectedReference))}
|
|
958
|
-
delegated={delegatedCurations?.get(referenceLineageId(selectedReference))}
|
|
959
|
-
decision={decisions?.get(referenceLineageId(selectedReference))}
|
|
960
|
-
demo={demo}
|
|
961
|
-
disabled={disabled}
|
|
962
|
-
onClose={closeDetail}
|
|
963
|
-
onMessageCount={setMessageCount}
|
|
964
|
-
onRelevanceReview={onRelevanceReview ? (curation) => onRelevanceReview(selectedReference, curation) : undefined}
|
|
965
|
-
onReview={runReview}
|
|
966
|
-
sectionAgreement={sectionAgreement}
|
|
967
|
-
sectionSuggestion={sectionSuggestions?.get(referenceLineageId(selectedReference))}
|
|
968
|
-
sections={sections}
|
|
969
|
-
previewAttachments={previewAttachments}
|
|
970
|
-
reference={selectedReference}
|
|
971
|
-
/>
|
|
972
|
-
</ScrollArea></div>
|
|
973
|
-
) : caughtUp ? caughtUpPanel : null}
|
|
974
|
-
{queueBar}
|
|
975
|
-
</SheetContent>
|
|
976
|
-
</Sheet>
|
|
977
|
-
) : null}
|
|
978
919
|
</div>
|
|
979
920
|
);
|
|
980
921
|
}
|
|
@@ -167,7 +167,7 @@ export function DraftCard({ busy, demo: _demo, error, item, onDecide, previews,
|
|
|
167
167
|
<article aria-busy="true" className={CARD} data-draft-state="loading" data-review-draft={draftId} role="status" {...FEED_SURFACE_PROPS}>
|
|
168
168
|
<div className="aspect-[16/10] animate-pulse bg-[var(--feed-control)]" />
|
|
169
169
|
<div className="space-y-3 p-5">
|
|
170
|
-
<p className="text-xs text-muted-foreground">{link?.label ?? "
|
|
170
|
+
<p className="text-xs text-muted-foreground">{link?.label ?? "Proposed Story"}: loading the Proposed Story…</p>
|
|
171
171
|
<div className="h-5 w-11/12 animate-pulse rounded-full bg-[var(--feed-control)]" />
|
|
172
172
|
<div className="h-5 w-2/3 animate-pulse rounded-full bg-[var(--feed-control)]" />
|
|
173
173
|
</div>
|
|
@@ -216,13 +216,13 @@ export function DraftCard({ busy, demo: _demo, error, item, onDecide, previews,
|
|
|
216
216
|
</li>
|
|
217
217
|
);
|
|
218
218
|
})}
|
|
219
|
-
{view.moreSources ? <li className="self-center text-xs text-muted-foreground">and {view.moreSources} more in the full
|
|
219
|
+
{view.moreSources ? <li className="self-center text-xs text-muted-foreground">and {view.moreSources} more in the full Proposed Story</li> : null}
|
|
220
220
|
</ul>
|
|
221
221
|
) : null}
|
|
222
222
|
<p className="mt-auto flex flex-wrap items-center justify-between gap-x-3 gap-y-1 pt-1 text-xs text-muted-foreground">
|
|
223
223
|
<span data-draft-caption>{requestCaption(request)}</span>
|
|
224
224
|
<Link className="inline-flex items-center gap-1 font-medium text-primary underline-offset-4 hover:underline" data-draft-open href={view.href}>
|
|
225
|
-
Open full
|
|
225
|
+
Open full Proposed Story<ExternalLinkIcon aria-hidden="true" className="size-3.5 shrink-0" />
|
|
226
226
|
</Link>
|
|
227
227
|
</p>
|
|
228
228
|
</div>
|
|
@@ -232,7 +232,7 @@ export function DraftCard({ busy, demo: _demo, error, item, onDecide, previews,
|
|
|
232
232
|
disabledReason={hold ?? session}
|
|
233
233
|
error={error}
|
|
234
234
|
kind="story"
|
|
235
|
-
lockedNote={<>To change it, <Link className="text-primary underline underline-offset-4" href={view.href}>open the
|
|
235
|
+
lockedNote={<>To change it, <Link className="text-primary underline underline-offset-4" href={view.href}>open the Proposed Story in Proposals</Link>.</>}
|
|
236
236
|
onDecide={(choice, extras) => onDecide(draftId, choice, extras)}
|
|
237
237
|
placeholder="Why? Optional. Your note teaches the AI."
|
|
238
238
|
/>
|
|
@@ -9,7 +9,6 @@ import { acceptPath, batchAnswer, draftDecisionOf, draftReasonUpdate, draftView,
|
|
|
9
9
|
import { previewsBySourceAddress, recentDecisions } from "../lib/notification-feed";
|
|
10
10
|
import { answerReasonUpdate, answerUpdate, applyReviewUpdate, cyclotronAutoDecided, draftIdsOf, isDraftReview, sectionReviews, type AnswerInput, type DraftDecision, type ReviewRequest } from "../lib/review-requests";
|
|
11
11
|
import type { RelevanceDecisionView } from "../lib/relevance-decisions";
|
|
12
|
-
import { learnFreshness } from "../lib/reference-feed-ranking";
|
|
13
12
|
import { learnConfidenceThreshold, shapingThisWeek } from "../lib/needs-me-ranking";
|
|
14
13
|
import { buildNeedsMeItems } from "../lib/needs-me-items";
|
|
15
14
|
import { captureStatusesByLineage } from "../lib/reference-capture";
|
|
@@ -290,9 +289,9 @@ export function NotificationCenter({
|
|
|
290
289
|
resolving.current.add(request.id);
|
|
291
290
|
try {
|
|
292
291
|
const view = drafts[draftId];
|
|
293
|
-
if (!view) throw new Error("This
|
|
292
|
+
if (!view) throw new Error("This Proposed Story has not loaded yet.");
|
|
294
293
|
const path = choice === "accept" ? acceptPath(view.state) : rejectPath(view.state);
|
|
295
|
-
if (!path) throw new Error(`This
|
|
294
|
+
if (!path) throw new Error(`This Proposed Story is ${stateLabel(view.state, false).toLowerCase()} and cannot be ${choice === "accept" ? "accepted" : "rejected"} from here. Open it in Proposals.`);
|
|
296
295
|
const receipt = await api.transitionProposalPath(draftId, path);
|
|
297
296
|
if (receipt.status !== "ok") throw new Error(proposalReceiptMessage(receipt));
|
|
298
297
|
const decided: DraftView = { ...view, state: choice === "accept" ? "approved" : "declined", decided: choice };
|
|
@@ -307,7 +306,7 @@ export function NotificationCenter({
|
|
|
307
306
|
setRequests((current) => current.map((entry) => (entry.id === saved.id ? saved : entry)));
|
|
308
307
|
if (closing) await markRead(request.id);
|
|
309
308
|
} catch (failure) {
|
|
310
|
-
throw new Error(`The
|
|
309
|
+
throw new Error(`The Proposed Story was ${choice === "accept" ? "accepted" : "rejected"} in Proposals, but this request could not record it. ${failure instanceof Error ? failure.message : ""}`.trim());
|
|
311
310
|
}
|
|
312
311
|
} catch (failure) {
|
|
313
312
|
if (!demo) void loadDraftsFor([request]);
|
|
@@ -375,7 +374,7 @@ export function NotificationCenter({
|
|
|
375
374
|
const request = latest.current.requests.find((entry) => entry.id === payload.item.request.id) ?? payload.item.request;
|
|
376
375
|
const view = latest.current.drafts[payload.item.draftId];
|
|
377
376
|
const decision = draftDecisionOf(request.links.find((link) => link.id === payload.item.draftId), view, { by: actorLabel, at: now.toISOString() });
|
|
378
|
-
if (!decision) throw new Error("This
|
|
377
|
+
if (!decision) throw new Error("This Proposed Story has not been decided yet.");
|
|
379
378
|
store(await api.saveReviewReason(request, draftReasonUpdate(request, payload.item.draftId, { ...decision, title: decision.title ?? view?.headline, why }, actorLabel, now)));
|
|
380
379
|
} else if (payload.kind === "request") {
|
|
381
380
|
const request = latest.current.requests.find((entry) => entry.id === payload.request.id) ?? payload.request;
|
|
@@ -417,7 +416,6 @@ export function NotificationCenter({
|
|
|
417
416
|
}),
|
|
418
417
|
[shownReferences, decisions, previews, captures, visitRated, requests, drafts, missingDrafts, draftsState, stay, bySource, storyTitles, storyExcerpts, deletedStories, emails, emailsDecided],
|
|
419
418
|
);
|
|
420
|
-
const freshness = useMemo(() => learnFreshness(shownReferences, visitRated), [shownReferences, visitRated]);
|
|
421
419
|
const threshold = useMemo(() => learnConfidenceThreshold(shownReferences, decisions, visitRated), [shownReferences, decisions, visitRated]);
|
|
422
420
|
const recent = useMemo(() => recentDecisions(requests, references, demo), [requests, references, demo]);
|
|
423
421
|
const shaping = useMemo(
|
|
@@ -435,7 +433,7 @@ export function NotificationCenter({
|
|
|
435
433
|
|
|
436
434
|
return (
|
|
437
435
|
<NeedsMeView
|
|
438
|
-
below={below} captureTeaserLine={teaserLine} demo={demo} draftsFailed={draftsState === "failed"} entries={entries} error={error}
|
|
436
|
+
below={below} captureTeaserLine={teaserLine} demo={demo} draftsFailed={draftsState === "failed"} entries={entries} error={error} legacyAuto={legacyAuto}
|
|
439
437
|
loadEmail={api.loadEmail} loadState={preview === "loading" ? "loading" : loadState} missingDraftCount={missingCount} now={now} onCorrectCapture={onCorrectCapture} onDecide={decide} onEditReason={editReason}
|
|
440
438
|
onRetryDrafts={() => void loadDraftsFor(requests)} predictionLoading={predictionLoading} recent={recent} referenceAttachments={referenceAttachments}
|
|
441
439
|
referenceDisabledReason={referenceDisabledReason} referencesLoading={referencesLoading} settled={settled} shaping={shaping} stale={stale} threshold={threshold}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
|
|
15
15
|
const FILTERS: Array<{ key: ProposalFilter; label: string }> = [
|
|
16
16
|
{ key: "open", label: "Open" },
|
|
17
|
-
{ key: "draft", label: "
|
|
17
|
+
{ key: "draft", label: "Proposed" },
|
|
18
18
|
{ key: "in_steering", label: "In steering" },
|
|
19
19
|
{ key: "editor_review", label: "Editor review" },
|
|
20
20
|
{ key: "decided", label: "Decided" },
|
|
@@ -42,8 +42,8 @@ export function ProposalsView(props: ProposalsViewProps) {
|
|
|
42
42
|
const shown = filterProposals(sorted, props.filter);
|
|
43
43
|
return (
|
|
44
44
|
<section aria-labelledby="newsroom-proposals-title" data-news-desk-admin-panel="proposals">
|
|
45
|
-
<h3 id="newsroom-proposals-title">
|
|
46
|
-
<p>
|
|
45
|
+
<h3 id="newsroom-proposals-title">Proposed Stories</h3>
|
|
46
|
+
<p>Proposed Stories and other changes that AI proposes. Editors pin, hold and decide them. Only editors and admins can see this.</p>
|
|
47
47
|
<p role="tablist" aria-label="Proposal views">
|
|
48
48
|
{FILTERS.filter((entry) => props.readers || entry.key !== "in_steering").map((entry) => (
|
|
49
49
|
<span key={entry.key}>
|
|
@@ -73,7 +73,7 @@ export function ProposalsView(props: ProposalsViewProps) {
|
|
|
73
73
|
{row.summary ? <p>{row.summary}</p> : null}
|
|
74
74
|
{row.body ? (
|
|
75
75
|
<details open={props.focusId === row.id || undefined}>
|
|
76
|
-
<summary>Read the
|
|
76
|
+
<summary>Read the Proposed Story</summary>
|
|
77
77
|
<pre style={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>{row.body}</pre>
|
|
78
78
|
</details>
|
|
79
79
|
) : null}
|
|
@@ -22,6 +22,7 @@ export function ReferenceConversation({ disabledReason = null, lineageId, onCoun
|
|
|
22
22
|
const timer = window.setInterval(() => setNow(Date.now()), 60000);
|
|
23
23
|
return () => window.clearInterval(timer);
|
|
24
24
|
}, []);
|
|
25
|
+
// A deleted Message stays in the conversation, so it still counts.
|
|
25
26
|
const settled = conversation.messages.filter((message) => !message.pending).length;
|
|
26
27
|
useEffect(() => {
|
|
27
28
|
if (conversation.state === "ready") onCount?.(lineageId, settled);
|
|
@@ -30,13 +31,17 @@ export function ReferenceConversation({ disabledReason = null, lineageId, onCoun
|
|
|
30
31
|
return (
|
|
31
32
|
<div data-reference-conversation={lineageId}>
|
|
32
33
|
<ConversationThread
|
|
34
|
+
authorNames={conversation.names}
|
|
33
35
|
disabledReason={disabledReason}
|
|
34
36
|
error={conversation.error}
|
|
35
37
|
messages={conversation.messages}
|
|
36
38
|
now={now}
|
|
39
|
+
onDelete={conversation.remove}
|
|
40
|
+
onEdit={conversation.edit}
|
|
37
41
|
onPost={conversation.post}
|
|
38
42
|
onRetry={conversation.retry}
|
|
39
43
|
state={conversation.state}
|
|
44
|
+
viewer={conversation.viewer}
|
|
40
45
|
/>
|
|
41
46
|
</div>
|
|
42
47
|
);
|