@anthusai/papyrus 5.44.0 → 5.46.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.
Files changed (48) hide show
  1. package/amplify/data/resource.js +6 -0
  2. package/amplify/data/resource.ts +7 -0
  3. package/amplify/data/schema.js +6 -0
  4. package/amplify/data/schema.ts +7 -0
  5. package/amplify/functions/content-actions/handler.py +8 -0
  6. package/amplify/functions/model-attachment-upload/handler.ts +1 -0
  7. package/amplify/functions/monitoring-view/schema.js +8 -0
  8. package/amplify/functions/monitoring-view/schema.ts +8 -0
  9. package/bin/papyrus-app.mjs +12 -1
  10. package/components/monitoring-overview-view.tsx +52 -6
  11. package/components/monitoring-panel.tsx +61 -3
  12. package/components/news-desk-client-provider.tsx +16 -1
  13. package/components/news-desk-page.tsx +2 -1
  14. package/components/newsroom-access-gate.tsx +7 -5
  15. package/components/newsroom-client-shell.tsx +2 -1
  16. package/components/newsroom-ops-overview.tsx +63 -60
  17. package/components/newsroom-ops-shell.tsx +4 -2
  18. package/components/newsroom-organizer.css +49 -2
  19. package/components/newsroom-references-view.tsx +296 -23
  20. package/components/notification-cards.tsx +364 -0
  21. package/components/notification-center.tsx +319 -331
  22. package/components/reference-capture-client.ts +110 -0
  23. package/components/reference-capture-panel.tsx +180 -0
  24. package/components/reference-feed.tsx +502 -0
  25. package/components/reference-review-card.tsx +32 -80
  26. package/components/references-page-loading.tsx +61 -0
  27. package/components/references-prefetch.ts +52 -0
  28. package/components/review-rating-grid.tsx +177 -0
  29. package/components/topic-steering-workspace.tsx +43 -9
  30. package/components/use-reference-ratings.ts +132 -0
  31. package/lib/category-repository.ts +6 -1
  32. package/lib/content-actions-client.ts +18 -0
  33. package/lib/local-preview-feed-fixture.ts +63 -0
  34. package/lib/local-preview-synthetic-rejects.ts +41 -0
  35. package/lib/monitoring-client.ts +16 -2
  36. package/lib/monitoring-overview.ts +20 -1
  37. package/lib/monitoring-paging.ts +58 -0
  38. package/lib/newsroom-nav.ts +1 -1
  39. package/lib/newsroom-organizer-nav.ts +1 -1
  40. package/lib/notification-feed.ts +250 -0
  41. package/lib/reference-capture.ts +293 -0
  42. package/lib/reference-feed-cache.ts +170 -0
  43. package/lib/reference-feed-ranking.ts +235 -0
  44. package/lib/reference-preview.ts +112 -0
  45. package/lib/review-drafts.ts +8 -3
  46. package/lib/site-brand.ts +2 -0
  47. package/package.json +17 -1
  48. package/routes.manifest.json +1 -1
@@ -1,19 +1,13 @@
1
1
  "use client";
2
2
 
3
3
  import Link from "next/link";
4
- import { usePathname } from "next/navigation";
5
- import { useMemo } from "react";
4
+ import { usePathname, useRouter } from "next/navigation";
5
+ import { useCallback, useMemo } from "react";
6
6
  import { Badge } from "./ui/badge";
7
7
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
8
- import type { AssignmentRecord, NewsroomSectionRecord, ReferenceRecord } from "../lib/category-repository";
8
+ import type { AssignmentRecord, NewsroomSectionRecord, ReferenceAttachmentRecord, ReferenceRecord } from "../lib/category-repository";
9
9
  import { getNewsroomNavHref } from "../lib/newsroom-nav";
