@anthusai/papyrus 1.0.0-next.9 → 1.0.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 (208) hide show
  1. package/amplify/auth/redirect-urls.js +54 -0
  2. package/amplify/auth/redirect-urls.ts +66 -0
  3. package/amplify/auth/resource.js +20 -5
  4. package/amplify/auth/resource.ts +27 -5
  5. package/amplify/data/resource.js +42 -80
  6. package/amplify/data/resource.ts +42 -80
  7. package/amplify/data/schema.js +45 -60
  8. package/amplify/data/schema.ts +47 -60
  9. package/amplify/functions/category-action/handler.ts +5 -3
  10. package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +0 -10
  11. package/amplify/functions/console-chat-responder/resource.js +0 -7
  12. package/amplify/functions/console-chat-responder/resource.ts +0 -8
  13. package/amplify/functions/console-chat-responder/src/main.rs +0 -9
  14. package/amplify/functions/content-actions/handler.py +143 -1
  15. package/amplify/functions/content-actions/requirements.txt +2 -1
  16. package/amplify/functions/content-actions/resource.js +6 -1
  17. package/amplify/functions/content-actions/resource.ts +9 -2
  18. package/amplify/functions/renditions/handler.py +120 -0
  19. package/amplify/functions/renditions/requirements.txt +4 -0
  20. package/amplify/functions/shared/python-bundle.js +42 -2
  21. package/amplify/functions/shared/python-bundle.ts +47 -2
  22. package/amplify/functions/shared/reference-curation-metadata.ts +29 -0
  23. package/amplify/inbound-email/ses-intake.js +86 -0
  24. package/amplify/inbound-email/ses-intake.ts +110 -0
  25. package/amplify/plugins/registry.js +19 -0
  26. package/amplify/plugins/registry.ts +23 -0
  27. package/amplify/plugins/signup/function/handler.py +200 -0
  28. package/amplify/plugins/signup/function/resource.js +27 -0
  29. package/amplify/plugins/signup/function/resource.ts +35 -0
  30. package/amplify/plugins/signup/papyrus.plugin.js +59 -0
  31. package/amplify/plugins/signup/papyrus.plugin.ts +74 -0
  32. package/amplify/plugins/signup/schema.js +40 -0
  33. package/amplify/plugins/signup/schema.ts +41 -0
  34. package/amplify/plugins/signup/waf.js +48 -0
  35. package/amplify/plugins/signup/waf.ts +51 -0
  36. package/amplify/plugins/types.js +1 -0
  37. package/amplify/plugins/types.ts +39 -0
  38. package/amplify/plugins/wire.js +24 -0
  39. package/amplify/plugins/wire.ts +26 -0
  40. package/amplify/publication/default-publication.js +18 -0
  41. package/amplify/publication/default-publication.ts +20 -0
  42. package/amplify/publication/invalidation-alarm.js +26 -0
  43. package/amplify/publication/invalidation-alarm.ts +30 -0
  44. package/amplify/publication/redirect-and-index.js +37 -0
  45. package/amplify/publication/renditions-function.js +74 -0
  46. package/amplify/publication/renditions-function.ts +112 -0
  47. package/amplify/publication/renditions-settings.js +51 -0
  48. package/amplify/publication/renditions-settings.ts +58 -0
  49. package/amplify/publication/reproject-on-deploy.js +73 -0
  50. package/amplify/publication/reproject-on-deploy.ts +78 -0
  51. package/amplify/publication/s3-projection-settings.js +67 -0
  52. package/amplify/publication/s3-projection-settings.ts +101 -0
  53. package/amplify/publication/s3-projection.js +151 -0
  54. package/amplify/publication/s3-projection.ts +201 -0
  55. package/amplify/resolver-waves.js +26 -0
  56. package/amplify/resolver-waves.ts +34 -0
  57. package/amplify/revalidate-invalidation-settings.js +30 -0
  58. package/amplify/revalidate-invalidation-settings.ts +35 -0
  59. package/amplify/revalidate-secret-policy.js +13 -0
  60. package/amplify/revalidate-secret-policy.ts +19 -0
  61. package/amplify/site-backend-config.js +65 -1
  62. package/amplify/site-backend-config.ts +153 -2
  63. package/amplify/site-backend-names.js +103 -0
  64. package/amplify/site-backend-names.ts +166 -0
  65. package/amplify/site-backend.js +143 -176
  66. package/amplify/site-backend.ts +181 -201
  67. package/amplify/storage-backups/construct.js +31 -0
  68. package/amplify/storage-backups/construct.ts +47 -0
  69. package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +8 -1
  70. package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -0
  71. package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +6 -1
  72. package/app/[year]/[month]/[day]/page.tsx +1 -1
  73. package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +6 -1
  74. package/app/api/archive/editions/route.ts +1 -1
  75. package/app/api/cyclotron/priming-proof/route.ts +11 -0
  76. package/app/api/media/[...path]/route.ts +5 -16
  77. package/app/api/revalidate/route.ts +15 -7
  78. package/app/archive/page.tsx +1 -1
  79. package/app/articles/[slug]/page.tsx +7 -5
  80. package/app/blog-rhythm.css +278 -0
  81. package/app/cyclotron/page.tsx +13 -0
  82. package/app/globals.css +2 -0
  83. package/app/layout.tsx +16 -0
  84. package/app/newsroom/[section]/[...selection]/page.tsx +9 -6
  85. package/app/newsroom/[section]/page.tsx +6 -3
  86. package/app/newsroom/layout.tsx +4 -9
  87. package/app/newsroom/settings/page.tsx +6 -0
  88. package/app/newsroom/work/page.tsx +10 -0
  89. package/app/page.tsx +22 -8
  90. package/app/robots.ts +2 -5
  91. package/app/tailwind.css +1 -0
  92. package/components/amplify-client-provider.tsx +5 -22
  93. package/components/archive-shell.tsx +2 -22
  94. package/components/article-video.tsx +152 -5
  95. package/components/blog-page-background.tsx +23 -6
  96. package/components/news-desk-page.tsx +4 -15
  97. package/components/news-desk-taxonomy-client.ts +90 -3
  98. package/components/newspaper.tsx +6 -2
  99. package/components/newsroom-access-gate.tsx +2 -1
  100. package/components/newsroom-board-view.tsx +99 -0
  101. package/components/newsroom-client-shell.tsx +12 -0
  102. package/components/newsroom-curation-provider.tsx +17 -0
  103. package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
  104. package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
  105. package/components/newsroom-fonts/playfair-display-OFL.txt +93 -0
  106. package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
  107. package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
  108. package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +93 -0
  109. package/components/newsroom-newsprint-board.css +48 -0
  110. package/components/newsroom-ops-overview.tsx +28 -11
  111. package/components/newsroom-ops-shell.tsx +7 -1
  112. package/components/newsroom-organizer-shell.tsx +130 -0
  113. package/components/newsroom-organizer.css +63 -0
  114. package/components/newsroom-references-view.tsx +92 -8
  115. package/components/papyrus-console-shell.tsx +4 -2
  116. package/components/pictogram-figure.tsx +9 -2
  117. package/components/presentation-header.tsx +135 -0
  118. package/components/presentation-rhythm-hrule.tsx +17 -0
  119. package/components/priming-proof-page.tsx +92 -0
  120. package/components/publication-cyclotron-panel.tsx +57 -0
  121. package/components/reader-auth-control.tsx +8 -5
  122. package/components/reference-curation-controls.tsx +77 -0
  123. package/components/reference-cyclotron-status.tsx +109 -0
  124. package/components/reference-priming.tsx +18 -0
  125. package/components/reference-relevance-review.tsx +75 -0
  126. package/components/settings-page.tsx +2 -1
  127. package/components/signup-entries-panel.tsx +94 -0
  128. package/components/topic-steering-workspace.tsx +78 -6
  129. package/components/use-blog-header-obstacles.ts +81 -0
  130. package/components/use-brand-fonts-ready.ts +28 -0
  131. package/components/use-near-viewport.ts +30 -0
  132. package/components/use-rhythm-overlay.ts +29 -0
  133. package/infra/amplify-app-shell.js +148 -27
  134. package/infra/amplify-app-shell.ts +160 -31
  135. package/infra/build-specs.js +23 -7
  136. package/infra/build-specs.ts +27 -10
  137. package/infra/site-config.js +248 -36
  138. package/infra/site-config.ts +313 -51
  139. package/lib/amplify-outputs-path.ts +5 -1
  140. package/lib/amplify-server-runtime.ts +36 -1
  141. package/lib/approved-priming-proof-reports.ts +6 -0
  142. package/lib/blog-feature-solver.ts +459 -0
  143. package/lib/blog-rhythm.ts +134 -0
  144. package/lib/brand-fonts-watch.ts +43 -0
  145. package/lib/brand-fonts.ts +112 -0
  146. package/lib/cached-content-repository.ts +12 -0
  147. package/lib/category-repository.ts +43 -93
  148. package/lib/content-item-lifecycle.ts +55 -0
  149. package/lib/content-repository.ts +3 -0
  150. package/lib/content-types.ts +4 -0
  151. package/lib/curation-client.ts +61 -0
  152. package/lib/edition-routes.ts +31 -29
  153. package/lib/graphql-content-repository.ts +56 -22
  154. package/lib/media-route-response.ts +45 -0
  155. package/lib/newspaper-layout.ts +3 -2
  156. package/lib/newsroom-base-path.ts +12 -3
  157. package/lib/newsroom-board.ts +99 -0
  158. package/lib/newsroom-category-drilldown.ts +3 -2
  159. package/lib/newsroom-forum-routes.ts +6 -4
  160. package/lib/newsroom-index-filters.ts +14 -10
  161. package/lib/newsroom-nav.ts +3 -1
  162. package/lib/newsroom-organizer-nav.ts +37 -0
  163. package/lib/newsroom-section-routing.ts +20 -0
  164. package/lib/oauth-redirect-priority.ts +40 -0
  165. package/lib/papyrus-web-locations.ts +7 -6
  166. package/lib/plugin-settings.ts +11 -0
  167. package/lib/presentation-footer.ts +3 -2
  168. package/lib/pretext-layout.ts +12 -3
  169. package/lib/priming-proof-report.ts +89 -0
  170. package/lib/prospectus-site-identity.ts +303 -0
  171. package/lib/publication-cyclotron.ts +74 -0
  172. package/lib/rate-limit-retry.ts +55 -0
  173. package/lib/reader-base-path.ts +24 -0
  174. package/lib/reader-cache-lifetime.ts +5 -0
  175. package/lib/reader-static-params.ts +85 -4
  176. package/lib/reader-storage-url.ts +9 -12
  177. package/lib/reference-policy.ts +5 -0
  178. package/lib/relevance-decisions.ts +103 -0
  179. package/lib/relevance-status.ts +63 -0
  180. package/lib/renderer.ts +7 -0
  181. package/lib/revalidate-prewarm.ts +53 -0
  182. package/lib/revalidate-secret.ts +59 -0
  183. package/lib/robots-policy.ts +14 -0
  184. package/lib/root-route-redirect.ts +16 -0
  185. package/lib/semantic-graph.ts +7 -0
  186. package/lib/shared-credentials-provider.ts +61 -0
  187. package/lib/signup-csv.ts +24 -0
  188. package/lib/site-brand.ts +59 -0
  189. package/lib/staging-gated-path.ts +23 -0
  190. package/lib/staging-preview-object.ts +21 -5
  191. package/lib/test-editor-override.ts +4 -0
  192. package/lib/video-loading.ts +22 -0
  193. package/lib/video-script.ts +14 -0
  194. package/middleware.ts +11 -19
  195. package/package.json +64 -1
  196. package/renderers/pretext/article-page.tsx +39 -3
  197. package/renderers/pretext/presentation-shared.tsx +113 -0
  198. package/renderers/pretext/presentation-shell.tsx +15 -102
  199. package/renderers/pretext/rhythm-article-page.tsx +165 -0
  200. package/renderers/pretext/rhythm-blog-presentation.tsx +279 -0
  201. package/renderers/pretext/rhythm-shell-style.ts +35 -0
  202. package/routes.manifest.json +68 -12
  203. package/src/sync.mjs +30 -6
  204. package/src/with-papyrus.d.mts +9 -0
  205. package/src/with-papyrus.mjs +48 -3
  206. package/amplify/functions/graphql-jwt-authorizer/handler.ts +0 -193
  207. package/amplify/functions/graphql-jwt-authorizer/resource.js +0 -14
  208. package/amplify/functions/graphql-jwt-authorizer/resource.ts +0 -15
