Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
87abd32
docs: standardize tutorial pages - Card components, blob URLs, i18n f…
lin-bot23 Jul 16, 2026
2e04427
fix: add preview images, input material cards, model storage paths
lin-bot23 Jul 16, 2026
fe29d4a
fix: add previews, input cards, model paths to hunyuan and kandinsky
lin-bot23 Jul 16, 2026
faf5430
fix: add missing Cloud cards to 15+ tutorial pages
lin-bot23 Jul 16, 2026
7c444bf
fix: add missing Cloud cards to remaining pages
lin-bot23 Jul 16, 2026
51cb3b7
fix: add previews and input cards to partner-nodes pages
lin-bot23 Jul 16, 2026
8d998af
fix: add previews and input cards to video + partner-nodes pages
lin-bot23 Jul 16, 2026
225a7ad
fix: add previews and input cards to tripo, rodin, wan2-7
lin-bot23 Jul 16, 2026
cc1c34d
Merge remote-tracking branch 'upstream/main' into docs/tutorial-stand…
lin-bot23 Jul 22, 2026
fbe22b8
fix: remove translation files and em dashes from tutorial-standardize PR
lin-bot23 Jul 22, 2026
1af7f02
i18n: sync zh/ja/ko translations for tutorial Card component standard…
lin-bot23 Jul 24, 2026
609bd61
fix: finish tutorial CardGroup layout and link standardization
comfyui-wiki Jul 24, 2026
8ba399b
Merge remote-tracking branch 'origin/main' into pr/1287
comfyui-wiki Jul 24, 2026
2a3e669
docs: finish tutorial workflow standardization and Partner Nodes nav …
comfyui-wiki Jul 24, 2026
a8bd96c
fix: fill empty Card bodies, subgraph i18n links, and ko kling MDX pa…
comfyui-wiki Jul 24, 2026
a6b384e
fix: remove stray CardGroup closing tags in zh kandinsky and wan fun-inp
comfyui-wiki Jul 24, 2026
58af3be
fix(i18n): restore ja/zh/ko pages corrupted by EN workflow block sync
comfyui-wiki Jul 24, 2026
8e6e2ef
fix(i18n): run pnpm translate for tutorial workflow pages
comfyui-wiki Jul 24, 2026
e13052d
fix(i18n): repair MDX structure broken by chunked translation
comfyui-wiki Jul 24, 2026
57c7d78
revert: drop built-in-nodes changes from tutorial standardization PR
comfyui-wiki Jul 24, 2026
a7ba53c
revert: scope PR i18n changes to tutorials/ only
comfyui-wiki Jul 24, 2026
fad1ff5
Merge branch 'main' into docs/tutorial-standardize
comfyui-wiki Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
153 changes: 153 additions & 0 deletions .cursor/skills/tutorial-workflow-standard/SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Tutorial workflow page standardization spec

Apply to any MDX tutorial page that links to `cloud.comfy.org/?template=` or `workflow_templates`.

## Source of truth for template metadata

- Index: `/Users/linmoumou/Documents/comfy/workflow_templates/templates/index.json`
- Local templates JSON: `/Users/linmoumou/Documents/comfy/workflow_templates/templates/{name}.json`
- Manifest (webp filenames): `/Users/linmoumou/Documents/comfy/workflow_templates/packages/core/src/comfyui_workflow_templates_core/manifest.json`

For each `template` name referenced on a page, look up `name`, `title`, `description`, `io.inputs`, `io.outputs`, `thumbnail`, `thumbnailVariant` in `index.json`.

## Per-workflow section structure

Each distinct template on a page gets its own `###` subsection:

