Bare URL auto-unfurl — paste a YouTube link on its own line and it embeds
Paste a YouTube link on a line of its own and it becomes a player. Shorts get a vertical frame, playlists get a playlist player, and a link dropped mid-sentence is left alone as an ordinary link.
What's new
Any markdown file rendered through @lossless-group/lfm now detects bare URLs — a URL that occupies an entire paragraph by itself, with blank lines above and below — and replaces them with the right embedded player. YouTube videos, Shorts, and Playlists each route to a dedicated component with theme parity.
Authoring is just:
content line content line
https://youtu.be/jCe2wg1ulus?si=oplqTdsbv8sv2JfH
content line content line→ renders as a 16:9 embedded YouTube player. Shorts URLs become a vertical 9:16 player; playlists become a 16:9 playlist player. Inline links (e.g. Check this out https://youtu.be/... mid-sentence) stay as plain autolinks — the detector requires the paragraph to have exactly one child that's a link whose visible text equals its URL.
flowchart LR
A[Bare URL paragraph] --> B[getBareLinkUrl]
B -- single link autolink --> C[classifyBareLink]
B -- mixed children --> Z[autolink — no unfurl]
C -- youtube.com/watch / youtu.be --> D[YouTubeEmbed 16:9]
C -- youtube.com/shorts --> E[YouTubeShortsEmbed 9:16]
C -- youtube.com/playlist --> F[YouTubePlaylistEmbed 16:9]
C -- no provider match --> ZClassifier
src/lib/markdown/classify-bare-link.ts— pure classifier with two exports:getBareLinkUrl(node)— returns the URL if the MDAST paragraph is the bare-URL shape (single autolink child), elsenull. CommonMark's paragraph rule does the blank-line gating for free.classifyBareLink(url)— runs the URL through ordered matchers (Shorts before Playlist before Watch), returns{ provider, component, kind, id, url }on first hit,nullon no match.
Matchers are the runtime mirror of the canonical catalog at
packages/lfm/src/plugins/Bare-Link-Provider-Catalog.md— when the LFMremark-bare-linkplugin lands, this site-local file is replaced by classification at parse time.
Components
Three components copied from lossless-monorepo/site/src/components/markdown/ and re-themed to mpstaton-site's semantic tokens:
src/components/markdown/YouTubeEmbed.astro— 16:9 horizontal player. Capturesyoutu.be/{id}andyoutube.com/watch?v={id}shapes (the existing internal Shorts branch is retained for callers that pass a Shorts URL directly, but bare-link classification routes those to the dedicated component).src/components/markdown/YouTubeShortsEmbed.astro— centered 9:16 player, max-width 320, polished caption block, bordered with the brand-aqua accent.src/components/markdown/YouTubePlaylistEmbed.astro— 16:9 playlist player. Thelistquery parameter is the playlist ID; individualvparameters are ignored at classification time.
All three preserve the original caption prop and copy-URL button. The button now uses the same aqua-bordered hover treatment shared with the Mermaid expand/close buttons. var(--card) / var(--border) / var(--foreground) with sensible hex fallbacks replaced the original rgba(255, 255, 255, 0.05) baked-in surfaces.
Renderer dispatch
src/components/markdown/AstroMarkdown.astro— thetype === "paragraph"branch now first asksgetBareLinkUrl(node)thenclassifyBareLink(url)and dispatches to the matched component before falling through to a normal<p>. Single dispatch, three component arms.
Catalog (canonical, lives in LFM)
packages/lfm/src/plugins/Bare-Link-Provider-Catalog.md(in the parent astro-knots repo) — the source of truth for which URLs the bare-link plugin recognizes. Frontmatter is the record (provider entries with matchers, named-capture regexes, target component, kind, aspect ratio); the body explains how the parser reads it and how to extend it. Three providersstabletoday (YouTube watch / short / playlist), fourplanned(Vimeo, Loom, Spotify, SoundCloud).Spec section §4.12 of
Codifying-a-Comprehensive-Extended-Markdown-Flavor-and-Shared-Package.mdupdated: precise platform table now points at the actual existing component names, both Tier-A (bare) and Tier-B (explicit:::directive) share a single directive name per provider so renderers register one dispatch arm per provider not two, and the boundary between bare-URL embedded players and inlineLinkPreview__*--Row/Card/Thumbpreviews is called out explicitly so the next implementer doesn't conflate them.
Test content
src/content/promote/_demo/memo/v1.md— added a barehttps://youtu.be/jCe2wg1ulus?si=...URL between the intro and the## Why nowheading. Reachable at/promote/_demo/memo/version-1for end-to-end verification of the classification + dispatch + theme parity.
Why this lives in render-time today (and not in LFM yet)
Same motion as Mermaid: develop the pattern in mpstaton-site first, prove it on real content, then port the canonical version to packages/lfm/src/plugins/remark-bare-link.ts and packages/lfm-astro/components/. The catalog markdown file already lives in packages/lfm/src/plugins/, so the plugin work is "wire the parser to consume the catalog the renderer already mirrors."
Pre-existing astro check noise
pnpm astro check reports 8 type errors on this branch — all pre-existing, none introduced by this work. Documented in astro-knots/context-v/issue-resolution/Astro-Check-Type-Errors-on-mpstaton-site.md with a recommended fix order. The build (pnpm build) passes cleanly; Vercel deploys are unaffected.
