Skip to content

feat: add YouTube chapter markers to community feed - #880

Open
trieloff wants to merge 6 commits into
mainfrom
youtube-chapter-markers
Open

feat: add YouTube chapter markers to community feed#880
trieloff wants to merge 6 commits into
mainfrom
youtube-chapter-markers

Conversation

@trieloff

@trieloff trieloff commented Aug 26, 2025

Copy link
Copy Markdown
Contributor

Summary

Adds YouTube chapter markers to the community feed: a scheduled workflow pulls chapter lists out of video descriptions and writes them into the feed sheet, and the feed block renders them as a collapsible list of deep links under each recording card.

Test Plan

Chapters will not appear on either link until the workflow has run once with credentials — the Chapters column does not exist in the sheet yet. See "Before this does anything" below. Rendering was verified locally against the real feed data with the column populated by the sync script.

What changed since the first version of this PR

The original approach parsed timestamps out of page.Description inside renderFeed. Neither half of that works against the site as it stands today:

  • renderFeed is not what /community uses. The page moved to the feed compact categorized variant, which renders through renderFeedCompact. Across all 226 pages in the sitemap, renderFeed only runs on /blog/archive's "favorite blogs" block.
  • The descriptions in the sheet never contain timestamps. Zero of the 116 recordings have a chapter list in their Description cell. Only 4 have one on YouTube itself, and the sheet's descriptions are abridged copies that drop the trailing "Topics" block.

So the feature now gets its data from a new Chapters column, populated from YouTube server-side.

Sync workflow

.github/workflows/youtube-chapters.yml runs tools/youtube-chapters/sync-chapters.js daily. Each run reads /community-feeds.json from the source bus, looks the youtube sheet's video ids up against the YouTube Data API (3 batched calls, 3 of 10,000 daily quota units), extracts chapters, writes the sheet back, and previews and publishes it.

Everything on the AEM side — source read, source write, preview, publish — goes through the Helix 6 admin API at https://api.aem.live, so one admin API key covers the whole run.

A run of timestamped lines is only accepted as a chapter list when it opens the video at 0:00, runs strictly forwards, and has at least three entries — close to what YouTube itself requires. Run against all 116 real descriptions, it finds exactly the four videos that have chapter lists and produces no false positives.

Runs are idempotent: nothing is written when no chapter list changed. Rows YouTube does not answer for (deleted or private videos) keep whatever an author typed by hand.

Feed block

Chapters render as a collapsed N chapters disclosure below each card, so the four-column grid stays compact until someone opens one. The card's own anchor cannot contain the chapter links, so the card chrome moved to a wrapper element and the anchor now holds only the thumbnail and title.

The column is also editable by hand in DA — one MM:SS Title line per chapter — and a manual edit survives until that video's own description gains a chapter list.

Before this does anything

Two repository secrets are needed; tools/youtube-chapters/README.md has the full walkthrough.

Secret Status
AEM_LIVE_ADMIN_TOKEN done — rotated to a fresh key for adobe/aem-website, scoped publish + author, valid until 2027-08-31
YOUTUBE_API_KEY done — "Helix YouTube Chapters" key in the helix-225321 GCP project, restricted to youtube.googleapis.com

Both are set. Once this merges, run the workflow manually with dry run checked to see what it would write, then again without it. (It cannot be dispatched before merge — workflow_dispatch needs the workflow file on the default branch.)

The admin key has been checked against every route this workflow uses — GET and PUT on
source, POST on preview and live — all pass. It deliberately cannot write configuration
(403 on any config/ path), so it cannot widen its own access.

Verification

  • npm run lint and npm test pass (35 tests, including 9 new ones for the chapter parser).

  • Parser checked against all 116 real video descriptions: 4 chapter lists found, 0 false positives.

  • Sync flow exercised end to end against stubbed admin API and YouTube endpoints: correct call sequence (GET/PUT sourcePOST previewPOST live), correct multi-sheet round-trip, other sheets untouched, :colWidths padded, second run a no-op.

  • Dry run against the production source bus, YouTube stubbed: read the real sheet from api.aem.live (116 rows, :type: multi-sheet, :colWidths intact), found 4 chaptered videos, stopped before writing.

  • Dry run against the real YouTube Data API, source stubbed from that same captured sheet: 3 batched videos.list calls, same 4 videos, same chapter counts. The cells it would write, verbatim:

    0:00 Introduction\n2:39 Start Presentation\n49:24 Q&A\n57:29 Upcoming Sessions
    0:00 Introduction\n11:18 Start Demo\n38:20 Extended Q&A\n45:12 Developers Live 2025\n48:35 Upcoming Sessions
    
  • The site's configured content source is {"type":"markup","url":"https://api.aem.live/adobe/sites/aem-website/source"} — the source bus is the content source, so this is the correct write path and not merely a convenient one.

  • Rendering checked in Chrome at 1280/800/380px, light and dark, with keyboard toggling and no nested anchors.

Known, pre-existing, not fixed here