```mdx
### {title from index} (`{template_name}`)

{description from index — one short paragraph, can trim if already covered above}

<img src="https://raw.githubusercontent.com/Comfy-Org/workflow_templates/main/templates/{template_name}-1.webp" alt="{title} workflow preview" />

### Workflow preview images (`templates/*-N.webp`)

- Files like `{template_name}-1.webp`, `-2.webp`, etc. under `templates/` are **workflow preview thumbnails only**.
- Use them **only** in the preview `<img>` above (immediately after the `###` heading, before `<CardGroup>`).
- **Never** use any `templates/*.webp` in **Input materials** or **Example output** sections. Those sections must use `input/{file}` and `output/{file}` from `index.json` only.

<CardGroup cols={2}>
<Card title="Run on Comfy Cloud" icon="cloud" href="https://cloud.comfy.org/?template={template_name}&utm_source=docs&utm_medium=referral&utm_campaign={page-stem}">
Open in Comfy Cloud
</Card>
<Card title="Download Workflow" icon="download" href="https://github.com/Comfy-Org/workflow_templates/blob/main/templates/{template_name}.json">
Download JSON or search &quot;{title}&quot; in Template Library
</Card>
</CardGroup>
```

- **`page-stem`**: MDX filename without extension (e.g. `z-image-turbo` for `z-image-turbo.mdx`).
- **UTM**: always `utm_source=docs&utm_medium=referral&utm_campaign={page-stem}`. Replace `inhouse_social`, `boogu_image_launch`, or other legacy campaign values.
- **CardGroup**: always `cols={2}` for Cloud + Download pairs. Never use `cols={1}` for these.
- **CardGroup lines must NOT start with `|`** — that renders as broken markdown tables.

## Input materials (when `io.inputs` exists)

```mdx
**Input materials**

Upload these files to the matching `LoadImage` nodes:

<CardGroup cols={2}>
<Card title="{filename}" icon="image" href="https://raw.githubusercontent.com/Comfy-Org/workflow_templates/main/input/{filename}">
`LoadImage` node {nodeId} · `{filename}`
</Card>
</CardGroup>

<div style={{display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '1rem', alignItems: 'start'}}>
<img src="https://raw.githubusercontent.com/Comfy-Org/workflow_templates/main/input/{filename}" alt="{filename}" style={{width: '100%', height: 'auto', objectFit: 'contain'}} />
</div>
```

Use `repeat(2, ...)` even for a single image. For multiple inputs, one card and one `<img>` per input in the grid.

## Example output

**Text-to-image** (only `io.outputs`, no inputs): single output image from `output/{file}`.

```mdx
**Example output**

![{alt}](https://raw.githubusercontent.com/Comfy-Org/workflow_templates/main/output/{file})
```

**Edit / compare** (`thumbnailVariant: compareSlider` or `thumbnail` array with input + output):

```mdx
**Example output**

<div style={{display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '1rem', alignItems: 'start'}}>
<img src="https://raw.githubusercontent.com/Comfy-Org/workflow_templates/main/input/{input_file}" alt="Input image" style={{width: '100%', height: 'auto', objectFit: 'contain'}} />
<img src="https://raw.githubusercontent.com/Comfy-Org/workflow_templates/main/output/{output_file}" alt="{title} example output" style={{width: '100%', height: 'auto', objectFit: 'contain'}} />
</div>
```

**URL verification**: run `curl -s -o /dev/null -w "%{http_code}" -L {url}` before embedding. If `output/{file}` 404s, **omit the Example output section** entirely. Do not substitute `templates/*-N.webp` preview thumbnails.

## Model download cards

- Wrap in `<CardGroup cols={2}>` when there are multiple model cards.
- Hugging Face links: use `https://huggingface.co/{org}/{repo}/blob/main/...` — **not** `/resolve/main`.
- Do not change model filenames or storage paths unless fixing an obvious broken link.

## Reference pages (already standardized — copy patterns from these)

| Page | Notes |
|------|-------|
| `tutorials/flux/flux-2-dev.mdx` | Multiple workflows, inputs + compare output |
| `tutorials/flux/flux-2-klein.mdx` | Six workflow subsections |
| `tutorials/image/qwen/qwen-image-edit-2511.mdx` | Two inputs, compare output |
| `tutorials/image/qwen/qwen-image-layered.mdx` | Single input |
| `tutorials/image/z-image/z-image.mdx` | T2I output, CardGroup model downloads |
| `tutorials/image/boogu/boogu-image-0.1.mdx` | Turbo + Edit, UTM `boogu-image-0-1` |

## i18n (zh / ja / ko)

For every English file updated, apply the same structural changes to:

- `zh/{same-path}`
- `ja/{same-path}`
- `ko/{same-path}`

Translate user-facing labels only (`Run on Comfy Cloud`, `Input materials`, `Example output`, card titles, alt text). Keep URLs, template names, node IDs, and filenames identical.

Preserve existing frontmatter (`translationSourceHash`, `translationBlockHashes`, etc.). Do not remove `translationFrom`.

Label translations:
| EN | zh | ja | ko |
|----|----|----|-----|
| Run on Comfy Cloud | 在 Comfy Cloud 上运行 | Comfy Cloud で実行 | Comfy Cloud에서 실행 |
| Download Workflow | 下载工作流 | ワークフローをダウンロード | 워크플로 다운로드 |
| Input materials | 输入素材 | 入力素材 | 입력 자료 |
| Example output | 输出示例 | 出力例 | 출력 예시 |

## Prose style

- Avoid em dashes (—). Use periods, commas, or colons.
- Match technical reference tone of surrounding pages.

## Scope rules

- **Only edit workflow-related sections** — do not rewrite intros, model storage trees, or unrelated content unless fixing HF `resolve` → `blob` links in model download cards on the same page.
- **Skip pages already passing audit** (have `-1.webp` preview, `CardGroup cols={2}`, correct UTM, no `resolve/main` in HF links on page).
- **Do not commit** unless explicitly asked.

## Audit script

`audit_workflows.py` checks tutorial MDX under `tutorials/` and `zh|ja|ko/tutorials/`:

```bash
python3 .cursor/skills/tutorial-workflow-standard/audit_workflows.py # report only
python3 .cursor/skills/tutorial-workflow-standard/audit_workflows.py --fix # auto-fix webp misuse + empty Cards
```

Requires sibling repo `../workflow_templates/templates/index.json` for output URL lookups when fixing webp misuse.

## Already completed (skip unless still failing audit)

- `tutorials/flux/flux-2-dev.mdx` (+ zh/ja/ko partial — sync i18n if EN-only)
- `tutorials/flux/flux-2-klein.mdx`
- `tutorials/image/qwen/qwen-image-2512.mdx`, `qwen-image-edit-2511.mdx`, `qwen-image-layered.mdx` (+ zh/ja/ko for layered)
- `tutorials/image/z-image/z-image.mdx` (+ zh/ja/ko)
- `tutorials/image/boogu/boogu-image-0.1.mdx` (+ zh/ja/ko)
206 changes: 206 additions & 0 deletions .cursor/skills/tutorial-workflow-standard/audit_workflows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#!/usr/bin/env python3
"""Audit (and optionally fix) tutorial workflow pages against SPEC.md."""

from __future__ import annotations

import argparse
import json
import re
from pathlib import Path

ROOT = Path(__file__).resolve().parents[3]
INDEX_PATH = ROOT.parent / "workflow_templates" / "templates" / "index.json"

OUTPUT_MARKERS = [
"**Example output**",
"**输出示例**",
"**出力例**",
"**출력 예시**",
]

WEBP_IN_TEMPLATES = re.compile(
r"https://raw\.githubusercontent\.com/Comfy-Org/workflow_templates/main/templates/[^\s\"')]+-\d+\.webp"
)

CARD_SHORT = {
"en": {"cloud": "Open in Comfy Cloud", "download": "Download JSON workflow"},
"zh": {"cloud": "在 Comfy Cloud 中打开", "download": "下载 JSON 工作流"},
"ja": {"cloud": "Comfy Cloud で開く", "download": "JSON ワークフローをダウンロード"},
"ko": {"cloud": "Comfy Cloud에서 열기", "download": "JSON 워크플로 다운로드"},
}

EMPTY_CARD = re.compile(
r'(?P<open><Card\s+title="[^"]*"\s+icon="(?P<icon>cloud|download)"[^>]*>)\s*\n\s*</Card>',
re.MULTILINE,
)

TUTORIAL_ROOTS = ("tutorials", "zh/tutorials", "ja/tutorials", "ko/tutorials")


def iter_mdx() -> list[Path]:
paths: list[Path] = []
for root in TUTORIAL_ROOTS:
base = ROOT / root
if base.exists():
paths.extend(sorted(base.rglob("*.mdx")))
return paths


def locale_for(path: Path) -> str:
try:
rel = path.relative_to(ROOT)
except ValueError:
return "en"
if rel.parts[0] in CARD_SHORT:
return rel.parts[0]
return "en"


def load_templates() -> dict:
if not INDEX_PATH.exists():
return {}
data = json.loads(INDEX_PATH.read_text(encoding="utf-8"))
out: dict = {}
for mod in data:
for t in mod.get("templates", []):
out[t["name"]] = t
return out


def output_url_for_template(templates: dict, template_name: str) -> str | None:
t = templates.get(template_name)
if not t:
return None
for item in t.get("thumbnail") or []:
if item.startswith("output/"):
return f"https://raw.githubusercontent.com/Comfy-Org/workflow_templates/main/{item}"
for o in t.get("io", {}).get("outputs", []):
f = o.get("file")
if f:
return f"https://raw.githubusercontent.com/Comfy-Org/workflow_templates/main/output/{f}"
return None


def find_example_output_blocks(text: str) -> list[tuple[int, int]]:
blocks: list[tuple[int, int]] = []
for marker in OUTPUT_MARKERS:
start = 0
while True:
idx = text.find(marker, start)
if idx == -1:
break
rest = text[idx + len(marker) :]
end_match = re.search(r"\n(?:## |### |\*\*[A-Z])", rest)
block_end = idx + len(marker) + (end_match.start() if end_match else len(rest))
blocks.append((idx, block_end))
start = idx + len(marker)
return sorted(set(blocks))


def audit_webp(path: Path, templates: dict, fix: bool) -> list[str]:
text = path.read_text(encoding="utf-8")
issues: list[str] = []
original = text

blocks = find_example_output_blocks(text)
for start, end in reversed(blocks):
block = text[start:end]
if not WEBP_IN_TEMPLATES.search(block):
continue
rel = path.relative_to(ROOT)
issues.append(f"{rel}: webp in Example output section")
if not fix:
continue
webp_match = WEBP_IN_TEMPLATES.search(block)
if not webp_match:
continue
m = re.search(r"templates/([^/\"')]+)-\d+\.webp", webp_match.group(0))
template_name = m.group(1) if m else None
real_url = output_url_for_template(templates, template_name) if template_name else None
if real_url:
block = block.replace(webp_match.group(0), real_url)
text = text[:start] + block + text[end:]
else:
text = text[:start] + text[end:]
text = re.sub(r"\n{3,}", "\n\n", text)

if fix:
text = WEBP_IN_TEMPLATES.sub(
lambda m: "" if "-2.webp" in m.group(0) else m.group(0),
text,
)
text = re.sub(r'\n\s*<img src=""[^>]*/>\s*', "\n", text)
for marker in OUTPUT_MARKERS:
text = re.sub(
rf"{re.escape(marker)}\s*\n\s*<div style=\{{\{{display: 'grid'[^>]*\}}\}}>\s*</div>\s*\n",
"",
text,
)
if text != original:
path.write_text(text, encoding="utf-8")

