v1 ship: YouTube playlist embed family functional
Started the day fixing how a single-line YouTube Short renders inside a memo's prose flow. Ended it with a build-time-fetched playlist UI that escape-positions itself in the article's right margin, with a 1,899-item scroller wired to the iframe via the YouTube IFrame Player API. Six commits, three changelogs, two specs touched, one new cross-cutting `MobileFeaturePlaceholder` pattern.
Changelog - 2026-05-07 (#3)
v1 ship: YouTube playlist embed family functional
Why Care?
The Lossless Flavored Markdown video-component family is the first part of LFM that crossed from "auto-unfurl ships you a working iframe" to "auto-unfurl ships you a thoughtful, theme-aware, design-system-aligned UI surface." The playlist case was the proving ground because it's the hardest one — a single-video embed has one rendering decision; a playlist has a dozen (item list, sort, click-to-play, lazy-load, mobile fallback, vertical alignment, scroll affordances, ...). All of those are now wired and working in v1.
This entry exists mostly to capture the full day's session arc as a single referenceable table — the work split across two changelog entries (_01 for the first-pass playlist landing, _02 for the cross-cutting aside-escape pattern) plus this third one for the ship close-out. Six commits in the site repo, four in the parent.
Today's session arc, in commits
Site repo (sites/mpstaton-site on development branch)
| Commit | Message | What |
36e3d50 | chore(lfm-dev, content-schema): switch lfm:local to relative link path, relax frontmatter schema | Made local lfm dev work without forcing the site into the astro-knots workspace. Relaxed augmented_with schema to accept lists. |
143ea6b | feat(shorts, lfm-directives): float Shorts as aside, wire :::youtube-short with versatile dispatch | Shorts default to <aside> with float: right; :::youtube-short directive accepts id attr, url attr, or a bare URL pasted in the body. |
fbc1d46 | progress(playlist, mobile-sidebar, svelte): dynamic YouTube playlist UI with sidebar list and click-to-play | First-pass playlist UI: build-time Data API fetcher, Astro shell + Svelte island, cross-cutting MobileFeaturePlaceholder, postMessage to iframe via the YouTube IFrame Player API. Svelte integration added to the site for the first time. |
12fb61e | progress(playlist): sort by addedAt desc, tighten margins, first attempt at right-escape | Captured snippet.publishedAt per item; sorted by recency desc; tightened margins. First attempt at the right-escape grid (later refactored). |
5a2ad2c | almost(playlists): almost there with Youtube playlist rendering in Markdown component pipeline | Refactored to the two-sibling architecture: YouTubePlaylistPlayer (in-column card) + YouTubePlaylistScroller (escape-positioned aside) + thin orchestrator. Player and scroller communicate via shared iframeId only. |
7b30ba3 | ship(playlists, ux): youtube playlist embeds now v1 functional | Wheel/touchpad scroll fix (root-caused to a broken height chain through MobileFeaturePlaceholder's display: contents). Vertical centering of the scroller against the player. Sticky "Up 5 / Down 5" scroll buttons with edge-disable. |
Parent repo (astro-knots on master)
| Commit | Message | What |
0e6dc0c | doc(lfm-spec, video-spec): extract Versatile-Component-Library-for-Video-Players, codify S→T→C | Extracted the video component family from the LFM spec into its own document. Codified the S→T→C model (Syntax → Trigger → Component) as foundational §3 of the new spec. |
7921837 | changelog(aside-escape): document the pattern + bump submodule for the playlist UI tweak pass | Published the aside-escape pattern changelog. |
474b967 | almost(playlists, video-spec): split playlist into orchestrator + sibling components, sync spec | Spec sync: §4.1.3.3 rewritten as "two sibling elements, not one card"; new §4.1.3.5 codifies the aside-escape CSS recipe; questions 1–2 in §7 marked resolved. |
56dc175 | ship(playlists): bump mpstaton-site submodule for v1 playlist embed UX close-out | Submodule bump for the v1 ship. |
What this v1 ships
A bare youtube.com/playlist?list=... URL on its own line in any markdown file — or a :::youtube-playlist container directive with the URL pasted inside, or ::youtube-playlist{id="..."} as a leaf directive — auto-unfurls into:
A player card that flows in the article reading column at the column's natural width, with a header strip carrying playlist title, channel, item count, and an "Open on YouTube" CTA.
A scroller aside that escape-positions itself in the article's right margin track at ≥1280px viewports (or stacks below at 768–1279px, or surfaces as a full-screen panel below 768px).
Build-time data acquisition via the YouTube Data API v3 — playlist metadata, paginated item list, batched per-item durations — cached at
src/data/youtube-playlist-cache.json(gitignored) with 30-day TTL. ~3 quota units per playlist on cold cache; free tier covers hundreds of playlists per build.Sort by
addedAtdescending so the most-recently-added video sits on top, regardless of how the playlist owner has the list ordered on YouTube.Click-to-play via a small Svelte island that hydrates with
client:visible, finds the iframe by ID, and usesplayVideoAt(N)through the YouTube IFrame Player API.Wheel and touchpad scroll that actually works inside the scroller (today's bug fix), themed with a thin brand-aqua scrollbar.
Sticky "Up 5 / Down 5" buttons at the top and bottom of the scroll viewport, auto-disabled at scroll edges.
Active-item highlight (brand-aqua left border) that updates as the iframe advances.
Vertical centering of the scroller against the player, so when the scroller is taller than the iframe it extends equally above and below.
Two cross-cutting patterns established, available to the next sidebar component
MobileFeaturePlaceholder.astro— the wrapper that lets any sidebar-positioned LFM component degrade gracefully on phones to a placeholder + full-screen panel. Spec §5.9. First consumer is the playlist scroller; future consumers (LinkPreview__Article--Cardwithaside="right",LinkRollup__Column) inherit the contract.The
aside-escapeCSS recipe —position: absolute+left: calc(100% + gap)+width: min(maxw, calc(((100vw - 100%) / 2) - 2rem))against aposition: relativewrapper, lets a child element pop out of a centered article column into the right margin track. Spec §4.1.3.5. The orchestrator pattern (one wrapper composing in-column + escape-positioned siblings) is the reusable shape;MarginEscape.astroextraction waits for a second consumer per the "premature extraction is worse than waiting one round" principle.
Next session
Three forks ahead, pick whichever feels most useful:
Phase 1b — facade mode for
YouTubeEmbed,YouTubeShortsEmbed,VimeoEmbed. Reduces first-paint cost on memo pages with multiple embeds. Spec §6.MarginEscape.astroextraction when a second consumer (likelyLinkPreview__Article--Cardwithaside="right-escape") comes online.Walk the playlist mobile path on a real phone. Implementation is in place; the swipe thresholds and back-button integration deserve a touch-hardware verification pass.