package/lib/site-brand.ts CHANGED
@@ -2,6 +2,7 @@ import type { ComponentType } from "react";
2
2
  import type { EditionPresentationFormat } from "./content-types";
3
3
  import type { Article, ArticleVideoAsset } from "./articles";
4
4
  import type { BlogPageBackgroundProps } from "../components/blog-page-background";
5
+ import type { SiteBrandFont } from "./brand-fonts";
5
6
  import type { PictogramFigureProps } from "../components/pictogram-figure";
6
7
  import papyrusSite from "papyrus-site";
7
8
  import {
@@ -24,6 +25,23 @@ export type SiteBrandComponents = {
24
25
  BlogPageBackground?: ComponentType<BlogPageBackgroundProps>;
25
26
  };
26
27
 
28
+ /**
29
+ * One line of a brand's header tagline: an emphasized lead and a plain tail,
30
+ * e.g. `{ emphasis: "Practical advice", tail: "for staying secure" }`.
31
+ */
32
+ export type SiteBrandTaglineLine = {
33
+ emphasis: string;
34
+ tail: string;
35
+ };
36
+
37
+ /**
38
+ * Blog presentation layout. `"classic"` (the default) is the original blog
39
+ * layout. `"rhythm"` snaps every height and gap to the vertical rhythm grid,
40
+ * floats featured images on the grid, draws rhythm rules between items, adds
41
+ * the eyebrow/tagline header and the rhythm overlay hotkey (Ctrl+=).
42
+ */
43
+ export type SiteBrandBlogLayout = "classic" | "rhythm";
44
+
27
45
  /** Default scenario content a brand supplies for the layout lab. */
28
46
  export type SiteBrandDemoEdition = {
29
47
  title: string;
@@ -46,6 +64,19 @@ export type SiteBrandVideo = {
46
64
  rhythm?: Record<string, unknown>;
47
65
  };
48
66
 
67
+ /**
68
+ * The publication's name for its one content item. A content item keeps one
69
+ * identity and one lifecycle from idea through research, reporting,
70
+ * copywriting and publication; only the word editors see changes per
71
+ * publication (for example Story, Article or Post).
72
+ */
73
+ export type ContentItemName = {
74
+ singular: string;
75
+ plural: string;
76
+ };
77
+
78
+ export const DEFAULT_CONTENT_ITEM_NAME: ContentItemName = { singular: "Story", plural: "Stories" };
79
+
49
80
  export type SiteBrand = {
50
81
  id: SiteBrandId;
51
82
  appTitle: string;
@@ -53,8 +84,14 @@ export type SiteBrand = {
53
84
  mastheadTitle: string;
54
85
  mastheadSubtitle: string;
55
86
  mastheadTagline?: string;
87
+ /** Rhythm blog layout only: small line above the masthead title, e.g. "Acme Research". The first word is emphasized. */
88
+ mastheadEyebrow?: string;
89
+ /** Rhythm blog layout only: structured tagline; `mastheadTagline` stays the plain-text fallback. */
90
+ mastheadTaglineLines?: SiteBrandTaglineLine[];
56
91
  backToHomeLabel: string;
57
92
  articleTitleSuffix: string;
93
+ /** Defaults to Story / Stories, the newsroom term. */
94
+ contentItemName?: ContentItemName;
58
95
  placeholderByline: string;
59
96
  defaultPresentation: EditionPresentationFormat;
60
97
  forcedPresentation?: EditionPresentationFormat;
@@ -80,13 +117,31 @@ export type SiteBrand = {
80
117
  rootRoute?: RootRouteConfig;
81
118
  /** Public URL prefix for newsroom routes. Default `/newsroom`; use `""` on a CMS-only subdomain. */
82
119
  newsroomBasePath?: string;
120
+ /** Public URL prefix for the Pretext reader (home, editions, articles, archive, settings). Default `""`; e.g. `/information`. */
121
+ readerBasePath?: string;
83
122
  opsChrome: OpsChrome;
123
+ /** Opt-in CMS viewport layout; independent of the public reader renderer/theme. */
124
+ newsroomLayout?: "organizer";
84
125
  corpusKey: string;
85
126
  steeringConfigPath: string;
86
127
  newsroomSectionsConfigPath: string;
87
128
  analysisProfilesPath: string;
88
129
  publicationName: string;
89
130
  components?: SiteBrandComponents;
131
+ /**
132
+ * Video player chrome. `"native"` (default) is the browser's controls;
133
+ * `"framed"` wraps the media in `.article-video__media` and renders a
134
+ * "Play Video" button with a seek bar (`.article-video__cta*`), styled by the publication CSS.
135
+ */
136
+ videoPlayer?: "native" | "framed";
137
+ /** Blog presentation layout. Default `"classic"`. See `SiteBrandBlogLayout`. */
138
+ blogLayout?: SiteBrandBlogLayout;
139
+ /**
140
+ * Brand font slot: families the root layout loads (Google Fonts stylesheet
141
+ * or self-hosted files) and exposes as CSS custom properties. See
142
+ * `lib/brand-fonts.ts` and docs/brand-slots.md.
143
+ */
144
+ fonts?: SiteBrandFont[];
90
145
  demoEdition?: SiteBrandDemoEdition;
91
146
  video?: SiteBrandVideo;
92
147
  };
@@ -164,6 +219,10 @@ export function getSiteBrand(id: SiteBrandId = resolveSiteBrandId()): SiteBrand
164
219
 
165
220
  export const SITE_BRAND = getSiteBrand();
166
221
 
222
+ export function getContentItemName(brand: SiteBrand = SITE_BRAND): ContentItemName {
223
+ return brand.contentItemName ?? DEFAULT_CONTENT_ITEM_NAME;
224
+ }
225
+
167
226
  /** Root-route config for a brand, defaulting to `{ kind: "reader" }`. */
168
227
  export function getRootRoute(brand: SiteBrand = SITE_BRAND): RootRouteConfig {
169
228
  return brand.rootRoute ?? { kind: "reader" };
@@ -0,0 +1,23 @@
1
+ import { normalizeReaderBasePath } from "./reader-base-path";
2
+
3
+ function isStaticOrApiPath(pathname: string): boolean {
4
+ return (
5
+ pathname.startsWith("/_next")
6
+ || pathname.startsWith("/api")
7
+ || pathname === "/favicon.ico"
8
+ || pathname.startsWith("/icon")
9
+ || /\.[a-zA-Z0-9]+$/.test(pathname)
10
+ );
11
+ }
12
+
13
+ export { isStaticOrApiPath };
14
+
15
+ export function isStagingGatedPath(pathname: string, readerBasePath: string = "", newsroomAtRoot: boolean = false): boolean {
16
+ if (newsroomAtRoot) return false;
17
+ if (isStaticOrApiPath(pathname)) return false;
18
+ if (pathname === "/robots.txt") return false;
19
+ if (pathname === "/newsroom" || pathname.startsWith("/newsroom/")) return false;
20
+ const base = normalizeReaderBasePath(readerBasePath);
21
+ if (base) return pathname === base || pathname.startsWith(`${base}/`);
22
+ return true;
23
+ }
@@ -8,6 +8,7 @@ export const PREVIEW_KEY_PREFIX = "preview/";
8
8
 
9
9
  const PRESIGN_SECONDS = 60;
10
10
  const NOT_FOUND_ERROR_NAMES = ["NotFound", "NoSuchKey"];
11
+ const FORBIDDEN_ERROR_NAMES = ["Forbidden", "AccessDenied"];
11
12
  const PREVIEW_EXCLUDED_PREFIXES = ["/newsroom", "/api", "/_next", PREVIEW_ROUTE_PREFIX];
12
13
 
13
14
  export function isStaticPreviewEnabled(environment: Record<string, string | undefined> = process.env): boolean {
@@ -32,13 +33,14 @@ export function previewCandidateKeys(requestPath: string): string[] | null {
32
33
  return [base, `${base}.html`, `${base}/index.html`];
33
34
  }
34
35
 
35
- export function shouldRewriteToPreview(pathname: string): boolean {
36
+ export function shouldRewriteToPreview(pathname: string, newsroomAtRoot: boolean = false): boolean {
36
37
  if (pathname === "/favicon.ico" || pathname === "/robots.txt" || pathname.startsWith("/icon")) return false;
37
- return !PREVIEW_EXCLUDED_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
38
+ if (PREVIEW_EXCLUDED_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`))) return false;
39
+ return !newsroomAtRoot || pathname.endsWith(".html");
38
40
  }
39
41
 
40
- export function isPreviewGatedPath(pathname: string): boolean {
41
- return shouldRewriteToPreview(pathname) || pathname === PREVIEW_ROUTE_PREFIX || pathname.startsWith(`${PREVIEW_ROUTE_PREFIX}/`);
42
+ export function isPreviewGatedPath(pathname: string, newsroomAtRoot: boolean = false): boolean {
43
+ return shouldRewriteToPreview(pathname, newsroomAtRoot) || pathname === PREVIEW_ROUTE_PREFIX || pathname.startsWith(`${PREVIEW_ROUTE_PREFIX}/`);
42
44
  }
43
45
 
44
46
  export function previewRewritePathname(pathname: string): string {
@@ -54,6 +56,12 @@ function isNotFoundError(error: unknown): boolean {
54
56
  return NOT_FOUND_ERROR_NAMES.includes(name);
55
57
  }
56
58
 
59
+ function isForbiddenError(error: unknown): boolean {
60
+ const candidate = error as { name?: string; $metadata?: { httpStatusCode?: number }; metadata?: { httpStatusCode?: number } } | null;
61
+ const status = candidate?.$metadata?.httpStatusCode ?? candidate?.metadata?.httpStatusCode;
62
+ return status === 403 || FORBIDDEN_ERROR_NAMES.includes(candidate?.name ?? "");
63
+ }
64
+
57
65
  async function presignFirstExistingKey(candidateKeys: string[]): Promise<URL | null> {
58
66
  const { runWithAmplifyServerContext } = getAmplifyServerRuntime();
59
67
  return runWithAmplifyServerContext({
@@ -83,7 +91,15 @@ export async function streamPreviewObject(pathname: string): Promise<Response> {
83
91
  if (!isStaticPreviewEnabled()) return notFound();
84
92
  const candidateKeys = previewCandidateKeys(previewRequestPath(pathname));
85
93
  if (!candidateKeys) return notFound();
86
- const presigned = await presignFirstExistingKey(candidateKeys);
94
+ let presigned: URL | null;
95
+ try {
96
+ presigned = await presignFirstExistingKey(candidateKeys);
97
+ } catch (error) {
98
+ if (isForbiddenError(error)) {
99
+ return new Response("Staging preview is limited to editors and admins.", { status: 403, headers: { "Cache-Control": "private, no-store" } });
100
+ }
101
+ throw error;
102
+ }
87
103
  if (!presigned) return notFound();
88
104
  const upstream = await fetch(presigned);
89
105
  if (!upstream.ok || !upstream.body) return notFound();
@@ -0,0 +1,4 @@
1
+ /** Fixture identity overrides can never replace production authorization. */
2
+ export function testEditorOverrideAllowed(runtime: string | undefined, flag: string | undefined): boolean {
3
+ return runtime === "test" || (runtime === "development" && flag === "true");
4
+ }
@@ -0,0 +1,22 @@
1
+ export type VideoPreload = "none" | "metadata";
2
+
3
+ export type VideoLoadingPlan = {
4
+ attachSource: boolean;
5
+ preload: VideoPreload;
6
+ };
7
+
8
+ export type VideoLoadingInput = {
9
+ hasHydrated: boolean;
10
+ isNearViewport: boolean;
11
+ hasPoster: boolean;
12
+ };
13
+
14
+ export function planVideoLoading({ hasHydrated, isNearViewport, hasPoster }: VideoLoadingInput): VideoLoadingPlan {
15
+ const attachSource = hasHydrated && isNearViewport;
16
+ const preload: VideoPreload = attachSource && !hasPoster ? "metadata" : "none";
17
+ return { attachSource, preload };
18
+ }
19
+
20
+ export function videoElementKey(attachSource: boolean, resolvedSource: string): string {
21
+ return attachSource ? resolvedSource : "unattached";
22
+ }
@@ -21,6 +21,20 @@ export type VideoScriptRef = {
21
21
  targetKind: VideoScriptTargetKind;
22
22
  };
23
23
 
24
+ export const EDITION_OVERVIEW_VIDEO_KEY = "edition-overview";
25
+
26
+ export function collectVideoScriptTargets(
27
+ items: ReadonlyArray<{ slug: string; video?: unknown }>,
28
+ hasEditionVideo: boolean,
29
+ ): string[] {
30
+ const targets = new Set<string>();
31
+ if (hasEditionVideo) targets.add(EDITION_OVERVIEW_VIDEO_KEY);
32
+ for (const item of items) {
33
+ if (item.video) targets.add(item.slug);
34
+ }
35
+ return [...targets];
36
+ }
37
+
24
38
  export function videomlItemSlug(targetSlug: string): string {
25
39
  return `${targetSlug}--videoml`;
26
40
  }
package/middleware.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import type { NextRequest } from "next/server";
2
2
  import { NextResponse } from "next/server";
3
3
  import { getSiteBrand } from "./lib/site-brand";
4
+ import { newsroomHref } from "./lib/newsroom-base-path";
5
+ import { getReaderBasePath } from "./lib/reader-base-path";
6
+ import { isStagingGatedPath, isStaticOrApiPath } from "./lib/staging-gated-path";
4
7
  import { getSiteEnv, isIndexable } from "./lib/site-env";
5
8
  import { getStagingAccess } from "./lib/staging-gate";
6
9
  import {
@@ -15,25 +18,12 @@ function usesNewsroomRootPaths(): boolean {
15
18
  return brand.rootRoute?.kind === "newsroom" && brand.newsroomBasePath === "";
16
19
  }
17
20
 
18
- function isStaticOrApiPath(pathname: string): boolean {
19
- return (
20
- pathname.startsWith("/_next")
21
- || pathname.startsWith("/api")
22
- || pathname === "/favicon.ico"
23
- || pathname.startsWith("/icon")
24
- || /\.[a-zA-Z0-9]+$/.test(pathname)
25
- );
26
- }
27
-
28
- function isStagingGatedPath(pathname: string): boolean {
29
- if (isStaticOrApiPath(pathname)) return false;
30
- if (pathname === "/robots.txt") return false;
31
- if (pathname === "/newsroom" || pathname.startsWith("/newsroom/")) return false;
32
- return true;
21
+ function isStagingGatedPathForBrand(pathname: string): boolean {
22
+ return isStagingGatedPath(pathname, getReaderBasePath(), usesNewsroomRootPaths());
33
23
  }
34
24
 
35
25
  function routeRequest(request: NextRequest): NextResponse {
36
- if (isStaticPreviewEnabled() && shouldRewriteToPreview(request.nextUrl.pathname)) {
26
+ if (isStaticPreviewEnabled() && shouldRewriteToPreview(request.nextUrl.pathname, usesNewsroomRootPaths())) {
37
27
  const rewriteUrl = request.nextUrl.clone();
38
28
  rewriteUrl.pathname = previewRewritePathname(request.nextUrl.pathname);
39
29
  return NextResponse.rewrite(rewriteUrl);
@@ -48,7 +38,7 @@ function routeRequest(request: NextRequest): NextResponse {
48
38
  }
49
39
 
50
40
  if (pathname === "/newsroom" || pathname === "/newsroom/") {
51
- return NextResponse.redirect(new URL("/", request.url));
41
+ return NextResponse.redirect(new URL(`/${request.nextUrl.search}`, request.url));
52
42
  }
53
43
  if (pathname.startsWith("/newsroom/")) {
54
44
  const target = pathname.slice("/newsroom".length) || "/";
@@ -71,12 +61,14 @@ function routeRequest(request: NextRequest): NextResponse {
71
61
  export async function middleware(request: NextRequest): Promise<NextResponse> {
72
62
  let sessionResponse: NextResponse | null = null;
73
63
  const { pathname } = request.nextUrl;
74
- const gated = isStaticPreviewEnabled() ? isPreviewGatedPath(pathname) : isStagingGatedPath(pathname);
64
+ const gated = isStaticPreviewEnabled()
65
+ ? isPreviewGatedPath(pathname, usesNewsroomRootPaths())
66
+ : isStagingGatedPathForBrand(pathname);
75
67
  if (getSiteEnv() === "staging" && gated) {
76
68
  sessionResponse = NextResponse.next();
77
69
  const access = await getStagingAccess(request, sessionResponse);
78
70
  if (access === "anonymous") {
79
- return withRobotsHeader(NextResponse.redirect(new URL("/newsroom", request.url)));
71
+ return withRobotsHeader(NextResponse.redirect(new URL(newsroomHref(), request.url)));
80
72
  }
81
73
  if (access === "forbidden") {
82
74
  return withRobotsHeader(new NextResponse("Staging is limited to editors and admins.", { status: 403 }));
package/package.json CHANGED
@@ -86,19 +86,32 @@
86
86
  "./components/newsroom-article-editor": "./components/newsroom-article-editor.tsx",
87
87
  "./components/newsroom-articles-view": "./components/newsroom-articles-view.tsx",
88
88
  "./components/newsroom-assignments-view": "./components/newsroom-assignments-view.tsx",
89
+ "./components/newsroom-board-view": "./components/newsroom-board-view.tsx",
90
+ "./components/newsroom-client-shell": "./components/newsroom-client-shell.tsx",
91
+ "./components/newsroom-curation-provider": "./components/newsroom-curation-provider.tsx",
89
92
  "./components/newsroom-ops-overview": "./components/newsroom-ops-overview.tsx",
90
93
  "./components/newsroom-ops-shell": "./components/newsroom-ops-shell.tsx",
94
+ "./components/newsroom-organizer-shell": "./components/newsroom-organizer-shell.tsx",
91
95
  "./components/newsroom-references-view": "./components/newsroom-references-view.tsx",
92
96
  "./components/newsroom-topics-view": "./components/newsroom-topics-view.tsx",
93
97
  "./components/papyrus-console-shell": "./components/papyrus-console-shell.tsx",
94
98
  "./components/pictogram-figure": "./components/pictogram-figure.tsx",
95
99
  "./components/presentation-footer": "./components/presentation-footer.tsx",
100
+ "./components/presentation-header": "./components/presentation-header.tsx",
101
+ "./components/presentation-rhythm-hrule": "./components/presentation-rhythm-hrule.tsx",
102
+ "./components/priming-proof-page": "./components/priming-proof-page.tsx",
103
+ "./components/publication-cyclotron-panel": "./components/publication-cyclotron-panel.tsx",
96
104
  "./components/reader-auth-control": "./components/reader-auth-control.tsx",
97
105
  "./components/reader-auth-state": "./components/reader-auth-state.ts",
98
106
  "./components/reader-settings": "./components/reader-settings.ts",
99
107
  "./components/reader-theme-control": "./components/reader-theme-control.tsx",
108
+ "./components/reference-curation-controls": "./components/reference-curation-controls.tsx",
109
+ "./components/reference-cyclotron-status": "./components/reference-cyclotron-status.tsx",
110
+ "./components/reference-priming": "./components/reference-priming.tsx",
111
+ "./components/reference-relevance-review": "./components/reference-relevance-review.tsx",
100
112
  "./components/reference-source-preview": "./components/reference-source-preview.tsx",
101
113
  "./components/settings-page": "./components/settings-page.tsx",
114
+ "./components/signup-entries-panel": "./components/signup-entries-panel.tsx",
102
115
  "./components/staging-banner": "./components/staging-banner.tsx",
103
116
  "./components/topic-steering-workspace": "./components/topic-steering-workspace.tsx",
104
117
  "./components/ui/badge": "./components/ui/badge.tsx",
@@ -119,13 +132,22 @@
119
132
  "./components/ui/tabs": "./components/ui/tabs.tsx",
120
133
  "./components/ui/textarea": "./components/ui/textarea.tsx",
121
134
  "./components/ui/tooltip": "./components/ui/tooltip.tsx",
135
+ "./components/use-blog-header-obstacles": "./components/use-blog-header-obstacles.ts",
136
+ "./components/use-brand-fonts-ready": "./components/use-brand-fonts-ready.ts",
137
+ "./components/use-near-viewport": "./components/use-near-viewport.ts",
122
138
  "./components/use-resolved-papyrus-theme": "./components/use-resolved-papyrus-theme.ts",
139
+ "./components/use-rhythm-overlay": "./components/use-rhythm-overlay.ts",
123
140
  "./lib/amplify-outputs-guard": "./lib/amplify-outputs-guard.ts",
124
141
  "./lib/amplify-outputs-path": "./lib/amplify-outputs-path.ts",
125
142
  "./lib/amplify-server-runtime": "./lib/amplify-server-runtime.ts",
143
+ "./lib/approved-priming-proof-reports": "./lib/approved-priming-proof-reports.ts",
126
144
  "./lib/archive-data": "./lib/archive-data.ts",
127
145
  "./lib/archive-types": "./lib/archive-types.ts",
128
146
  "./lib/articles": "./lib/articles.ts",
147
+ "./lib/blog-feature-solver": "./lib/blog-feature-solver.ts",
148
+ "./lib/blog-rhythm": "./lib/blog-rhythm.ts",
149
+ "./lib/brand-fonts-watch": "./lib/brand-fonts-watch.ts",
150
+ "./lib/brand-fonts": "./lib/brand-fonts.ts",
129
151
  "./lib/cached-content-repository": "./lib/cached-content-repository.ts",
130
152
  "./lib/category-dashboard": "./lib/category-dashboard.ts",
131
153
  "./lib/category-repository": "./lib/category-repository.ts",
@@ -133,10 +155,12 @@
133
155
  "./lib/console-web-context": "./lib/console-web-context.ts",
134
156
  "./lib/content-actions-client": "./lib/content-actions-client.ts",
135
157
  "./lib/content-actions-demo": "./lib/content-actions-demo.ts",
158
+ "./lib/content-item-lifecycle": "./lib/content-item-lifecycle.ts",
136
159
  "./lib/content-load-timing": "./lib/content-load-timing.ts",
137
160
  "./lib/content-repository": "./lib/content-repository.ts",
138
161
  "./lib/content-source-context": "./lib/content-source-context.ts",
139
162
  "./lib/content-types": "./lib/content-types.ts",
163
+ "./lib/curation-client": "./lib/curation-client.ts",
140
164
  "./lib/define-site": "./lib/define-site.ts",
141
165
  "./lib/demo-amplify-outputs": "./lib/demo-amplify-outputs.ts",
142
166
  "./lib/dev-sandbox-editor-auth": "./lib/dev-sandbox-editor-auth.ts",
@@ -153,11 +177,13 @@
153
177
  "./lib/markus-ir": "./lib/markus-ir.ts",
154
178
  "./lib/markus-projection": "./lib/markus-projection.ts",
155
179
  "./lib/markus-to-article": "./lib/markus-to-article.ts",
180
+ "./lib/media-route-response": "./lib/media-route-response.ts",
156
181
  "./lib/news-desk-session": "./lib/news-desk-session.ts",
157
182
  "./lib/newspaper-layout": "./lib/newspaper-layout.ts",
158
183
  "./lib/newsroom-articles": "./lib/newsroom-articles.ts",
159
184
  "./lib/newsroom-assignments": "./lib/newsroom-assignments.ts",
160
185
  "./lib/newsroom-base-path": "./lib/newsroom-base-path.ts",
186
+ "./lib/newsroom-board": "./lib/newsroom-board.ts",
161
187
  "./lib/newsroom-card-layout": "./lib/newsroom-card-layout.ts",
162
188
  "./lib/newsroom-category-drilldown": "./lib/newsroom-category-drilldown.ts",
163
189
  "./lib/newsroom-demo-articles": "./lib/newsroom-demo-articles.ts",
@@ -170,35 +196,56 @@
170
196
  "./lib/newsroom-media-backend": "./lib/newsroom-media-backend.ts",
171
197
  "./lib/newsroom-media": "./lib/newsroom-media.ts",
172
198
  "./lib/newsroom-nav": "./lib/newsroom-nav.ts",
199
+ "./lib/newsroom-organizer-nav": "./lib/newsroom-organizer-nav.ts",
173
200
  "./lib/newsroom-references": "./lib/newsroom-references.ts",
201
+ "./lib/newsroom-section-routing": "./lib/newsroom-section-routing.ts",
174
202
  "./lib/newsroom-sections": "./lib/newsroom-sections.ts",
175
203
  "./lib/newsroom-topics": "./lib/newsroom-topics.ts",
204
+ "./lib/oauth-redirect-priority": "./lib/oauth-redirect-priority.ts",
176
205
  "./lib/papyrus-object-kinds": "./lib/papyrus-object-kinds.ts",
177
206
  "./lib/papyrus-web-locations": "./lib/papyrus-web-locations.ts",
178
207
  "./lib/pilobol-sample": "./lib/pilobol-sample.ts",
208
+ "./lib/plugin-settings": "./lib/plugin-settings.ts",
179
209
  "./lib/presentation-footer": "./lib/presentation-footer.ts",
180
210
  "./lib/pretext-layout": "./lib/pretext-layout.ts",
211
+ "./lib/priming-proof-report": "./lib/priming-proof-report.ts",
212
+ "./lib/prospectus-site-identity": "./lib/prospectus-site-identity.ts",
181
213
  "./lib/public-placeholder-config": "./lib/public-placeholder-config.ts",
214
+ "./lib/publication-cyclotron": "./lib/publication-cyclotron.ts",
182
215
  "./lib/publication-items": "./lib/publication-items.ts",
216
+ "./lib/rate-limit-retry": "./lib/rate-limit-retry.ts",
217
+ "./lib/reader-base-path": "./lib/reader-base-path.ts",
218
+ "./lib/reader-cache-lifetime": "./lib/reader-cache-lifetime.ts",
183
219
  "./lib/reader-route-config": "./lib/reader-route-config.ts",
184
220
  "./lib/reader-static-params": "./lib/reader-static-params.ts",
185
221
  "./lib/reader-storage-url": "./lib/reader-storage-url.ts",
186
222
  "./lib/reference-display": "./lib/reference-display.ts",
187
223
  "./lib/reference-policy": "./lib/reference-policy.ts",
188
224
  "./lib/reference-source-preview": "./lib/reference-source-preview.ts",
225
+ "./lib/relevance-decisions": "./lib/relevance-decisions.ts",
226
+ "./lib/relevance-status": "./lib/relevance-status.ts",
189
227
  "./lib/renderer-config": "./lib/renderer-config.ts",
190
228
  "./lib/renderer": "./lib/renderer.ts",
191
229
  "./lib/reporting-story-budget": "./lib/reporting-story-budget.ts",
230
+ "./lib/revalidate-prewarm": "./lib/revalidate-prewarm.ts",
231
+ "./lib/revalidate-secret": "./lib/revalidate-secret.ts",
232
+ "./lib/robots-policy": "./lib/robots-policy.ts",
233
+ "./lib/root-route-redirect": "./lib/root-route-redirect.ts",
192
234
  "./lib/semantic-graph": "./lib/semantic-graph.ts",
235
+ "./lib/shared-credentials-provider": "./lib/shared-credentials-provider.ts",
236
+ "./lib/signup-csv": "./lib/signup-csv.ts",
193
237
  "./lib/site-brand-tenant": "./lib/site-brand-tenant.ts",
194
238
  "./lib/site-brand": "./lib/site-brand.ts",
195
239
  "./lib/site-env": "./lib/site-env.ts",
196
240
  "./lib/site-renderer": "./lib/site-renderer.ts",
197
241
  "./lib/site-stack": "./lib/site-stack.ts",
198
242
  "./lib/staging-gate": "./lib/staging-gate.ts",
243
+ "./lib/staging-gated-path": "./lib/staging-gated-path.ts",
199
244
  "./lib/staging-preview-object": "./lib/staging-preview-object.ts",
245
+ "./lib/test-editor-override": "./lib/test-editor-override.ts",
200
246
  "./lib/themed-image": "./lib/themed-image.ts",
201
247
  "./lib/utils": "./lib/utils.ts",
248
+ "./lib/video-loading": "./lib/video-loading.ts",
202
249
  "./lib/video-mode": "./lib/video-mode.ts",
203
250
  "./lib/video-script": "./lib/video-script.ts",
204
251
  "./renderers/markus/index": "./renderers/markus/index.ts",
@@ -206,17 +253,23 @@
206
253
  "./renderers/pretext/article-page": "./renderers/pretext/article-page.tsx",
207
254
  "./renderers/pretext/index": "./renderers/pretext/index.ts",
208
255
  "./renderers/pretext/layouts": "./renderers/pretext/layouts.ts",
256
+ "./renderers/pretext/presentation-shared": "./renderers/pretext/presentation-shared.tsx",
209
257
  "./renderers/pretext/presentation-shell": "./renderers/pretext/presentation-shell.tsx",
258
+ "./renderers/pretext/rhythm-article-page": "./renderers/pretext/rhythm-article-page.tsx",
259
+ "./renderers/pretext/rhythm-blog-presentation": "./renderers/pretext/rhythm-blog-presentation.tsx",
260
+ "./renderers/pretext/rhythm-shell-style": "./renderers/pretext/rhythm-shell-style.ts",
210
261
  "./app/%5F_preview/[[...path]]/route": "./app/%5F_preview/[[...path]]/route.ts",
211
262
  "./app/[year]/[month]/[day]/[articleSlug]/page": "./app/[year]/[month]/[day]/[articleSlug]/page.tsx",
212
263
  "./app/[year]/[month]/[day]/page": "./app/[year]/[month]/[day]/page.tsx",
213
264
  "./app/[year]/[month]/[day]/page/[pageNumber]/page": "./app/[year]/[month]/[day]/page/[pageNumber]/page.tsx",
214
265
  "./app/[year]/[month]/[day]/section/[sectionKey]/page": "./app/[year]/[month]/[day]/section/[sectionKey]/page.tsx",
215
266
  "./app/api/archive/editions/route": "./app/api/archive/editions/route.ts",
267
+ "./app/api/cyclotron/priming-proof/route": "./app/api/cyclotron/priming-proof/route.ts",
216
268
  "./app/api/media/[...path]/route": "./app/api/media/[...path]/route.ts",
217
269
  "./app/api/revalidate/route": "./app/api/revalidate/route.ts",
218
270
  "./app/archive/page": "./app/archive/page.tsx",
219
271
  "./app/articles/[slug]/page": "./app/articles/[slug]/page.tsx",
272
+ "./app/cyclotron/page": "./app/cyclotron/page.tsx",
220
273
  "./app/layout": "./app/layout.tsx",
221
274
  "./app/newsroom/[section]/[...selection]/page": "./app/newsroom/[section]/[...selection]/page.tsx",
222
275
  "./app/newsroom/[section]/page": "./app/newsroom/[section]/page.tsx",
@@ -228,7 +281,10 @@
228
281
  "./app/newsroom/layout": "./app/newsroom/layout.tsx",
229
282
  "./app/newsroom/page": "./app/newsroom/page.tsx",
230
283
  "./app/newsroom/sections/[sectionId]/page": "./app/newsroom/sections/[sectionId]/page.tsx",
284
+ "./app/newsroom/settings/page": "./app/newsroom/settings/page.tsx",
285
+ "./app/newsroom/work/page": "./app/newsroom/work/page.tsx",
231
286
  "./app/page": "./app/page.tsx",
287
+ "./app/robots": "./app/robots.ts",
232
288
  "./app/settings/page": "./app/settings/page.tsx"
233
289
  },
234
290
  "files": [
@@ -276,17 +332,22 @@
276
332
  "optional": true
277
333
  }
278
334
  },
279
- "version": "1.0.0-next.9",
335
+ "version": "1.0.0",
280
336
  "dependencies": {
281
337
  "@aws-amplify/adapter-nextjs": "^1.7.3",
338
+ "@aws-crypto/sha256-js": "5.2.0",
282
339
  "@aws-sdk/client-cognito-identity-provider": "3.1045.0",
283
340
  "@aws-sdk/client-lambda": "^3.1045.0",
284
341
  "@aws-sdk/client-s3": "^3.1045.0",
285
342
  "@aws-sdk/client-ssm": "^3.1045.0",
343
+ "@aws-sdk/credential-provider-node": "3.972.84",
286
344
  "@aws-sdk/s3-request-presigner": "^3.1045.0",
287
345
  "@base-ui/react": "^1.5.0",
288
346
  "@chenglou/pretext": "^0.0.7",
347
+ "@kanbus/ui": "https://github.com/AnthusAI/Kanbus/releases/download/ui-v0.2.0/kanbus-ui-0.2.0.tgz",
289
348
  "@radix-ui/colors": "3.0.0",
349
+ "@smithy/protocol-http": "5.4.1",
350
+ "@smithy/signature-v4": "5.7.4",
290
351
  "@streamdown/cjk": "^1.0.3",
291
352
  "@streamdown/code": "^1.1.1",
292
353
  "@streamdown/math": "^1.0.2",
@@ -295,9 +356,11 @@
295
356
  "ai": "^6.0.191",
296
357
  "anthus-vultus": "github:AnthusAI/Vultus#af98d57",
297
358
  "aws-amplify": "^6.17.0",
359
+ "aws-jwt-verify": "4.0.1",
298
360
  "class-variance-authority": "^0.7.1",
299
361
  "clsx": "^2.1.1",
300
362
  "cmdk": "^1.1.1",
363
+ "cyclotron": "https://github.com/AnthusAI/Cyclotron/releases/download/ui-v0.2.2/cyclotron-0.2.2.tgz",
301
364
  "esbuild": "^0.28.2",
302
365
  "gray-matter": "^4.0.3",
303
366
  "gsap": "^3.15.0",
@@ -9,6 +9,7 @@ import { SITE_BRAND } from "../../lib/site-brand";
9
9
  import { ArticleVideoFigure } from "../../components/article-video";
10
10
  import { PictogramFigure as GenericPictogramFigure } from "../../components/pictogram-figure";
11
11
  import { PresentationFooter } from "../../components/presentation-footer";
12
+ import { RhythmArticlePage } from "./rhythm-article-page";
12
13
 
13
14
  const PictogramFigure = SITE_BRAND.components?.PictogramFigure ?? GenericPictogramFigure;
14
15
 
@@ -16,7 +17,24 @@ export type { ArticlePageEditionFooter };
16
17
 
17
18
  type ArticlePageViewProps = RenderArticleProps;
18
19
 
19
- export function ArticlePageView({ article, backHref, backLabel = SITE_BRAND.backToHomeLabel, editionFooter, editionDate }: ArticlePageViewProps) {
20
+ export function ArticlePageView({ article, backHref, backLabel = SITE_BRAND.backToHomeLabel, editionFooter, editionDate, videoScript = null }: ArticlePageViewProps) {
21
+ if (SITE_BRAND.blogLayout === "rhythm") {
22
+ return (
23
+ <RhythmArticlePage
24
+ body={article.body}
25
+ deck={article.deck}
26
+ editionDate={editionDate}
27
+ editionFooter={editionFooter}
28
+ headline={article.headline}
29
+ image={article.image ?? null}
30
+ section={article.section}
31
+ slug={article.slug}
32
+ video={article.video ?? null}
33
+ videoScript={videoScript}
34
+ />
35
+ );
36
+ }
37
+
20
38
  const articleDate = editionDate ? formatArticleDate(editionDate) : null;
21
39
 
22
40
  return (
@@ -55,9 +73,27 @@ export function ArticlePageView({ article, backHref, backLabel = SITE_BRAND.back
55
73
 
56
74
  type ItemPageViewProps = RenderItemProps;
57
75
 
58
- export function ItemPageView({ item, backHref, backLabel = "Back to edition", editionFooter, editionDate }: ItemPageViewProps) {
76
+ export function ItemPageView({ item, backHref, backLabel = "Back to edition", editionFooter, editionDate, videoScript = null }: ItemPageViewProps) {
59
77
  if (item.type === "article") {
60
- return <ArticlePageView article={item} backHref={backHref} backLabel={backLabel} editionFooter={editionFooter} editionDate={editionDate} />;
78
+ return <ArticlePageView article={item} backHref={backHref} backLabel={backLabel} editionFooter={editionFooter} editionDate={editionDate} videoScript={videoScript} />;
79
+ }
80
+
81
+ if (SITE_BRAND.blogLayout === "rhythm") {
82
+ return (
83
+ <RhythmArticlePage
84
+ body={item.body ?? []}
85
+ deck={item.deck}
86
+ editionDate={editionDate}
87
+ editionFooter={editionFooter}
88
+ headline={item.title}
89
+ image={item.image ?? null}
90
+ itemType={item.type}
91
+ section={item.section ?? item.type}
92
+ slug={item.slug}
93
+ video={getPublicationItemVideoAsset(item) ?? null}
94
+ videoScript={videoScript}
95
+ />
96
+ );
61
97
  }
62
98
 
63
99
  const itemDate = editionDate ? formatArticleDate(editionDate) : null;