return issues


def audit_empty_cards(path: Path, fix: bool) -> list[str]:
text = path.read_text(encoding="utf-8")
matches = list(EMPTY_CARD.finditer(text))
if not matches:
return []

rel = path.relative_to(ROOT)
issues = [f"{rel}: {len(matches)} empty Card(s)"]
if not fix:
return issues

locale = locale_for(path)
labels = CARD_SHORT[locale]
count = 0

def repl(match: re.Match[str]) -> str:
nonlocal count
count += 1
body = labels[match.group("icon")]
return f'{match.group("open")}\n {body}\n </Card>'

updated = EMPTY_CARD.sub(repl, text)
if count:
path.write_text(updated, encoding="utf-8")
return issues


def main() -> None:
parser = argparse.ArgumentParser(description="Audit tutorial workflow MDX pages.")
parser.add_argument("--fix", action="store_true", help="Apply safe auto-fixes")
args = parser.parse_args()

templates = load_templates()
if not templates and args.fix:
print(f"warn: index not found at {INDEX_PATH}, webp fixes may be limited")

webp_issues: list[str] = []
card_issues: list[str] = []
for path in iter_mdx():
webp_issues.extend(audit_webp(path, templates, args.fix))
card_issues.extend(audit_empty_cards(path, args.fix))

print(f"webp misuse: {len(webp_issues)}")
for item in webp_issues[:20]:
print(f" {item}")
if len(webp_issues) > 20:
print(f" ... and {len(webp_issues) - 20} more")

print(f"empty Cards: {len(card_issues)}")
for item in card_issues[:20]:
print(f" {item}")
if len(card_issues) > 20:
print(f" ... and {len(card_issues) - 20} more")

if args.fix:
print("fixes applied where possible")
elif webp_issues or card_issues:
print("run with --fix to apply safe auto-fixes")


if __name__ == "__main__":
main()
Loading
Loading