l-youtube-guide-writer
Orchestrate YouTube-based guide article writing. Downloads video, extracts frames, fetches transcript, optionally references product manual, and writes MDX guide article. Use when: (1) User wants to c...
File Structure
l-youtube-guide-writer/
├── SKILL.md
└── references/
└── workflow-guide.md
YouTube Guide Writer
Orchestrate the full workflow for creating a guide article from a YouTube video source.
Overview
Before starting, read .claude/skills/l-youtube-guide-writer/references/workflow-guide.md
(relative to project root) for detailed conventions and examples.
Also read doc/src/content/docs/writing/guide-article-writing.md in the project for
canonical article structure, naming conventions, and best practices.
Draft Placement Decision (series & base branch)
Before anything else, decide which series the new draft joins and which base branch it sits on. Apply this precedence top-to-bottom — stop at the first match:
- Existing series continuation — search
src/data/guide-series.mjs,main, and allorigin/base/wip-articles*branches for a guide series already covering the target product. If one exists, add the new article as its next-ep{N+1}. Updateguide-series.mjsif the entry is missing the new slug. Never start a parallel series for the same product. - User-specified series — if the user passes
--series=<slug>(or names one in free-text), add to that series even when rule 1 would pick differently. - Tiny-module single article — for a one-off short video with no existing
series (e.g., a 2-minute brand overview of a small utility module), draft a
standalone
{product}-guide.mdx(no episode number) and park it directly onbase/wip-articles. Do not create a themed sub-base for a single tiny article — sub-bases are for epic-scale work only. - Multi-article epic / porting — for ~5+ articles, bulk modernization of stale
drafts, or porting from an old branch, open a themed sub-base
base/wip-articles-<theme>and run it as an epic (e.g., via/x-wt-teams).
Before creating any branch or file, run these quick checks:
# Is there already a guide series for this product?
grep -E "slug:.*<product-slug>" src/data/guide-series.mjs
# Are there already draft articles for this product anywhere (main + WIP branches)?
git log --all --oneline --diff-filter=A -- "src/mdx/guides/<product>-*" | head -20
ls src/mdx/guides/ | grep "^<product>-"
If either check finds existing work, default to rule 1 (continue the series) and ask the user before deviating. Mis-placement is cheap to fix later but annoying; continuing an existing series cleanly is always the right starting move.
Canonical rule source: “Placement rules” in the repo-root CLAUDE.md and “Placement
Decision” in doc/src/content/docs/writing/guide-article-writing.md.
Step 1: Parse Arguments and Determine Article Type
Parse the YouTube URL from the argument. Determine the article type from --type:
video-guide(default): Article follows the video timeline. Sections mirror the video’s natural structure. Best when the video IS the content.product-explanation: Manual-based guide enriched with video captures. Feature-by- feature structure based on the product manual, with video frames as visual evidence. Best when a product manual exists and the video demonstrates features.
Extract optional flags:
--product=<slug>: Product slug for finding manual in zmanuals--series=<slug>: Guide series slug forguide-series.mjsregistration--episode=<N>: Episode number (determines file naming and frontmatter)
Print the parsed configuration and confirm with the user before proceeding.
Step 2: Download Full Video and Capture Frames
MANDATORY: Always download the full video and run 2-second interval captures. The captures serve as a visual index of the entire video — they are the primary image source for the article. Never skip this step.
Use the yt-tools sub-package:
cd sub-packages/yt-tools
pnpm download <youtube-url>
This creates ~/youtube-dl/{date}-{videoId}-{slug}/ with:
video/— downloaded video file (REQUIRED — not just transcript)metadata/— video metadata JSONtranscript/— transcript JSON with timestamps
Then always run auto-capture at 2-second intervals:
pnpm capture:auto <video-id> --interval=2
This populates ~/youtube-dl/{dir}/captures/ with timestamped frame images
(e.g., capture-00-00-00.jpg, capture-00-00-02.jpg, … capture-00-10-42.jpg).
A 10-minute video produces ~300 frames — this is expected and necessary.
Copy captures to image-stash and commit
After captures are generated, copy them into the image-stash repo and commit/push
so they are available for <YouTubeCaptureWip> components in the dev server.
image-stash is a symlink to $HOME/repos/zp/zmod-image-stash (a separate git repo).
# Copy captures and transcript
VIDEO_DIR="<date>-<videoId>-<slug>"
mkdir -p "$HOME/repos/zp/zmod-image-stash/$VIDEO_DIR/captures"
cp ~/youtube-dl/$VIDEO_DIR/captures/*.jpg "$HOME/repos/zp/zmod-image-stash/$VIDEO_DIR/captures/"
# Also copy transcript (needed for caption timeline in gallery dialog)
if [ -f ~/youtube-dl/$VIDEO_DIR/transcript/transcript.json ]; then
mkdir -p "$HOME/repos/zp/zmod-image-stash/$VIDEO_DIR/transcript"
cp ~/youtube-dl/$VIDEO_DIR/transcript/transcript.json "$HOME/repos/zp/zmod-image-stash/$VIDEO_DIR/transcript/"
fi
# Commit and push in image-stash repo
cd "$HOME/repos/zp/zmod-image-stash"
git pull origin main
git add "$VIDEO_DIR"
git commit -m "Add captures for $VIDEO_DIR"
git push origin main
cd - # return to previous directory
This step is mandatory. Without it, <YouTubeCaptureWip> components will show
broken images. The image-stash repo is independent of the main repo — commits here
do not affect any branch in the main repo.
If yt-tools is not available
Fall back to manual steps — but the full video download and captures are still mandatory:
- Use
yt-dlpdirectly to download the video:yt-dlp -o "~/youtube-dl/%(upload_date)s-%(id)s-%(title)s/video/%(title)s.%(ext)s" "<url>" - Use the
/youtube-text-fetchskill to get the transcript - Use
ffmpegto capture frames at 2-second intervals:ffmpeg -i video.mp4 -vf "fps=0.5" -q:v 2 "captures/capture-%04d.jpg"
Step 3: Fetch Transcript
Read the transcript from ~/youtube-dl/{dir}/transcript/transcript.json.
If the yt-tools transcript fetch failed or the file is missing, fall back to
youtube-transcript-api:
python3 -c "
from youtube_transcript_api import YouTubeTranscriptApi
snippets = YouTubeTranscriptApi().fetch('VIDEO_ID')
for s in snippets:
print(f'{s.start:.1f}\t{s.text}')
"
Important: Always preserve timestamps — they are needed for YouTube embed timestamp links and for mapping transcript sections to captured frames.
Step 4: Find Product Manual (if applicable)
If --product is specified, look for the product manual in the zmanuals repo:
# Check if the manual exists
ls ~/repos/zp/zmanuals/public/{product-slug}/data/
Manual content is stored at:
- Translated JSON (JA):
~/repos/zp/zmanuals/public/{product-slug}/data/pages-ja.json - Translated JSON (EN):
~/repos/zp/zmanuals/public/{product-slug}/data/pages-en.json - Manifest:
~/repos/zp/zmanuals/public/{product-slug}/data/manifest.json - Page images:
~/repos/zp/zmanuals/public/{product-slug}/pages/page-NNN.png - Source PDF:
~/repos/zp/zmanuals/manual-pdf/{product-slug}/
Read pages-ja.json for the Japanese translated manual content. The manual provides
accurate technical details (parameter ranges, signal specs, operational procedures)
that should inform the article’s accuracy.
Use the /refer-another-project zmanuals pattern — safe to read and reference
structures, but restructure into original prose. Never copy content verbatim.
If no manual is found, proceed without it and note this to the user.
Step 5: Plan Article Structure
Present a structured outline to the user for approval before writing.
For video-guide type
- Read the full transcript with timestamps
- Identify natural section breaks in the video (topic changes, chapter markers)
- Plan sections that follow the video timeline
- For each section, note:
- Timestamp range
- Key topics covered
- Which captures to use (pick the clearest frame for each concept)
- Whether a YouTube embed with timestamp is useful here
- Plan the introduction and outro
For product-explanation type
- Read the product manual translation (from zmanuals)
- Plan feature-by-feature sections based on the manual structure
- For each feature section, note:
- Manual section reference
- Video timestamp(s) where this feature is demonstrated
- Which captures show this feature best
- Technical specs from the manual to include
- Plan info boxes for manual-sourced technical details
Present the outline and get user confirmation before proceeding.
Step 6: Auto-Select and Stage Images
No placeholders. No asking user to confirm. Automatically select captures by matching transcript timestamps to capture filenames, then copy them to the temp image directory for immediate use in the article.
Selection process
- Read
transcript.jsonto identify timestamps for each planned section - For each section, find the capture closest to the key timestamp:
- Transcript entry at
start: 42.5→capture-00-00-42.jpg - Transcript entry at
start: 185.0→capture-00-03-04.jpg - Convert seconds to
HH-MM-SSto match capture filenames
- Transcript entry at
- Select 10-15 captures per article (roughly 1 every 2-3 paragraphs)
- Prefer captures that show:
- The screen/panel state being discussed
- Clear UI/parameter views rather than transition frames
- If two adjacent captures exist, pick the sharper one
Use YouTubeCaptureWip component
Use the <YouTubeCaptureWip> component in MDX — it reads directly from the
image-stash directory (symlinked to $HOME/repos/zp/zmod-image-stash).
<YouTubeCaptureWip
videoDir="20260326-wONHDerXUSY-first-steps-with-oxi-one-mkii-ep-7-pattern-workflo"
capture="capture-00-00-42.jpg"
id="ep7-page-nav"
caption="ページボタンによるナビゲーション"
/>
The component shows the selected capture with debug info (filename + id), and clicking opens a gallery dialog with all 2-sec captures for that video.
For non-YouTube images
Use <WipImg> for images not from YouTube captures (diagrams, product photos,
screenshots). Place the images in image-stash/wip-images/.
<WipImg src="my-diagram.jpg" id="diagram-1" caption="回路図" />
WIP image architecture
| Component | Purpose | Source path | Production |
|---|---|---|---|
<YoutubeAt> (WIP) | Timestamp video embed with cover (Pick Cover dialog in dev) | image-stash/{videoDir}/captures/ | R2 via coverSlug prop (added by --finalize); WIP form blocked by check:capture-wip on PRs targeting main |
<YouTubeCaptureWip> | Video frame selection (gallery UI) | image-stash/{videoDir}/captures/ | Replace with <ArticleImage> |
<WipImg> | General WIP images | image-stash/wip-images/ | Replace with <ArticleImage> |
<ArticleImage> | Finalized production images | R2 CDN (via imgs/ pipeline) | Already production |
Important notes
<YouTubeCaptureWip>,<WipImg>, and WIP<YoutubeAt>(withvideoDir) are WIP-only — the CI guardcheck:capture-wipprevents them from reaching main (only enforced on PRs targeting main). Note: the WIP<YoutubeAt>guard is extended as part of the youtube-at-finalize epic; earlier guard versions only covered<YouTubeCaptureWip>and<WipImg>.<YoutubeAt>withcoverSlugis production-safe — served from R2 CDN. WithoutcoverSlug, the component falls back to a standard YouTube iframe (no custom cover)- When finalizing an article: run
/l-youtube-guide-writer --finalize <mdx-path>(Step 10) image-stashis a symlink to$HOME/repos/zp/zmod-image-stash(separate git repo)- Clone
git@github.com:zudolab/zmod-image-stash.gitto$HOME/repos/zp/zmod-image-stashfor local dev
Step 7: Write the MDX Article
Create the MDX file at src/mdx/guides/{product}-guide-ep{N}.mdx.
Frontmatter template
---
title: "Product Name Guide EP.N: Episode Title"
description: >-
EP.N of the Product Name guide series. What this episode covers.
imgThumb: product-promo-image__og
avoidListing: false
tags:
- guide
- product-tag
categories:
- product-guide-series
- guide
contentType: guides
createdAt: YYYY-MM-DD
updatedAt: null
---
Standard sections
-
Introduction — Overview, context, why this episode matters. Include a link to the series page. For guide series, do NOT include Discord invitation text (see Guide Series Conventions below).
-
<ProductDetailManualNav>(guide series only) — Place<ProductDetailManualNav slug="product-slug" />here, before the YouTube embed. Example:<ProductDetailManualNav slug="oxi-coral" /> -
YouTube embed —
<Youtube url="https://youtu.be/VIDEO_ID" />(full video, no timestamp) -
## TOC— Auto-generated table of contents -
Body sections — The main content. Each section has:
- Temp capture images every 2-3 paragraphs:
<YouTubeCaptureWip>or<img>tags during draft - Timestamp-specific video embeds using
<YoutubeAt>(see below) - Bold for button names, parameter names, mode names
- Concrete values: “Set Density to 80%-100%” not “Set Density high”
- Temp capture images every 2-3 paragraphs:
-
Practical tips — Concrete usage advice and patterns
-
Outro — Optional next-episode preview, then a mandatory h2 product detail nav section that closes the article:
Closing line voicing — never use “解説しました”: the person explaining the feature in the video is NOT Takazudo (the article author). At first glance “今回は◯◯を解説しました” reads naturally, but a careful reader notices it is wrong because Takazudo did not do the explaining. Use a third-person framing instead:
- ✅
EP.Nはここまでです。 今回は◯◯の解説でした。 - ❌
EP.Nはここまでです。 今回は◯◯を解説しました。 - ❌
EP.Nはここまでです。 今回は◯◯をひととおり解説しました。
This rule applies to ALL video-based explanation articles (any article whose body is built from a third-party YouTube video), not just guide series.
感想 / personal-commentary paragraphs — DO NOT auto-generate: paragraphs that begin with
Takazudo感想としては(or any equivalent first-person opinion block) MUST be written by Takazudo himself, never invented by AI. When drafting:- Stop the outro after the closing line above (and any factual next-episode preview).
- Do NOT write a
Takazudo感想としては …paragraph, even as a placeholder. - If you want to leave a slot for the human to fill in, leave it empty or add a
short HTML comment marker like
{/* Takazudo感想ここに */}— never invent the content.
This applies to all video-based explanation articles.
## <Product Name> 商品詳細 <Product Name>の商品詳細は以下よりご覧いただけます。 <RelatedProducts ids={[...]} />Always use an h2 heading (not an inline trailing paragraph) for the final product nav — it makes the product block a proper section, which is the standard across guide series. Reference:
oxi-one-mkii-guide-ep10, entireoxi-coral-guideseries.Keep the description line short and generic (e.g.,
OXI Coralの商品詳細は以下よりご覧いただけます。). Do not inline a[OXI Coral 商品詳細](/products/oxi-coral-intro/)link here — the h2 +<RelatedProducts>already makes the section clearly navigable. When multiple products are featured (e.g., Coral + E16), combine them in the heading:## OXI Coral / OXI E16 商品詳細.Open-ended series rule: never declare the series finished in the outro (no
最終回,final episode, etc.). See:doc/src/content/docs/writing/guide-article-writing.md#open-ended-series - ✅
YoutubeAt: Timestamp video embeds with custom covers
For timestamp-specific video references, use <YoutubeAt> instead of <Youtube>.
<YoutubeAt> shows a captured video frame at the specified timestamp as a cover image,
with a play button overlay and “MM
Use <Youtube> for: The full-video embed at the top of each article (no ?t=).
Use <YoutubeAt> for: All timestamp-specific embeds within article body sections.
WIP form (during writing)
During writing, always use the WIP form with videoDir and id — never add coverSlug:
{/* Full video at top — standard embed */}
<Youtube url="https://youtu.be/VIDEO_ID" />
{/* Timestamp references in body — WIP form */}
<YoutubeAt
url="https://youtu.be/VIDEO_ID?t=67"
videoDir="20260411-VIDEO_ID-slug"
id="epN-yt-feature-name"
/>
id is required and must be unique within the article. Convention:
ep{N}-yt-{feature-kebab}. Examples: ep9-yt-grid-overview, ep9-yt-chord-input,
ep1-yt-panel-layout.
The videoDir must match the directory name in image-stash where captures are stored.
The component auto-derives the cover image from the ?t= parameter to the nearest
2-second capture. At build time, the island wrapper pre-resolves the exact capture
filename from the capture manifest — only the resolved filename is sent to the client.
Pick Cover dialog: In pnpm dev, each WIP <YoutubeAt> shows a Pick Cover
button (requires image-stash repo cloned to $HOME/repos/zp/zmod-image-stash — this
UI is part of the youtube-at-finalize epic). Clicking it opens a gallery dialog of all
2-second captures for that video, with the transcript text displayed alongside each frame.
The human author picks a better frame than the auto-derived one. The dialog writes the
picked filename back into the MDX file via the capture attribute. Always run the Pick
Cover pass before finalizing.
SSR behavior: Before JavaScript hydration, a loading placeholder (black box with spinning indicator) is shown. After hydration, the cover image fades in (0.8s).
Counter-example: what NOT to do (EP.9 incident)
Do NOT use a placeholder coverSlug pointing to a shared image during writing:
{/* WRONG — all videos share the same thumbnail */}
<YoutubeAt url="https://youtu.be/VIDEO_ID?t=34" coverSlug="shared-promo-image__og" />
<YoutubeAt url="https://youtu.be/VIDEO_ID?t=349" coverSlug="shared-promo-image__og" />
<YoutubeAt url="https://youtu.be/VIDEO_ID?t=472" coverSlug="shared-promo-image__og" />
This is the EP.9 pre-fix state — all <YoutubeAt> embeds showed the same promo image,
making each embed look identical. Always use the WIP form (videoDir + id, no
coverSlug) so each embed shows its own auto-derived frame and can be individually
refined via the Pick Cover dialog.
Production form (after —finalize)
After finalization, <YoutubeAt> uses coverSlug for R2-served images. The --finalize
step (Step 10) handles this rewrite automatically. The slug ends with the cover frame’s
timestamp (HH-MM-SS, copied from the source capture-HH-MM-SS.jpg) so the cover time
is permanently recoverable from the filename even if ?t=N is later corrupted:
{/* Production form — after --finalize. Slug encodes 00:01:08 → ?t=68 */}
<YoutubeAt url="https://youtu.be/VIDEO_ID?t=68" coverSlug="oxi-ep9-cap-grid-overview-00-01-08" />
The ?t=N value MUST equal HH*3600 + MM*60 + SS from the slug suffix. See Step 10 for
the full rule and rationale.
Without coverSlug, the component falls back to a standard YouTube iframe (no custom cover).
Build-time requirements: After adding/changing captures in image-stash, run
pnpm build:capture-manifest to regenerate src/data/generated/capture-manifests.json.
This runs automatically as part of pnpm dev and pnpm build.
Note on long videos: For videos over 30 minutes, consider using --interval=4
(4-second captures) instead of the default 2-second interval to reduce frame count.
Video aspect ratio
Not all YouTube videos are 16
. Before using<Youtube> and <YoutubeAt> components,
check the actual frame dimensions from the captures in image-stash/{videoDir}/captures/.
- 4 (e.g., 1440×1080, 2880×2160): add
aspectRatio="4 / 3"to both<Youtube>and<YoutubeAt>components - 16 (default): no extra prop needed
{/* 4:3 video */}
<Youtube url="https://youtu.be/VIDEO_ID" aspectRatio="4 / 3" />
<YoutubeAt url="https://youtu.be/VIDEO_ID?t=67" videoDir="..." id="..." aspectRatio="4 / 3" />
Writing style: Video Writedown Voice
Use the video writedown voice — a project-specific application of the project-wide AI writing rule.
Read first (in this order):
.claude/skills/_shared/writing-voice.md— the project-wide “AI writes facts, human adds emotion” rule. This is the base rule for ALL writing skills in this repo. Every line below is consistent with it..claude/skills/l-youtube-guide-writer/references/workflow-guide.mdsection “Article Voice: Video Writedown Style” — the video-specific anti-pattern catalog (4 patterns from EP.7 A/B testing) and worked examples.
Core rule: AI writes facts, technical explanations, and operation descriptions.
Emotional commentary and personal opinions are added by the human author later.
Even tiny expressions of emotion are forbidden — see the forbidden-categories
table in _shared/writing-voice.md.
Write: Technical facts, operation steps with bold button names, contextual explanations (why/when), brief section summaries, practical tips
Don’t write: Personal feelings (感動した), importance markers (ポイントは,
面白いのは), conversational fillers (〜ですよね), dramatic emphasis (革命的),
tutorial commands (やってみましょう), interpretive flourishes
(Meng Qi氏ならでは), first-person experience claims (自分も最初はそうでした)
Additional rules:
- Restructure transcript into readable prose — never use transcript verbatim
- Bold button names, parameter names, mode names
- Reference specific values and settings from the video/manual
- Use timestamp-specific YouTube embeds for key demonstration moments
- Balance text and images — no more than 3 paragraphs without a visual element
- Follow the project’s Japanese text rules (see
src/CLAUDE.md)
Product-specific components
For OXI sequencer articles, consider using the <OxiGridFigure> component to
visualize grid states where relevant. Other products may benefit from similar
custom components — note the need in the plan but don’t create unilaterally.
Articles without transcripts
Some videos lack transcripts (e.g., ADDAC215 demo videos with no narration). For these:
- Still download the full video and run 2-sec captures — captures are essential
- Use the captures as a visual guide to understand what the video demonstrates
- Supplement with product data from
product-master-data.mjsand manuals - Structure the article around features shown in the video, using captures as evidence
- The image selection process is the same — match video segments to captures by timestamp, even without transcript text
Step 8: Register in Guide Series (if applicable)
If --series is specified, update src/data/guide-series.mjs:
- If the series already exists, append the new article slug to the
articleSlugsarray - If the series doesn’t exist, add a new series entry with all required fields:
slug,name,nameEn,description,descriptionEn,thumbSlug,articleSlugs - Confirm the series entry with the user
Step 9: Verification
- Run
pnpm devand verify the article renders at the expected URL - Check that all
<ArticleImage>slugs resolve (no missing images) - Verify YouTube embeds load correctly
- Check mobile layout using headless browser or
/verify-ui - Run
pnpm checkfor code quality
Present a summary of verification results to the user.
Writing rules: 人名表記 (naming convention)
Always use 氏 suffix when referring to people by name. No space before 氏.
- Correct:
Devin氏,Steve氏,Matthew氏 - Incorrect:
Devinさん,Devin さん,Devin 氏,Matthew(Easybot)さん
Writing rules: Avoid subjective sound descriptions
Never use 音の太さ or 太い音 — these are subjective/emotional expressions with
no clear technical meaning. Replace with factual descriptions:
- Incorrect:
音の太さや安定性に驚かされます - Correct:
アナログ回路の魅力を強く感じられるモジュールになっていることに気付かされます - Incorrect:
めちゃくちゃ太い音になる - Correct:
5つのオシレーターによる密度の濃いサウンドが得られる
Writing rules: No manual source citations in body text
Never use phrases that cite “the manual” as a source in article body text:
- Incorrect:
マニュアルによると,マニュアルを参照してください,マニュアルに記載されています - Correct: State the information as a direct fact
The intro section may link to the manual translation site as a resource. But in body text, present technical facts from the manual directly — readers don’t need to know where the information came from.
- Incorrect:
マニュアルによると、Densityパラメーターは0〜127の範囲で設定できます - Correct:
**Density**パラメーターは0〜127の範囲で設定できる
Guide Series Conventions
When writing articles that are part of a guide series (i.e., --series is specified),
follow these additional rules:
No Discord invitation text
Do NOT include Discord invitation text or links in guide series articles. Discord guidance is verbose when repeated across every episode — omit it entirely from guide series intros and outros.
Use ProductDetailManualNav for manual links
Instead of writing inline manual translation text and a link in the introduction, use
the <ProductDetailManualNav> component. Place it before the YouTube embed (item 3
in Standard sections above):
<ProductDetailManualNav slug="oxi-coral" />
The component renders a standardized manual navigation block for the product. Adjust
the slug to match the product (e.g., "oxi-coral", "disting-mk4").
No “前回までの記事” section
Do NOT include a “前回までの記事” (previous articles) list. The articles-in-the-same- series navigation component renders cross-episode links automatically — a manual list is redundant and adds maintenance overhead.
Writing rules: Third-party content attribution
When writing articles based on third-party creators’ videos (not the manufacturer’s official demos), add an “〇〇について” section before the Outro with:
- Who the creator is (full name, location, speciality)
- What they do (channels, courses, templates, etc.)
- A statement of respect for their creative work and knowledge sharing
- Links to their YouTube channel and website
This is mandatory for non-brand videos. If the video is from a brand we handle (e.g., Weston Precision Audio’s own demo), no attribution block is needed.
Quick Reference: Key Components
| Component | Usage |
|---|---|
<YoutubeAt url="...?t=N" videoDir="..." id="..." /> | WIP: timestamp embed with Pick Cover dialog |
<YoutubeAt url="..." coverSlug="..." /> | Production: finalized cover (after --finalize) |
<Youtube url="..." /> | Full video embed (no timestamp, at article top) |
<YouTubeCaptureWip videoDir="..." capture="..." id="..." /> | WIP capture image with gallery |
<ArticleImage slug="..." caption="..." /> | Processed image (after R2 finalization) |
<RelatedProducts ids={[...]} /> | Product cards with links |
<InfoBox>...</InfoBox> | Highlighted information box |
<OxiGridFigure> | OXI sequencer grid visualization (product-specific) |
Quick Reference: File Paths
| What | Path |
|---|---|
| Guide articles | src/mdx/guides/{product}-guide-ep{N}.mdx |
| Guide series data | src/data/guide-series.mjs |
| Temp article captures | public/images/temp-article-caps/ |
| Image originals (final) | imgs/{product}-cap-ep{N}-{feature}.heic |
| YouTube downloads | ~/youtube-dl/{date}-{videoId}-{slug}/ |
| Product manuals (translated) | ~/repos/zp/zmanuals/public/{slug}/data/pages-ja.json |
| Product manuals (PDF source) | ~/repos/zp/zmanuals/manual-pdf/{slug}/ |
| Writing guidelines | doc/src/content/docs/writing/guide-article-writing.md |
Step 10: Finalization
Invoked via: /l-youtube-guide-writer --finalize <mdx-path>
Run this when the article is approved and all WIP images have been picked — via the
Pick Cover dialog for <YoutubeAt>, or the gallery dialog for <YouTubeCaptureWip>.
Algorithm
-
Parse the MDX file — find all WIP components:
<YoutubeAt>withvideoDirand nocoverSlug<YouTubeCaptureWip><WipImg>
-
Derive production slugs — propose a slug for each WIP component:
- Read the article filename and
src/data/guide-series.mjsto find the product/brand prefix and episode number - Slug convention (current — required for all new articles):
{brand}-ep{N}-cap-{id-without-ep-prefix}-{HH-MM-SS}- The
HH-MM-SSsuffix is copied verbatim from the source capture filename (e.g., picked covercapture-00-04-20.jpg→ suffix-00-04-20) - Component
id="ep9-yt-grid-overview"+ covercapture-00-01-08.jpg→ slugoxi-ep9-cap-grid-overview-00-01-08 - Component
id="ep9-cap-chord-input"+ covercapture-00-04-20.jpg→ slugoxi-ep9-cap-chord-input-00-04-20
- The
- Why the timestamp suffix is mandatory: the
?t=NURL parameter on<YoutubeAt>is just text and easy to corrupt — a later edit, a careless agent, or a bad guess can desync it from the cover frame. Encoding the time into the slug filename makes it permanent: any future reader can recover the true cover-frame time from the slug, even if?t=Nis wrong. Without this, when the time drifts there is no way to tell which value (URL or cover) is the truth. - For
<YoutubeAt>specifically, also assert URL/slug agreement: after picking the cover, the?t=Nvalue MUST equalHH*3600 + MM*60 + SSfrom the slug suffix. If they disagree, fix the URL to match the slug — the cover frame is the source of truth, never the guessed?t=N. - Legacy articles (already finalized before this rule, e.g., the original
shik-ep1-cap-editor-overviewSHIK EP.1 set, the originaloxi-cap-ep1-*set): keep their existing slugs — do NOT rename, since R2 paths andmetadata-db.jsonrecords are stable. The rule applies only to articles being finalized fresh. - Present the full mapping as a table — include both the slug and the
?t=Nvalue derived from its suffix — and ASK FOR CONFIRMATION before proceeding. Never auto-proceed — slug names are permanent once uploaded to R2.
- Read the article filename and
-
Stage source files — for each confirmed slug, copy the source image to
imgs/:<YoutubeAt>:image-stash/{videoDir}/captures/{capture}→imgs/{slug}.jpgThecaptureattribute must already be set by the Pick Cover dialog. If missing, fall back to auto-deriving from?t=Nand warn the user.<YouTubeCaptureWip>: same —image-stash/{videoDir}/captures/{capture}→imgs/{slug}.jpg<WipImg>:image-stash/wip-images/{src}→imgs/{slug}.jpg(or appropriate extension)
-
Process to R2:
pnpm convimgs:upload # process newly staged files and upload to R2 in one step pnpm build:metadata # regenerate metadata-db.json -
Rewrite MDX — apply to the JA
.mdxand, if it exists, the EN.en.mdxsibling:- WIP
<YoutubeAt>→<YoutubeAt url="..." coverSlug="{slug}" />(dropvideoDir,capture,id) <YouTubeCaptureWip>→<ArticleImage slug="{slug}" caption="..." />(preserve caption, drop WIP-only props)<WipImg>→<ArticleImage slug="{slug}" caption="..." />
- WIP
-
Create EN sibling if missing — if the article has no
.en.mdxsibling yet, delegate the translation to theen-translatorsubagent (Agent tool withsubagent_type: "en-translator"). Pass the JA.mdxpath (already rewritten to production form in Step 5). The subagent writes the.en.mdxnext to the JA file, producing EN with productioncoverSlugvalues that match JA. Do this EN-creation step inline as part of--finalize; do not ask the user to run a separate command.Skip this step if the
.en.mdxalready exists — Step 5 already kept it in sync. -
Verify:
pnpm check:capture-wip # must pass — this is the enforcement gate pnpm build:metadata # confirm all new slugs present in metadata-db.json pnpm dev # smoke test — load the article and verify all images render pnpm check # type check, lint, format -
Commit — single commit per article finalization:
[content] Finalize {article-name} imagesInclude: MDX files (JA + EN),
metadata-db.json, any new image originals inimgs/. If a new EN sibling was created in Step 6, it goes into this same commit.
Safety
- NEVER run
--finalizewithout confirming the slug mapping with the user - NEVER rewrite an MDX file that still has unpicked WIP
<YoutubeAt>auto-deriving from?t=N— ask the user to pick covers first via the Pick Cover dialog, or explicitly confirm they accept the auto-derived captures - Safe to re-run: the algorithm is idempotent as long as slugs resolve to the same files