From deb901f338721bbfa320dfa074f02347ef1bd68a Mon Sep 17 00:00:00 2001 From: Rik Roots Date: Thu, 16 Apr 2026 10:03:10 +0100 Subject: [PATCH 01/11] Update ContentAtomBlockElement to include caption field --- dotcom-rendering/src/frontend/schemas/feArticle.json | 3 +++ dotcom-rendering/src/model/block-schema.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json index 777dfd17431..bcae1c444c9 100644 --- a/dotcom-rendering/src/frontend/schemas/feArticle.json +++ b/dotcom-rendering/src/frontend/schemas/feArticle.json @@ -1914,6 +1914,9 @@ }, "atomId": { "type": "string" + }, + "caption": { + "type": "string" } }, "required": [ diff --git a/dotcom-rendering/src/model/block-schema.json b/dotcom-rendering/src/model/block-schema.json index 9dfcdcb8629..196176cd3b0 100644 --- a/dotcom-rendering/src/model/block-schema.json +++ b/dotcom-rendering/src/model/block-schema.json @@ -1387,6 +1387,9 @@ }, "atomId": { "type": "string" + }, + "caption": { + "type": "string" } }, "required": [ From 32f2a0d04f7453452a7d8b3e841bff1a8d688398 Mon Sep 17 00:00:00 2001 From: Rik Roots Date: Thu, 16 Apr 2026 10:20:28 +0100 Subject: [PATCH 02/11] Regenerate schemas --- dotcom-rendering/src/frontend/schemas/feArticle.json | 3 --- dotcom-rendering/src/model/block-schema.json | 3 --- 2 files changed, 6 deletions(-) diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json index bcae1c444c9..777dfd17431 100644 --- a/dotcom-rendering/src/frontend/schemas/feArticle.json +++ b/dotcom-rendering/src/frontend/schemas/feArticle.json @@ -1914,9 +1914,6 @@ }, "atomId": { "type": "string" - }, - "caption": { - "type": "string" } }, "required": [ diff --git a/dotcom-rendering/src/model/block-schema.json b/dotcom-rendering/src/model/block-schema.json index 196176cd3b0..9dfcdcb8629 100644 --- a/dotcom-rendering/src/model/block-schema.json +++ b/dotcom-rendering/src/model/block-schema.json @@ -1387,9 +1387,6 @@ }, "atomId": { "type": "string" - }, - "caption": { - "type": "string" } }, "required": [ From a7cd14adb45ebc380c481cfa089d11771654109a Mon Sep 17 00:00:00 2001 From: Rik Roots Date: Thu, 16 Apr 2026 10:47:01 +0100 Subject: [PATCH 03/11] Update ContentAtomBlockElement to include caption field --- dotcom-rendering/src/types/content.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/dotcom-rendering/src/types/content.ts b/dotcom-rendering/src/types/content.ts index 03cffb278ef..7662a8a64cd 100644 --- a/dotcom-rendering/src/types/content.ts +++ b/dotcom-rendering/src/types/content.ts @@ -213,6 +213,7 @@ export interface ContentAtomBlockElement { _type: 'model.dotcomrendering.pageElements.ContentAtomBlockElement'; elementId: string; atomId: string; + caption?: string; } interface DisclaimerBlockElement { From 86b6cede32c0cde1d05b49767b9a9c90dd27c3ac Mon Sep 17 00:00:00 2001 From: Rik Roots Date: Thu, 16 Apr 2026 10:50:07 +0100 Subject: [PATCH 04/11] Regenerate schemas --- dotcom-rendering/src/frontend/schemas/feArticle.json | 3 +++ dotcom-rendering/src/model/block-schema.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json index 777dfd17431..bcae1c444c9 100644 --- a/dotcom-rendering/src/frontend/schemas/feArticle.json +++ b/dotcom-rendering/src/frontend/schemas/feArticle.json @@ -1914,6 +1914,9 @@ }, "atomId": { "type": "string" + }, + "caption": { + "type": "string" } }, "required": [ diff --git a/dotcom-rendering/src/model/block-schema.json b/dotcom-rendering/src/model/block-schema.json index 9dfcdcb8629..196176cd3b0 100644 --- a/dotcom-rendering/src/model/block-schema.json +++ b/dotcom-rendering/src/model/block-schema.json @@ -1387,6 +1387,9 @@ }, "atomId": { "type": "string" + }, + "caption": { + "type": "string" } }, "required": [ From 62f9ece57321a23ef974ebe9e2bb61df4f55b6b0 Mon Sep 17 00:00:00 2001 From: Rik Roots Date: Tue, 21 Apr 2026 12:03:15 +0100 Subject: [PATCH 05/11] Consider where work needs to be done to implement feature --- dotcom-rendering/src/components/Caption.tsx | 1 + .../src/components/SelfHostedVideoPlayer.tsx | 2 ++ dotcom-rendering/src/lib/renderElement.tsx | 8 ++++++++ dotcom-rendering/src/lib/video.ts | 2 ++ dotcom-rendering/src/types/content.ts | 10 ++++++++++ dotcom-rendering/src/types/mainMedia.ts | 2 ++ 6 files changed, 25 insertions(+) diff --git a/dotcom-rendering/src/components/Caption.tsx b/dotcom-rendering/src/components/Caption.tsx index 536aea4105e..590c65f183a 100644 --- a/dotcom-rendering/src/components/Caption.tsx +++ b/dotcom-rendering/src/components/Caption.tsx @@ -1,3 +1,4 @@ +// DEV NOTE: at this point, we should be supplying the component with `captionText` - decisions about what that string should be should have taken place before this point import { css } from '@emotion/react'; import { between, diff --git a/dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx b/dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx index 0727c28f3fd..4d718d39f3f 100644 --- a/dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx +++ b/dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx @@ -1,3 +1,5 @@ +// DEV NOTE: by this point, captions text should be settled - this component doesn't care about it + import { css } from '@emotion/react'; import { space, diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx index cdfc0982545..931e7201cf6 100644 --- a/dotcom-rendering/src/lib/renderElement.tsx +++ b/dotcom-rendering/src/lib/renderElement.tsx @@ -372,6 +372,7 @@ export const renderElement = ({ ); case 'model.dotcomrendering.pageElements.GuVideoBlockElement': + // DEV NOTE: makes use of `credit` and `caption` attribute data return ( ); } else { + // DEV NOTE: It's at this point where `title` attribute content gets set to `caption` attribute + // - add in a check to override title with our new caption at this point + // - but we need to make sure that the `caption` attribute gets fed down the chain to this point return ( ; case 'model.dotcomrendering.pageElements.VideoFacebookBlockElement': + // DEV NOTE: ignoring FaceBook video for now return ( ); case 'model.dotcomrendering.pageElements.VideoVimeoBlockElement': + // DEV NOTE: ignoring Vimeo video for now return ( Date: Wed, 22 Apr 2026 09:54:09 +0100 Subject: [PATCH 06/11] Consider where work needs to be done to implement feature --- dotcom-rendering/src/components/SelfHostedVideo.island.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dotcom-rendering/src/components/SelfHostedVideo.island.tsx b/dotcom-rendering/src/components/SelfHostedVideo.island.tsx index e66964e182c..00c58ac4620 100644 --- a/dotcom-rendering/src/components/SelfHostedVideo.island.tsx +++ b/dotcom-rendering/src/components/SelfHostedVideo.island.tsx @@ -1,3 +1,5 @@ +// DEV NOTE: this component takes a `caption` attribute - the text of the caption should be settled upstream so we don't need to do any additional work here. + import { css } from '@emotion/react'; import { isUndefined, log, storage } from '@guardian/libs'; import { from, space, until } from '@guardian/source/foundations'; From ce960e97a627a485d57c605f125be82c5bd4604f Mon Sep 17 00:00:00 2001 From: Rik Roots Date: Wed, 22 Apr 2026 09:55:08 +0100 Subject: [PATCH 07/11] Consider where work needs to be done to implement feature --- dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx b/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx index caf63faab21..9b5bb783af5 100644 --- a/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx +++ b/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx @@ -26,6 +26,8 @@ export const SelfHostedVideoInArticle = ({ role = 'inline', }: SelfHostedVideoInArticleProps) => { const posterImageUrl = element.posterImage?.[0]?.url; + + // DEV NOTE: this is where we need to do work to determine whether the `caption` attribute text gets to override the `title` attribute text const caption = element.title; const sources = extractValidSourcesFromAssets(element.assets); From 173b82d381ab625fc6c8456a4e689f501efb8bd4 Mon Sep 17 00:00:00 2001 From: Simon Byford Date: Tue, 19 May 2026 09:45:24 +0100 Subject: [PATCH 08/11] Update logic --- .../src/components/SelfHostedVideoInArticle.tsx | 6 ++---- dotcom-rendering/src/frontend/schemas/feArticle.json | 6 +++--- dotcom-rendering/src/lib/renderElement.tsx | 3 ++- dotcom-rendering/src/model/block-schema.json | 6 +++--- dotcom-rendering/src/types/content.ts | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx b/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx index 9b5bb783af5..3d312d43f3b 100644 --- a/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx +++ b/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx @@ -16,6 +16,7 @@ type SelfHostedVideoInArticleProps = { isMainMedia: boolean; videoStyle: VideoPlayerFormat; role?: RoleType; + caption?: string; }; export const SelfHostedVideoInArticle = ({ @@ -24,12 +25,9 @@ export const SelfHostedVideoInArticle = ({ isMainMedia, videoStyle, role = 'inline', + caption, }: SelfHostedVideoInArticleProps) => { const posterImageUrl = element.posterImage?.[0]?.url; - - // DEV NOTE: this is where we need to do work to determine whether the `caption` attribute text gets to override the `title` attribute text - const caption = element.title; - const sources = extractValidSourcesFromAssets(element.assets); const aspectRatio = getAspectRatioFromSources(sources); const firstVideoSource = sources[0]; diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json index bcae1c444c9..09e486ed9d2 100644 --- a/dotcom-rendering/src/frontend/schemas/feArticle.json +++ b/dotcom-rendering/src/frontend/schemas/feArticle.json @@ -1914,9 +1914,6 @@ }, "atomId": { "type": "string" - }, - "caption": { - "type": "string" } }, "required": [ @@ -2876,6 +2873,9 @@ }, "role": { "$ref": "#/definitions/RoleType" + }, + "caption": { + "type": "string" } }, "required": [ diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx index 931e7201cf6..e07373b6029 100644 --- a/dotcom-rendering/src/lib/renderElement.tsx +++ b/dotcom-rendering/src/lib/renderElement.tsx @@ -512,6 +512,7 @@ export const renderElement = ({ isMainMedia={isMainMedia} videoStyle={element.videoPlayerFormat} role={element.role} + caption={element.caption ?? element.title} /> ); } else { @@ -523,7 +524,7 @@ export const renderElement = ({ format={format} assets={element.assets} poster={element.posterImage?.[0]?.url} - caption={element.title} + caption={element.caption ?? element.title} isMainMedia={isMainMedia} /> ); diff --git a/dotcom-rendering/src/model/block-schema.json b/dotcom-rendering/src/model/block-schema.json index 196176cd3b0..918da7ac58b 100644 --- a/dotcom-rendering/src/model/block-schema.json +++ b/dotcom-rendering/src/model/block-schema.json @@ -1387,9 +1387,6 @@ }, "atomId": { "type": "string" - }, - "caption": { - "type": "string" } }, "required": [ @@ -2349,6 +2346,9 @@ }, "role": { "$ref": "#/definitions/RoleType" + }, + "caption": { + "type": "string" } }, "required": [ diff --git a/dotcom-rendering/src/types/content.ts b/dotcom-rendering/src/types/content.ts index 6c2df1a7416..67976797d9e 100644 --- a/dotcom-rendering/src/types/content.ts +++ b/dotcom-rendering/src/types/content.ts @@ -213,7 +213,6 @@ export interface ContentAtomBlockElement { _type: 'model.dotcomrendering.pageElements.ContentAtomBlockElement'; elementId: string; atomId: string; - caption?: string; } interface DisclaimerBlockElement { @@ -474,6 +473,7 @@ export interface MediaAtomBlockElement { duration?: number; videoPlayerFormat?: VideoPlayerFormat; role?: RoleType; + caption?: string; } export interface MultiImageBlockElement { From 3ca61fa3f589f209a5e70cad28e80c3d57476074 Mon Sep 17 00:00:00 2001 From: Simon Byford Date: Tue, 19 May 2026 09:49:00 +0100 Subject: [PATCH 09/11] Remove dev notes --- dotcom-rendering/src/components/Caption.tsx | 1 - .../src/components/SelfHostedVideo.island.tsx | 2 -- .../src/components/SelfHostedVideoPlayer.tsx | 2 -- dotcom-rendering/src/lib/renderElement.tsx | 8 -------- dotcom-rendering/src/lib/video.ts | 2 -- dotcom-rendering/src/types/content.ts | 10 ---------- dotcom-rendering/src/types/mainMedia.ts | 2 -- 7 files changed, 27 deletions(-) diff --git a/dotcom-rendering/src/components/Caption.tsx b/dotcom-rendering/src/components/Caption.tsx index 590c65f183a..536aea4105e 100644 --- a/dotcom-rendering/src/components/Caption.tsx +++ b/dotcom-rendering/src/components/Caption.tsx @@ -1,4 +1,3 @@ -// DEV NOTE: at this point, we should be supplying the component with `captionText` - decisions about what that string should be should have taken place before this point import { css } from '@emotion/react'; import { between, diff --git a/dotcom-rendering/src/components/SelfHostedVideo.island.tsx b/dotcom-rendering/src/components/SelfHostedVideo.island.tsx index 00c58ac4620..e66964e182c 100644 --- a/dotcom-rendering/src/components/SelfHostedVideo.island.tsx +++ b/dotcom-rendering/src/components/SelfHostedVideo.island.tsx @@ -1,5 +1,3 @@ -// DEV NOTE: this component takes a `caption` attribute - the text of the caption should be settled upstream so we don't need to do any additional work here. - import { css } from '@emotion/react'; import { isUndefined, log, storage } from '@guardian/libs'; import { from, space, until } from '@guardian/source/foundations'; diff --git a/dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx b/dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx index 4d718d39f3f..0727c28f3fd 100644 --- a/dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx +++ b/dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx @@ -1,5 +1,3 @@ -// DEV NOTE: by this point, captions text should be settled - this component doesn't care about it - import { css } from '@emotion/react'; import { space, diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx index e07373b6029..ba22bc5dd8e 100644 --- a/dotcom-rendering/src/lib/renderElement.tsx +++ b/dotcom-rendering/src/lib/renderElement.tsx @@ -372,7 +372,6 @@ export const renderElement = ({ ); case 'model.dotcomrendering.pageElements.GuVideoBlockElement': - // DEV NOTE: makes use of `credit` and `caption` attribute data return ( ); } else { - // DEV NOTE: It's at this point where `title` attribute content gets set to `caption` attribute - // - add in a check to override title with our new caption at this point - // - but we need to make sure that the `caption` attribute gets fed down the chain to this point return ( ; case 'model.dotcomrendering.pageElements.VideoFacebookBlockElement': - // DEV NOTE: ignoring FaceBook video for now return ( ); case 'model.dotcomrendering.pageElements.VideoVimeoBlockElement': - // DEV NOTE: ignoring Vimeo video for now return ( Date: Tue, 19 May 2026 11:33:02 +0100 Subject: [PATCH 10/11] Add support in YoutubeBlockComponent --- dotcom-rendering/src/frontend/schemas/feArticle.json | 3 +++ dotcom-rendering/src/lib/renderElement.tsx | 2 +- dotcom-rendering/src/model/block-schema.json | 3 +++ dotcom-rendering/src/types/content.ts | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json index 09e486ed9d2..835e5afc646 100644 --- a/dotcom-rendering/src/frontend/schemas/feArticle.json +++ b/dotcom-rendering/src/frontend/schemas/feArticle.json @@ -4069,6 +4069,9 @@ }, "role": { "$ref": "#/definitions/RoleType" + }, + "caption": { + "type": "string" } }, "required": [ diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx index ba22bc5dd8e..31ae9aa8198 100644 --- a/dotcom-rendering/src/lib/renderElement.tsx +++ b/dotcom-rendering/src/lib/renderElement.tsx @@ -953,7 +953,7 @@ export const renderElement = ({ getLargestImageSize(element.posterImage ?? [])?.url } duration={element.duration} - mediaTitle={element.mediaTitle} + mediaTitle={element.caption ?? element.mediaTitle} altText={element.altText} origin={host} stickyVideos={!!(isBlog && switches.stickyVideos)} diff --git a/dotcom-rendering/src/model/block-schema.json b/dotcom-rendering/src/model/block-schema.json index 918da7ac58b..ff206916ea2 100644 --- a/dotcom-rendering/src/model/block-schema.json +++ b/dotcom-rendering/src/model/block-schema.json @@ -3542,6 +3542,9 @@ }, "role": { "$ref": "#/definitions/RoleType" + }, + "caption": { + "type": "string" } }, "required": [ diff --git a/dotcom-rendering/src/types/content.ts b/dotcom-rendering/src/types/content.ts index 9b949bc851e..dccae6e7676 100644 --- a/dotcom-rendering/src/types/content.ts +++ b/dotcom-rendering/src/types/content.ts @@ -753,6 +753,7 @@ export interface YoutubeBlockElement { overrideImage?: string; altText?: string; role?: RoleType; + caption?: string; } interface WitnessTypeDataBase { From fb9c8724c9bfa100e0adf406c85862b432ec62c5 Mon Sep 17 00:00:00 2001 From: Simon Byford Date: Wed, 20 May 2026 14:38:04 +0100 Subject: [PATCH 11/11] Add bottom margin to video elements --- .../components/SelfHostedVideoInArticle.tsx | 63 +++++++++++-------- dotcom-rendering/src/components/VideoAtom.tsx | 10 ++- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx b/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx index 3d312d43f3b..c640fedf653 100644 --- a/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx +++ b/dotcom-rendering/src/components/SelfHostedVideoInArticle.tsx @@ -1,3 +1,5 @@ +import { css } from '@emotion/react'; +import { space } from '@guardian/source/foundations'; import type { FEAspectRatio } from '../frontend/feFront'; import type { ArticleFormat } from '../lib/articleFormat'; import { @@ -10,6 +12,10 @@ import type { VideoPlayerFormat } from '../types/mainMedia'; import { Island } from './Island'; import { SelfHostedVideo } from './SelfHostedVideo.island'; +const containerStyles = css` + margin-bottom: ${space[3]}px; +`; + type SelfHostedVideoInArticleProps = { element: MediaAtomBlockElement; format: ArticleFormat; @@ -37,31 +43,36 @@ export const SelfHostedVideoInArticle = ({ } return ( - - - +
+ + + +
); }; diff --git a/dotcom-rendering/src/components/VideoAtom.tsx b/dotcom-rendering/src/components/VideoAtom.tsx index c899ea6053d..41a9ca837ff 100644 --- a/dotcom-rendering/src/components/VideoAtom.tsx +++ b/dotcom-rendering/src/components/VideoAtom.tsx @@ -1,7 +1,13 @@ +import { css } from '@emotion/react'; +import { space } from '@guardian/source/foundations'; import type { ArticleFormat } from '../lib/articleFormat'; import { Caption } from './Caption'; import { MaintainAspectRatio } from './MaintainAspectRatio'; +const figureStyles = css` + margin-bottom: ${space[3]}px; +`; + type AssetType = { url: string; mimeType?: string; @@ -30,7 +36,7 @@ export const VideoAtom = ({ return null; } // Handle empty assets array return ( - <> +
)} - +
); };