10
- import {
11
- filterReferencesByStatus,
12
- formatReferenceListDate,
13
- referenceDisplayTitle,
14
- referenceLineageId,
15
- selectCanonicalReferenceRecords,
16
- } from "../lib/newsroom-references";
10
+ import { referenceLineageId, selectCanonicalReferenceRecords, type ReferenceCurationAction } from "../lib/newsroom-references";
17
11
  import {
18
12
  buildNewsroomSectionHref,
19
13
  displayNewsroomSectionShortTitle,
@@ -23,8 +17,11 @@ import {
23
17
  } from "../lib/newsroom-sections";
24
18
  import { newsroomInboxRowClassName } from "../lib/newsroom-list-selection";
25
19
  import type { RelevanceDecisionView } from "../lib/relevance-decisions";
20
+ import type { ReferenceRejectionReasonCode } from "../lib/reference-policy";
21
+ import { referencePreviewsByLineage, type ReferencePreview } from "../lib/reference-preview";
22
+ import type { NotificationFixture } from "./notification-center";
26
23
  import { NotificationCenter } from "./notification-center";
27
- import { CyclotronRow, predictionFromDecision } from "./reference-review-card";
24
+ import type { ReviewChoice } from "./reference-review-card";
28
25
 
29
26
  type NewsroomOpsOverviewProps = {
30
27
  assignments: AssignmentRecord[];
@@ -34,26 +31,58 @@ type NewsroomOpsOverviewProps = {
34
31
  humanCuratedReferenceLineageIds?: ReadonlySet<string>;
35
32
  newsroomSections: NewsroomSectionRecord[];
36
33
  references: ReferenceRecord[];
34
+ referenceAttachments?: ReferenceAttachmentRecord[];
35
+ /** True until the References have been read; a saved copy of the last view fills the page meanwhile. */
36
+ loading?: boolean;
37
+ savedCopy?: { references: ReferenceRecord[]; previews: Array<[string, ReferencePreview]>; decisions: Array<[string, RelevanceDecisionView]> } | null;
38
+ predictionState?: "loading" | "ready" | "error";
39
+ /** Words for why a rating cannot be saved right now, or null. */
40
+ disabledReason?: string | null;
41
+ onReview?: (reference: ReferenceRecord, action: ReferenceCurationAction, options?: { reasonCode?: ReferenceRejectionReasonCode | null; decisionRelationId?: string; shareable?: boolean }) => Promise<void> | void;
42
+ /** Local real-data preview of the Notification Center (scripts/export-notification-center-fixture.py). */
43
+ notificationFixture?: NotificationFixture | null;
37
44
  };
38
45
 
39
46
  const NO_DECISIONS: ReadonlyMap<string, RelevanceDecisionView> = new Map();
40
- const NO_IDS: ReadonlySet<string> = new Set();
41
47
 
42
48
  export function NewsroomOpsOverview({
43
49
  actorLabel = "",
44
50
  assignments,
45
51
  decisions = NO_DECISIONS,
46
52
  demo = false,
47
- humanCuratedReferenceLineageIds = NO_IDS,
53
+ humanCuratedReferenceLineageIds,
54
+ disabledReason = null,
55
+ loading = false,
48
56
  newsroomSections,
57
+ notificationFixture = null,
58
+ onReview,
59
+ predictionState = "ready",
60
+ referenceAttachments = [],
49
61
  references,
62
+ savedCopy = null,
50
63
  }: NewsroomOpsOverviewProps) {
51
64
  const pathname = usePathname();
52
- const canonicalReferences = useMemo(() => selectCanonicalReferenceRecords(references), [references]);
53
- const pendingReferences = useMemo(
54
- () => filterReferencesByStatus(canonicalReferences, "pending").slice(0, 5),
55
- [canonicalReferences],
65
+ const router = useRouter();
66
+ const liveReferences = useMemo(() => selectCanonicalReferenceRecords(references), [references]);
67
+ // Until the live read arrives, a saved copy of the last view fills the page. It is display only.
68
+ const showingSavedCopy = loading && !liveReferences.length && Boolean(savedCopy?.references.length);
69
+ const canonicalReferences = showingSavedCopy ? savedCopy!.references : liveReferences;
70
+ const shownDecisions = useMemo(() => (showingSavedCopy && !decisions.size ? new Map(savedCopy!.decisions) : decisions), [decisions, savedCopy, showingSavedCopy]);
71
+ const previews = useMemo(() => {
72
+ const live = referencePreviewsByLineage(referenceAttachments);
73
+ return showingSavedCopy && !live.size ? new Map(savedCopy!.previews) : live;
74
+ }, [referenceAttachments, savedCopy, showingSavedCopy]);
75
+ const openReference = useCallback(
76
+ (lineageId: string) => router.push(getNewsroomNavHref(`/newsroom/references/${encodeURIComponent(lineageId)}`, demo)),
77
+ [demo, router],
56
78
  );
79
+ const rateReference = useCallback(async (reference: ReferenceRecord, choice: ReviewChoice, reasonCode: ReferenceRejectionReasonCode | null) => {
80
+ const decision = shownDecisions.get(referenceLineageId(reference));
81
+ await onReview?.(reference, choice, {
82
+ reasonCode: choice === "reject" ? reasonCode : null,
83
+ ...(decision ? { decisionRelationId: decision.decisionRelationId, shareable: false } : {}),
84
+ });
85
+ }, [onReview, shownDecisions]);
57
86
  const attentionAssignments = useMemo(
58
87
  () => assignments
59
88
  .filter((assignment) => {
@@ -69,55 +98,35 @@ export function NewsroomOpsOverview({
69
98
  );
70
99
  const coreDesks = useMemo(() => desks.filter(desk => newsroomDeskKind(desk) === "core"), [desks]);
71
100
  const rotatingDesks = useMemo(() => desks.filter(desk => newsroomDeskKind(desk) === "rotating"), [desks]);
101
+ const rateDisabled = disabledReason ?? (showingSavedCopy ? "Confirming your sign-in. You can answer in a moment." : null);
72
102
 
73
103
  return (
74
104
  <div
75
- className="space-y-8 font-sans"
105
+ className="space-y-12 font-sans"
76
106
  data-news-desk-section="overview"
77
107
  data-newsroom-ops-home="true"
78
108
  >
79
- <div className="space-y-8" data-newsroom-overview-feeds>
80
- <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}>
109
+ <div className="space-y-12" data-newsroom-overview-feeds>
110
+ <div data-newsroom-overview-notifications>
81
111
  <NotificationCenter
82
112
  actorLabel={actorLabel}
83
- decisions={decisions}
113
+ decisions={shownDecisions}
84
114
  demo={demo}
115
+ fixture={notificationFixture}
85
116
  humanCuratedReferenceLineageIds={humanCuratedReferenceLineageIds}
117
+ onOpenReference={openReference}
118
+ onRateReference={rateReference}
119
+ predictionLoading={predictionState === "loading"}
120
+ previews={previews}
121
+ referenceDisabledReason={rateDisabled}
86
122
  references={canonicalReferences}
123
+ referencesLoading={loading && !canonicalReferences.length}
87
124
  />
88
125
  </div>
89
- <section aria-label="Needs attention" className="space-y-3">
90
- <h2 className="text-sm font-medium text-muted-foreground">Needs attention</h2>
91
- {pendingReferences.length || attentionAssignments.length ? (
126
+ {attentionAssignments.length ? (
127
+ <section aria-label="Open assignments" className="space-y-3">
128
+ <h2 className="text-sm font-medium text-muted-foreground">Open assignments</h2>
92
129
  <div className="space-y-2">
93
- {pendingReferences.map((reference) => {
94
- const lineageId = referenceLineageId(reference);
95
- const href = getNewsroomNavHref(`/newsroom/references/${encodeURIComponent(lineageId)}`, demo);
96
- const active = pathname === href || pathname.startsWith(`${href}/`);
97
- return (
98
- <Link
99
- aria-current={active ? "true" : undefined}
100
- className={newsroomInboxRowClassName(active)}
101
- data-newsroom-inbox-item="reference"
102
- data-newsroom-list-row
103
- data-selected={active || undefined}
104
- data-newsroom-card
105
- data-newsroom-card-id={lineageId}
106
- href={href}
107
- key={lineageId}
108
- >
109
- <div className="min-w-0 flex-1 space-y-1">
110
- <p className="truncate text-sm font-medium text-foreground">{referenceDisplayTitle(reference)}</p>
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>
116
- </div>
117
- <Badge className="mt-0.5 shrink-0" variant="outline">Pending</Badge>
118
- </Link>
119
- );
120
- })}
121
130
  {attentionAssignments.map((assignment) => {
122
131
  const href = getNewsroomNavHref(`/newsroom/assignments/${encodeURIComponent(assignment.id)}`, demo);
123
132
  const active = pathname === href || pathname.startsWith(`${href}/`);
@@ -140,14 +149,8 @@ export function NewsroomOpsOverview({
140
149
  );
141
150
  })}
142
151
  </div>
143
- ) : (
144
- <Card>
145
- <CardContent className="p-4 text-sm text-muted-foreground">
146
- No pending references or open assignments. Check those destinations for other work.
147
- </CardContent>
148
- </Card>
149
- )}
150
- </section>
152
+ </section>
153
+ ) : null}
151
154
 
152
155
  {desks.length ? (
153
156
  <section aria-label="Sections" className="space-y-3" data-newsroom-ops-desks>
@@ -143,6 +143,8 @@ export function NewsroomOpsShell({ activeTab, appTitle, backHref = "/", backLabe
143
143
  media.addEventListener("change", change);
144
144
  return () => { document.removeEventListener("focusin", remember); media.removeEventListener("change", change); cancelAnimationFrame(frame); };
145
145
  }, []);
146
+ // The mobile top bar prefers the brand's compact mark, then scales the full logo; the serif title is the no-logo fallback.
147
+ const MastheadLogo = SITE_BRAND.components?.NewsroomLogoCompact ?? SITE_BRAND.components?.NewsroomLogo;
146
148
  const activePath = browserPathToInternalNewsroomPath(pathname ?? "/newsroom");
147
149
  const sidebarToggleLabel = collapsed ? "Expand sidebar" : "Collapse sidebar";
148
150
  const sidebarToggle = <button type="button" className="organizer-toggle" title={sidebarToggleLabel}
@@ -162,10 +164,10 @@ export function NewsroomOpsShell({ activeTab, appTitle, backHref = "/", backLabe
162
164
  <div className="organizer-workspace">
163
165
  <header className="organizer-topbar" data-newsroom-ops-topbar>
164
166
  {showNavigation && collapsed && sidebarToggle}
165
- <Link className="organizer-masthead" href={backHref} title={backLabel}>{appTitle}</Link>
167
+ <Link className="organizer-masthead" href={backHref} title={backLabel}>{MastheadLogo ? <MastheadLogo className="organizer-masthead-logo" /> : appTitle}</Link>
166
168
  <h1 data-newsroom-ops-title>{pageTitle}</h1><OrganizerSearch demo={demo} /><div className="organizer-header-actions">{headerActions}</div><Account placement="header" />
167
169
  </header>
168
- <main className="organizer-main">{demo && <p className="organizer-demo-notice">Demo workspace · synthetic examples</p>}{children}</main>
170
+ <main className="organizer-main">{demo && !session?.shell.dashboard?.realDataPreview && <p className="organizer-demo-notice">Demo workspace · synthetic examples</p>}{children}</main>
169
171
  </div>
170
172
  {showNavigation && <NewsroomMobileNavigation activeTab={activeTab} items={ORGANIZER_PRIMARY_NAV.map(item => ({ ...item, href: getNewsroomNavHref(item.path, demo) }))}
171
173
  chat={consoleState?.shouldOfferConsole ? { open: consoleState.open, onOpen: consoleState.openConsole } : undefined}
@@ -18,13 +18,12 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
18
18
  .organizer-workspace{display:flex;flex-direction:column;min-height:0;min-width:0;overflow:hidden;padding:0 16px}
19
19
  .organizer-topbar{height:48px;min-height:48px;display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--org-line)}
20
20
  .organizer-masthead{font:900 28px var(--font-wordmark),Georgia,serif;white-space:nowrap;max-width:30vw;overflow:hidden;text-overflow:ellipsis}
21
+ .organizer-masthead-logo{display:block;height:24px;width:auto;max-width:36vw;overflow:visible}
21
22
  .organizer-topbar h1{font:400 12px var(--kb-theme-ui-font);flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0}
22
23
  .organizer-toggle{display:flex;align-items:center;justify-content:center;width:36px;min-height:36px;padding:0;border:1px solid var(--org-line);flex-shrink:0}
23
24
  .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
25
  .organizer-header-actions{display:flex;align-items:center;gap:8px;min-width:0;max-width:45%;overflow-x:auto}
25
26
  .organizer-main{min-width:0;min-height:0;overflow:auto;overscroll-behavior:contain;flex:1;padding:16px 0}
26
- .newsroom-overview-feed-frame{height:auto}
27
- @media(min-width:640px){.newsroom-overview-feed-frame{height:38rem;overflow-y:auto}}
28
27
  .organizer-main>[data-category-steering]{min-height:0}.organizer-demo-notice{font-size:11px;color:var(--org-muted);margin-bottom:12px}
29
28
  .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
29
  .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}
@@ -347,3 +346,51 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
347
346
  .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
347
  .news-desk-columns--administration .news-desk-newsletter__filters{display:flex;flex-wrap:wrap;gap:12px 24px}
349
348
  .news-desk-columns--administration .news-desk-newsletter__filters .news-desk-doctrine-card__field{min-width:180px}
349
+
350
+ /* The References feed is rounded and borderless (Ryan's design rule): flat tones and rounded rectangles instead of lines and boxes.
351
+ The newsroom chrome squares every "rounded" class above; inside a [data-feed-surface] the rounded classes mean what they say.
352
+ The repeated attribute raises specificity over the squaring rules without !important escalation. */
353
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-full"],
354
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-full"] { border-radius: 9999px !important; }
355
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-[20px]"],
356
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-[20px]"] { border-radius: 20px !important; }
357
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-2xl"],
358
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-2xl"] { border-radius: 16px !important; }
359
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-[16px]"],
360
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-[16px]"] { border-radius: 16px !important; }
361
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface] [class*="rounded-3xl"],
362
+ .newsroom-organizer[data-kanbus-theme] [data-feed-surface][data-feed-surface][data-feed-surface][class*="rounded-3xl"] { border-radius: 24px !important; }
363
+
364
+ /* Feed tones. Light: cards a touch lighter than the page, footers between, controls a touch darker. Dark: each step lifts toward the ink.
365
+ Also set on the portaled mobile sheet, which carries the theme attributes itself. */
366
+ [data-kanbus-theme] [data-feed-surface], [data-kanbus-theme][data-feed-surface] {
367
+ --feed-card: color-mix(in oklab, var(--kb-theme-paper) 35%, white);
368
+ --feed-footer: color-mix(in oklab, var(--kb-theme-paper) 70%, white);
369
+ --feed-control: color-mix(in oklab, var(--kb-theme-paper) 90%, var(--kb-theme-ink));
370
+ }
371
+ [data-kanbus-theme][data-color-mode="dark"] [data-feed-surface], [data-kanbus-theme][data-color-mode="dark"][data-feed-surface] {
372
+ --feed-card: color-mix(in oklab, var(--kb-theme-paper) 94%, var(--kb-theme-ink));
373
+ --feed-footer: color-mix(in oklab, var(--kb-theme-paper) 90%, var(--kb-theme-ink));
374
+ --feed-control: color-mix(in oklab, var(--kb-theme-paper) 80%, var(--kb-theme-ink));
375
+ }
376
+
377
+ /* Borderless detail pane, Add panel and mobile sheet ([data-feed-flat]): no border, rule or box anywhere inside. Regions are told apart
378
+ by tone (card, footer, control) and shapes are rounded rectangles. The repeated attribute outranks the newsroom chrome's own rules. */
379
+ [data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat],
380
+ [data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] * { border-width: 0 !important; }
381
+ [data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat][data-feed-flat] *:not(:focus-visible) { box-shadow: none; }
382
+ [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; }
383
+ [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"]) {
384
+ background: var(--feed-control) !important; border-radius: 9999px !important; color: var(--foreground, inherit);
385
+ }
386
+ [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"]) {
387
+ background: var(--primary) !important; color: var(--primary-foreground) !important; border-radius: 9999px !important;
388
+ }
389
+ [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; }
390
+ [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])) {
391
+ background: var(--feed-control) !important; border-radius: 16px !important;
392
+ }
393
+ [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; }
394
+ [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; }
395
+ /* The rating grid keeps its own rounded rectangles in the borderless panes (not the pill shape the flat rules give other buttons). */
396
+ [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; }