The compact card's background is light-dark(var(--color-white), var(--color-gray-900)), and --color-gray-900 is itself a light-dark() pair, so cards come out near-white in dark mode. Its hover rule also references --color-brand-purple and --color-accent-purple, neither of which is defined. Both are on main and are left alone here; the new chapter styles follow the card's existing text treatment so a single fix would correct them together.

🤖 Generated with Claude Code

@aem-code-sync

aem-code-sync Bot commented Aug 26, 2025

Copy link
Copy Markdown

Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
In case there are problems, just click a checkbox below to rerun the respective action.

  • Re-run all PSI checks
  • Re-run failed PSI checks
  • Re-sync branch
Commits

@aem-code-sync

aem-code-sync Bot commented Aug 26, 2025

Copy link
Copy Markdown
Page Scores Audits Google
📱 /community PERFORMANCE A11Y SEO BEST PRACTICES SI FCP LCP TBT CLS PSI
🖥️ /community PERFORMANCE A11Y SEO BEST PRACTICES SI FCP LCP TBT CLS PSI
📱 /community PERFORMANCE A11Y SEO BEST PRACTICES SI FCP LCP TBT CLS PSI
🖥️ /community Timeout Exceeded PSI

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds automatic detection and display of YouTube chapter markers from video descriptions in the community feed. When a video description contains timestamps, the system now parses them and creates clickable chapter links that navigate to specific moments in the video.

  • Parses timestamp formats (00:00, 1:23, 1:23:45) from video descriptions
  • Creates interactive chapter UI with deep links to specific video moments
  • Adds responsive styling for chapter markers that integrates with existing feed design

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
blocks/feed/feed.js Adds timestamp parsing logic and chapter marker creation functionality
blocks/feed/feed.css Adds responsive styling for chapter markers with hover states and typography

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread blocks/feed/feed.js Outdated
Comment thread blocks/feed/feed.js Outdated
Comment on lines +25 to +37
let match = timestampRegex.exec(description);

while (match !== null) {
const [, time, title] = match;
const seconds = timeToSeconds(time);
chapters.push({
timestamp: time,
title: title.trim(),
seconds,
url: `${videoUrl}&t=${seconds}s`,
});
match = timestampRegex.exec(description);
}

Copilot AI Sep 9, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using exec() with a global regex in a while loop can create an infinite loop if the regex continues to match the same position. Consider using matchAll() or resetting the regex's lastIndex property, or use a non-global regex with match() method instead.

Copilot uses AI. Check for mistakes.
claude and others added 4 commits August 31, 2026 09:50
Automatically detect and display chapter timestamps from YouTube video descriptions,
creating deep links to specific moments in videos. Each chapter shows its
timestamp and title, making it easy for users to navigate to specific topics.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Signed-off-by: Lars Trieloff <lars@trieloff.net>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The community page moved to the `feed compact categorized` variant, so the
chapter markers added to renderFeed never reached it. Move them onto the
compact cards instead, as a collapsed disclosure below each card so the
four-column grid stays compact until someone asks for the chapter list.

The card's own anchor cannot contain the chapter links, so the card chrome
moves to a wrapper element and the anchor keeps only the thumbnail and title.

Chapters now come from the sheet's Chapters column rather than from the
description: the descriptions authors write into Document Authoring are
abridged and never carry the "Topics" block, so parsing them found nothing
on any of the 116 recordings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Lars Trieloff <lars@trieloff.net>
Adds a daily workflow that reads the community feed sheet from Document
Authoring, looks its videos up against the YouTube Data API, extracts the
chapter list from each description, and writes it back as a Chapters column
before previewing and publishing the sheet. The feed block then renders
chapters without calling YouTube at runtime.

A run of timestamped lines is only accepted as chapters when it opens the
video at 0:00, runs strictly forwards, and has at least three entries, which
is close to what YouTube itself requires. Checked against all 116 recordings
in the feed: it finds the four that have chapter lists and nothing else.

Runs are idempotent - nothing is written when no chapter list changed - and
rows YouTube does not answer for keep whatever an author typed by hand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Lars Trieloff <lars@trieloff.net>
Reading and writing the feed sheet, previewing and publishing now all go
through https://api.aem.live, whose source bus exposes the same document that
admin.da.live did. That drops the separate DA_TOKEN secret and the Adobe
Developer Console OAuth server-to-server credential behind it - the admin API
key that Track Publishes already uses covers the whole run.

Setup is down to two secrets: AEM_LIVE_ADMIN_TOKEN and YOUTUBE_API_KEY.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Lars Trieloff <lars@trieloff.net>
A run wrote an empty Chapters cell for any video whose description has no
Topics block - which is all but four of the 116 recordings. That is fine
while the column is only ever filled from YouTube, but it makes the sync
destructive the moment chapters arrive from anywhere else: the first run
after a transcript-derived backfill would wipe every one of them.

A ChaptersSource column now records where each cell came from - youtube,
generated or manual - and a run only replaces cells it owns. An author's own
Topics block still wins over anything else, since it is the most direct
statement of intent, but a description that is merely silent is not an
instruction to delete someone else's work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Lars Trieloff <lars@trieloff.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants