Compare commits

...

13 Commits

Author SHA1 Message Date
Jim Liu 宝玉 dcd0f81433 chore: release v1.105.0 2026-04-13 19:51:57 -05:00
Jim Liu 宝玉 c938396efc feat(baoyu-diagram): unify to analyze→confirm→generate workflow
Remove single/multi mode split. Skill now analyzes any input material,
recommends diagram types and splitting strategy, confirms once, then generates.
2026-04-13 19:42:29 -05:00
Jim Liu 宝玉 1ba68c9a9c chore: release v1.104.0 2026-04-13 14:24:04 -05:00
Jim Liu 宝玉 b3f5c0a8aa fix(baoyu-post-to-wechat): verify editor focus before paste operations 2026-04-13 14:23:41 -05:00
Jim Liu 宝玉 9c45ede0c7 feat(baoyu-diagram): add Mermaid sketch step before SVG generation 2026-04-13 14:23:39 -05:00
Jim Liu 宝玉 30d2ac98ce chore: release v1.103.1 2026-04-13 11:13:06 -05:00
Jim Liu 宝玉 bfdd64bd4e chore: sync baoyu-md vendor to baoyu-markdown-to-html and baoyu-post-to-weibo 2026-04-12 20:18:05 -05:00
Jim Liu 宝玉 434d4857da chore: release v1.103.0 2026-04-12 20:17:25 -05:00
Jim Liu 宝玉 990fea4f7b fix(baoyu-post-to-wechat): decode HTML entities and strip tags from article summary
Add cleanSummaryText() to baoyu-md package: decodes HTML entities (&, <, &#x..., etc.)
and strips HTML tags before using frontmatter description/summary as WeChat article digest
2026-04-12 20:16:53 -05:00
Jim Liu 宝玉 517ff566a1 fix(baoyu-image-cards): prevent color names from appearing as visible text in images 2026-04-12 20:16:48 -05:00
Jim Liu 宝玉 4c9af7d92f fix(baoyu-cover-image): prevent color names and hex codes from appearing as visible text in images
Add semantic constraint to all palette references and prompt template:
color values are rendering guidance only and must not be displayed as text labels
2026-04-12 20:16:44 -05:00
Jim Liu 宝玉 46c4859d48 fix(baoyu-article-illustrator): prevent color names and hex codes from appearing as visible text in images
Add semantic constraint to palette references and prompt-construction rules:
color values are rendering guidance only and must not be displayed as text labels
2026-04-12 20:16:40 -05:00
Jim Liu 宝玉 f3f886217b feat(baoyu-diagram): add multi-diagram mode for article-wide diagram generation
- New multi-diagram mode: analyze article content and generate diagrams at identified positions
- New options: --density (minimal/balanced/per-section/rich), --mode (single/multi/auto)
- Auto mode detection: file path or multi-paragraph content → multi, short topic → single
- Output structure: diagram/{article-slug}/NN-{type}-{slug}/diagram.svg + outline.md
- Step 7: insert diagram image links into article at identified positions
- Modification guide: regenerate/add/remove individual diagrams
- Version bumped to 1.1.0 in SKILL.md
2026-04-12 20:16:36 -05:00
38 changed files with 742 additions and 113 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
}, },
"metadata": { "metadata": {
"description": "Skills shared by Baoyu for improving daily work efficiency", "description": "Skills shared by Baoyu for improving daily work efficiency",
"version": "1.102.0" "version": "1.105.0"
}, },
"plugins": [ "plugins": [
{ {
+30
View File
@@ -2,6 +2,36 @@
English | [中文](./CHANGELOG.zh.md) English | [中文](./CHANGELOG.zh.md)
## 1.105.0 - 2026-04-13
### Features
- `baoyu-diagram`: unify to analyze→confirm→generate workflow — remove single/multi mode split; skill now analyzes any input material, recommends diagram types and splitting strategy, confirms once, then generates all diagrams
## 1.104.0 - 2026-04-13
### Features
- `baoyu-diagram`: add Mermaid sketch step (6d-0) before SVG generation — write a Mermaid code block as structural intent; add MermaidSVG consistency check in step 6f
### Fixes
- `baoyu-post-to-wechat`: verify editor focus before paste and type operations to prevent silent paste failures
## 1.103.1 - 2026-04-13
### Fixes
- `baoyu-markdown-to-html`: decode HTML entities and strip tags from article summary
- `baoyu-post-to-weibo`: decode HTML entities and strip tags from article summary
## 1.103.0 - 2026-04-12
### Features
- `baoyu-diagram`: add multi-diagram mode — analyze article content and generate multiple diagrams at identified positions; new `--density` option (`minimal`, `balanced`, `per-section`, `rich`) and `--mode` option (`single`, `multi`, `auto`); auto-detects mode from input (file path → multi, short topic → single); inserts diagram image links into article; output structure `diagram/{article-slug}/NN-{type}-{slug}/`
### Fixes
- `baoyu-article-illustrator`: prevent color names and hex codes from appearing as visible text in generated images — add semantic constraint to all palette references and prompt construction rules
- `baoyu-cover-image`: prevent color names and hex codes from appearing as visible text in generated images — add constraint to all palette references and prompt template
- `baoyu-image-cards`: prevent color names from appearing as visible text in generated images
- `baoyu-post-to-wechat`: decode HTML entities and strip HTML tags from article summary before using as WeChat article digest
## 1.102.0 - 2026-04-12 ## 1.102.0 - 2026-04-12
### Features ### Features
+30
View File
@@ -2,6 +2,36 @@
[English](./CHANGELOG.md) | 中文 [English](./CHANGELOG.md) | 中文
## 1.105.0 - 2026-04-13
### 新功能
- `baoyu-diagram`:统一为分析→确认→生成工作流 —— 移除单图/多图模式区分;技能现在分析任意输入素材,推荐图表类型和拆分策略,一次确认后批量生成所有图表
## 1.104.0 - 2026-04-13
### 新功能
- `baoyu-diagram`:新增 Mermaid 草图步骤(6d-0),在生成 SVG 前先写 Mermaid 代码块作为结构意图;在步骤 6f 新增 Mermaid–SVG 一致性检查
### 修复
- `baoyu-post-to-wechat`:在粘贴和输入操作前校验编辑器焦点,避免粘贴静默失败
## 1.103.1 - 2026-04-13
### 修复
- `baoyu-markdown-to-html`:修复文章摘要中 HTML 实体未解码及 HTML 标签未剥离的问题
- `baoyu-post-to-weibo`:修复文章摘要中 HTML 实体未解码及 HTML 标签未剥离的问题
## 1.103.0 - 2026-04-12
### 新功能
- `baoyu-diagram`:新增多图模式 —— 分析文章内容,在识别出的位置批量生成图表;新增 `--density` 参数(`minimal``balanced``per-section``rich`)和 `--mode` 参数(`single``multi``auto`);根据输入自动判断模式(文件路径→多图,短主题→单图);自动在文章中插入图表链接;输出目录结构 `diagram/{article-slug}/NN-{type}-{slug}/`
### 修复
- `baoyu-article-illustrator`:修复生成图像中出现颜色名称和色值文字的问题 —— 在所有调色板参考文件和提示构建规则中添加语义约束
- `baoyu-cover-image`:修复生成图像中出现颜色名称和色值文字的问题 —— 在所有调色板参考文件和提示模板中添加约束
- `baoyu-image-cards`:修复生成图像中出现颜色名称文字的问题
- `baoyu-post-to-wechat`:修复文章摘要中 HTML 实体未解码及 HTML 标签未剥离的问题,避免微信文章摘要显示乱码
## 1.102.0 - 2026-04-12 ## 1.102.0 - 2026-04-12
### 新功能 ### 新功能
+1 -1
View File
@@ -1,6 +1,6 @@
# CLAUDE.md # CLAUDE.md
Claude Code marketplace plugin providing AI-powered content generation skills. Version: **1.97.0**. Claude Code marketplace plugin providing AI-powered content generation skills. Version: **1.104.0**.
## Architecture ## Architecture
+9 -10
View File
@@ -274,19 +274,16 @@ Generate professional infographics with 21 layout types and 21 visual styles. An
#### baoyu-diagram #### baoyu-diagram
Generate publication-ready SVG diagrams — flowcharts, structural/architecture diagrams, and illustrative intuition diagrams. Claude writes real SVG code directly following a cohesive design system. Output is a single self-contained `.svg` file with embedded styles and auto dark-mode, ready to embed in articles, WeChat posts, slides, Notion, and docs. Generate publication-ready SVG diagrams from source material — flowcharts, sequence/protocol diagrams, structural/architecture diagrams, and illustrative intuition diagrams. Analyzes input material to recommend diagram type(s) and splitting strategy, confirms the plan once, then generates all diagrams. Claude writes real SVG code directly following a cohesive design system. Output is self-contained `.svg` files with embedded styles and auto dark-mode.
```bash ```bash
# Auto-route on the verb in the prompt # Topic string — skill analyzes and proposes a plan
/baoyu-diagram "how JWT authentication works" /baoyu-diagram "how JWT authentication works"
# Force a specific type
/baoyu-diagram "Kubernetes architecture" --type structural /baoyu-diagram "Kubernetes architecture" --type structural
/baoyu-diagram "how attention works" --type illustrative /baoyu-diagram "OAuth 2.0 flow" --type sequence
/baoyu-diagram "CI/CD pipeline" --type flowchart
# From a markdown source file # File path — skill reads, analyzes, and proposes a plan
/baoyu-diagram path/to/content.md /baoyu-diagram path/to/article.md
# Language and output path # Language and output path
/baoyu-diagram "微服务架构" --lang zh /baoyu-diagram "微服务架构" --lang zh
@@ -296,17 +293,19 @@ Generate publication-ready SVG diagrams — flowcharts, structural/architecture
**Options**: **Options**:
| Option | Description | | Option | Description |
|--------|-------------| |--------|-------------|
| `--type <name>` | `flowchart`, `structural`, `illustrative`, `auto` (default) | | `--type <name>` | `flowchart`, `sequence`, `structural`, `illustrative`, `class`, `auto` (default). Skips type recommendation. |
| `--lang <code>` | Output language (en, zh, ja, ...) | | `--lang <code>` | Output language (en, zh, ja, ...) |
| `--out <path>` | Output file path (default: `diagram/{slug}/diagram.svg`) | | `--out <path>` | Output file path. Generates exactly one diagram focused on the most important aspect. |
**Diagram types**: **Diagram types**:
| Type | Reader need | Verbs that trigger it | | Type | Reader need | Verbs that trigger it |
|------|-------------|------------------------| |------|-------------|------------------------|
| `flowchart` | Walk me through the steps in order | walk through, steps, process, lifecycle, workflow, state machine | | `flowchart` | Walk me through the steps in order | walk through, steps, process, lifecycle, workflow, state machine |
| `sequence` | Who talks to whom, in what order | protocol, handshake, auth flow, OAuth, TCP, request/response |
| `structural` | Show me what's inside what, how it's organised | architecture, components, topology, layout, what's inside | | `structural` | Show me what's inside what, how it's organised | architecture, components, topology, layout, what's inside |
| `illustrative` | Give me the intuition — draw the mechanism | how does X work, explain X, intuition for, why does X do Y | | `illustrative` | Give me the intuition — draw the mechanism | how does X work, explain X, intuition for, why does X do Y |
| `class` | What are the types and how are they related | class diagram, UML, inheritance, interface, schema |
Not an image-generation skill — no LLM image model is called. Claude writes the SVG by hand with hand-computed layout math, so every diagram honors the design system. Embedded `<style>` block with `@media (prefers-color-scheme: dark)` means the same file renders correctly in both light and dark mode anywhere it's embedded. Not an image-generation skill — no LLM image model is called. Claude writes the SVG by hand with hand-computed layout math, so every diagram honors the design system. Embedded `<style>` block with `@media (prefers-color-scheme: dark)` means the same file renders correctly in both light and dark mode anywhere it's embedded.
+10 -11
View File
@@ -274,19 +274,16 @@ clawhub install baoyu-markdown-to-html
#### baoyu-diagram #### baoyu-diagram
生成可直接发布的 SVG 图表 —— 包括流程图、架构/结构图、示意图(直觉图解)。Claude 直接输出符合统一设计规范的真实 SVG 代码,产物是单个自包含的 `.svg` 文件,内嵌样式并自动支持深色模式,可直接嵌入文章、微信公众号、幻灯片、Notion 和各类文档中 从源素材生成可直接发布的 SVG 图表 —— 包括流程图、时序/协议图、架构/结构图、示意图(直觉图解)。分析输入素材,推荐图表类型和拆分策略,一次确认后批量生成。Claude 直接输出符合统一设计规范的真实 SVG 代码,产物是自包含的 `.svg` 文件,内嵌样式并自动支持深色模式。
```bash ```bash
# 自动根据提示词中的动词路由类型 # 主题描述 —— 技能分析并提出方案
/baoyu-diagram "JWT 认证流程是怎么工作的" /baoyu-diagram "JWT 认证流程是怎么工作的"
# 强制指定类型
/baoyu-diagram "Kubernetes 架构" --type structural /baoyu-diagram "Kubernetes 架构" --type structural
/baoyu-diagram "注意力机制原理" --type illustrative /baoyu-diagram "OAuth 2.0 流程" --type sequence
/baoyu-diagram "CI/CD 流水线" --type flowchart
# 从 Markdown 源文件生成 # 文件路径 —— 技能读取、分析并提出方案
/baoyu-diagram path/to/content.md /baoyu-diagram path/to/article.md
# 语言和输出路径 # 语言和输出路径
/baoyu-diagram "微服务架构" --lang zh /baoyu-diagram "微服务架构" --lang zh
@@ -296,17 +293,19 @@ clawhub install baoyu-markdown-to-html
**参数** **参数**
| 参数 | 说明 | | 参数 | 说明 |
|------|------| |------|------|
| `--type <name>` | `flowchart`(流程图)`structural`(结构/架构图)`illustrative`(示意图)`auto`(默认,按动词路由) | | `--type <name>` | `flowchart``sequence``structural``illustrative``class``auto`(默认)。跳过类型推荐直接生成。 |
| `--lang <code>` | 输出语言(en、zh、ja 等) | | `--lang <code>` | 输出语言(en、zh、ja 等) |
| `--out <path>` | 输出文件路径(默认:`diagram/{slug}/diagram.svg` | | `--out <path>` | 输出文件路径。生成聚焦于最重要内容的单张图表。 |
**种图表类型** **种图表类型**
| 类型 | 适用场景 | 触发动词 | | 类型 | 适用场景 | 触发动词 |
|------|----------|----------| |------|----------|----------|
| `flowchart` | 按顺序走一遍流程 | 流程、步骤、工作流、生命周期、状态机 | | `flowchart` | 按顺序走一遍流程 | 流程、步骤、工作流、生命周期、状态机 |
| `sequence` | 谁和谁通信、按什么顺序 | 协议、握手、认证流程、OAuth、TCP、请求/响应 |
| `structural` | 展示什么包含什么、如何组织 | 架构、组件、拓扑、布局、什么在什么里面 | | `structural` | 展示什么包含什么、如何组织 | 架构、组件、拓扑、布局、什么在什么里面 |
| `illustrative` | 建立直觉 —— 画出机制本身 | 怎么工作、原理、为什么、直观解释 | | `illustrative` | 建立直觉 —— 画出机制本身 | 怎么工作、原理、为什么、直观解释 |
| `class` | 类型是什么、它们如何关联 | 类图、UML、继承、接口、数据模型 |
本技能不调用任何图像生成模型 —— Claude 通过手算坐标直接写 SVG 代码,确保每个图表都遵守设计规范。内嵌的 `<style>` 块包含 `@media (prefers-color-scheme: dark)`,同一个文件在浅色和深色模式下均正确渲染,可嵌入到任意支持 SVG 的宿主环境中。 本技能不调用任何图像生成模型 —— Claude 通过手算坐标直接写 SVG 代码,确保每个图表都遵守设计规范。内嵌的 `<style>` 块包含 `@media (prefers-color-scheme: dark)`,同一个文件在浅色和深色模式下均正确渲染,可嵌入到任意支持 SVG 的宿主环境中。
+17
View File
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import test from "node:test"; import test from "node:test";
import { import {
cleanSummaryText,
extractSummaryFromBody, extractSummaryFromBody,
extractTitleFromMarkdown, extractTitleFromMarkdown,
parseFrontmatter, parseFrontmatter,
@@ -91,3 +92,19 @@ This is **the first paragraph** with [a link](https://example.com) and \`inline
"This is the first paragraph with a link and inline code that should...", "This is the first paragraph with a link and inline code that should...",
); );
}); });
test("summary extraction normalizes raw HTML paragraphs to plain text", () => {
const summary = extractSummaryFromBody(
`
# Heading
<p style="font-size: 16px; color: #666; margin-bottom: 20px;">2026</p>
`,
120,
);
assert.equal(
summary,
"2026年初,一只“龙虾”搅动了整个科技圈。腾讯楼下排起近千人长队,只为让工程师领取一份福利。",
);
assert.equal(cleanSummaryText("<strong>Good&nbsp;text&#33;&apos;</strong>"), "Good text!'");
});
+43 -3
View File
@@ -46,6 +46,45 @@ export function stripWrappingQuotes(value: string): string {
return value.trim(); return value.trim();
} }
const HTML_ENTITIES: Record<string, string> = {
amp: "&",
apos: "'",
gt: ">",
lt: "<",
nbsp: " ",
quot: '"',
};
function decodeHtmlCodePoint(codePoint: number, fallback: string): string {
if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
return fallback;
}
return String.fromCodePoint(codePoint);
}
function decodeHtmlEntities(value: string): string {
return value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (entity, body: string) => {
const normalized = body.toLowerCase();
if (normalized.startsWith("#x")) {
return decodeHtmlCodePoint(Number.parseInt(normalized.slice(2), 16), entity);
}
if (normalized.startsWith("#")) {
return decodeHtmlCodePoint(Number.parseInt(normalized.slice(1), 10), entity);
}
return HTML_ENTITIES[normalized] ?? entity;
});
}
export function cleanSummaryText(value: string): string {
return decodeHtmlEntities(stripWrappingQuotes(value))
.replace(/<script\b[\s\S]*?<\/script>/gi, " ")
.replace(/<style\b[\s\S]*?<\/style>/gi, " ")
.replace(/<br\s*\/?>/gi, " ")
.replace(/<\/?[a-z][a-z0-9:-]*(?:\s+[^>]*)?>/gi, " ")
.replace(/\s+/g, " ")
.trim();
}
export function toFrontmatterString(value: unknown): string | undefined { export function toFrontmatterString(value: unknown): string | undefined {
if (typeof value === "string") { if (typeof value === "string") {
return stripWrappingQuotes(value); return stripWrappingQuotes(value);
@@ -94,10 +133,11 @@ export function extractSummaryFromBody(body: string, maxLen: number): string {
.replace(/\*(.+?)\*/g, "$1") .replace(/\*(.+?)\*/g, "$1")
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/`([^`]+)`/g, "$1"); .replace(/`([^`]+)`/g, "$1");
const summaryText = cleanSummaryText(cleanText);
if (cleanText.length > 20) { if (summaryText.length > 20) {
if (cleanText.length <= maxLen) return cleanText; if (summaryText.length <= maxLen) return summaryText;
return `${cleanText.slice(0, maxLen - 3)}...`; return `${summaryText.slice(0, maxLen - 3)}...`;
} }
} }
@@ -39,6 +39,22 @@ test("buildHtmlDocument includes optional meta tags and code theme CSS", () => {
assert.match(html, /<article>Hello<\/article>/); assert.match(html, /<article>Hello<\/article>/);
}); });
test("buildHtmlDocument escapes head metadata attributes", () => {
const html = buildHtmlDocument(
{
title: `Doc <draft>`,
author: `Bao"yu`,
description: `<p style="color: red">Summary & notes</p>`,
},
"",
"",
);
assert.match(html, /<title>Doc &lt;draft&gt;<\/title>/);
assert.match(html, /meta name="author" content="Bao&quot;yu"/);
assert.match(html, /meta name="description" content="&lt;p style=&quot;color: red&quot;&gt;Summary &amp; notes&lt;\/p&gt;"/);
});
test("normalizeCssText and normalizeInlineCss replace variables and strip declarations", () => { test("normalizeCssText and normalizeInlineCss replace variables and strip declarations", () => {
const rawCss = ` const rawCss = `
:root { --md-primary-color: #000; --md-font-size: 12px; --foreground: 0 0% 5%; } :root { --md-primary-color: #000; --md-font-size: 12px; --foreground: 0 0% 5%; }
+8 -3
View File
@@ -45,19 +45,24 @@ export function loadCodeThemeCss(themeName: string): string {
} }
export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string { export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string {
const escapeHtmlAttribute = (value: string) => value
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const lines = [ const lines = [
"<!doctype html>", "<!doctype html>",
"<html>", "<html>",
"<head>", "<head>",
' <meta charset="utf-8" />', ' <meta charset="utf-8" />',
' <meta name="viewport" content="width=device-width, initial-scale=1" />', ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
` <title>${meta.title}</title>`, ` <title>${escapeHtmlAttribute(meta.title)}</title>`,
]; ];
if (meta.author) { if (meta.author) {
lines.push(` <meta name="author" content="${meta.author}" />`); lines.push(` <meta name="author" content="${escapeHtmlAttribute(meta.author)}" />`);
} }
if (meta.description) { if (meta.description) {
lines.push(` <meta name="description" content="${meta.description}" />`); lines.push(` <meta name="description" content="${escapeHtmlAttribute(meta.description)}" />`);
} }
lines.push(` <style>${css}</style>`); lines.push(` <style>${css}</style>`);
if (codeThemeCss) { if (codeThemeCss) {
@@ -24,6 +24,10 @@ Soft macaron pastel color blocks on warm cream
Coral Red (#E8655A) for key data, warnings, and emphasis highlights. Use sparingly — one or two elements per illustration. Coral Red (#E8655A) for key data, warnings, and emphasis highlights. Use sparingly — one or two elements per illustration.
## Semantic Constraint
Soft pastel macaron color palette. Use block colors as rounded card backgrounds for distinct information sections. Accent coral red sparingly for emphasis on key terms only. Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Educational content, knowledge sharing, concept explainers, tutorials, tech summaries, onboarding materials Educational content, knowledge sharing, concept explainers, tutorials, tech summaries, onboarding materials
@@ -22,6 +22,10 @@ Black ink on pure white with sparse semantic accent colors
Use black ink for all structural elements — lines, text, figures. Accent colors appear only for semantic highlighting: coral red for risks/gaps/problems, muted teal for positive/solution/after-states, dusty lavender for neutral category tags. Total colored pixels must remain under 10% of canvas. Pale gray may back a subtle zone but must never dominate. Use black ink for all structural elements — lines, text, figures. Accent colors appear only for semantic highlighting: coral red for risks/gaps/problems, muted teal for positive/solution/after-states, dusty lavender for neutral category tags. Total colored pixels must remain under 10% of canvas. Pale gray may back a subtle zone but must never dominate.
## Semantic Constraint
Black ink on white canvas. Accent colors for semantic highlighting only — total colored pixels under 10% of canvas. Do NOT render color names, hex codes, or role labels as visible text in the image.
## Compatible With ## Compatible With
- `ink-notes` (primary, default pairing) - `ink-notes` (primary, default pairing)
@@ -24,6 +24,10 @@ Vibrant neon colors on dark backgrounds
Hot Pink (#FF1493) for primary emphasis. High contrast neon-on-dark creates immediate visual impact. Hot Pink (#FF1493) for primary emphasis. High contrast neon-on-dark creates immediate visual impact.
## Semantic Constraint
Vibrant neon-on-dark palette. High contrast, immediate visual impact. Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Gaming, retro tech, 80s/90s nostalgic content, bold editorial, trend and pop culture Gaming, retro tech, 80s/90s nostalgic content, bold editorial, trend and pop culture
@@ -23,6 +23,10 @@ Warm earth tones on soft peach, no cool colors
Warm Orange (#ED8936) for primary emphasis. Warm-only palette — no cool colors (no green, blue, purple). Modern-retro feel. Warm Orange (#ED8936) for primary emphasis. Warm-only palette — no cool colors (no green, blue, purple). Modern-retro feel.
## Semantic Constraint
Warm earth tone palette. Warm-only — no cool colors (no green, blue, purple). Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Product showcases, team introductions, feature grids, brand content, personal growth, lifestyle Product showcases, team introductions, feature grids, brand content, personal growth, lifestyle
@@ -67,6 +67,17 @@ STYLE (from reference):
--- ---
## Color Specification Rules
Colors in prompts use hex codes for **rendering guidance only** — they tell the model which colors to use, NOT what text to display.
**⚠️ CRITICAL**: Image generation models sometimes render color names and hex values as visible text labels in the image (e.g., painting "Macaron Blue #A8D8EA" as a label). This must be prevented.
**Add to ALL prompts that contain a COLORS section**:
> Color values (#hex) and color names are rendering guidance only — do NOT display color names, hex codes, or palette labels as visible text in the image.
---
## Character Rendering ## Character Rendering
When depicting people: When depicting people:
@@ -21,6 +21,10 @@ Technical, professional, precise
- Technical schematics and diagrams - Technical schematics and diagrams
- Geometric precision elements - Geometric precision elements
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Architecture, system design, API, technical documentation, engineering, data analysis Architecture, system design, API, technical documentation, engineering, data analysis
@@ -21,6 +21,10 @@ Cinematic, premium, atmospheric
- Silhouettes with backlit edges - Silhouettes with backlit edges
- Subtle gradient backgrounds - Subtle gradient backgrounds
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Entertainment, premium brands, cinematic storytelling, dark mode, gaming, night themes Entertainment, premium brands, cinematic storytelling, dark mode, gaming, night themes
@@ -34,6 +34,10 @@ Choose ONE pair based on content mood. The two colors dominate the entire image:
- Minimal use of third color (only for small highlights) - Minimal use of third color (only for small highlights)
- High contrast figure-ground relationships - High contrast figure-ground relationships
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Movie posters, album covers, concert prints, dramatic announcements, cinematic content, bold branding, editorial covers, artistic campaigns Movie posters, album covers, concert prints, dramatic announcements, cinematic content, bold branding, editorial covers, artistic campaigns
@@ -21,6 +21,10 @@ Natural, organic, grounded
- Botanical illustrations - Botanical illustrations
- Earthy textures and natural patterns - Earthy textures and natural patterns
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Nature, wellness, eco, organic, travel, sustainability, outdoor topics, slow living Nature, wellness, eco, organic, travel, sustainability, outdoor topics, slow living
@@ -21,6 +21,10 @@ Sophisticated, refined, understated luxury
- Refined geometric patterns - Refined geometric patterns
- Balanced, symmetrical compositions - Balanced, symmetrical compositions
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Business, professional, thought leadership, luxury, corporate communications Business, professional, thought leadership, luxury, corporate communications
@@ -21,6 +21,10 @@ Soft macaron pastel color blocks on warm cream
- Soft shadows, no hard edges - Soft shadows, no hard edges
- Gentle gradient transitions between zones - Gentle gradient transitions between zones
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Educational content, knowledge sharing, concept explainers, tutorials, tech summaries, onboarding materials Educational content, knowledge sharing, concept explainers, tutorials, tech summaries, onboarding materials
@@ -21,6 +21,10 @@ Clean, focused, essential
- Single focal point emphasis - Single focal point emphasis
- Stark contrast between elements - Stark contrast between elements
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Zen, focus, essential concepts, pure, simple, minimalist philosophy, clean design Zen, focus, essential concepts, pure, simple, minimalist philosophy, clean design
@@ -21,6 +21,10 @@ Gentle, whimsical, soft
- Soft shadows and gentle highlights - Soft shadows and gentle highlights
- Storybook-style elements - Storybook-style elements
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Fantasy, children, gentle content, creative, whimsical, casual, beginner guides Fantasy, children, gentle content, creative, whimsical, casual, beginner guides
@@ -25,6 +25,10 @@ Nostalgic, vintage, classic
- Pill-shaped clouds, small dots and stars - Pill-shaped clouds, small dots and stars
- Classic icons and retro motifs - Classic icons and retro motifs
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
History, vintage, retro, classic, exploration, retrospectives, throwback content, creative proposals, educational History, vintage, retro, classic, exploration, retrospectives, throwback content, creative proposals, educational
@@ -21,6 +21,10 @@ Energetic, bold, attention-grabbing
- Dramatic lighting effects - Dramatic lighting effects
- High-energy visual compositions - High-energy visual compositions
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Product launch, gaming, promotion, event, marketing, announcements, brand showcases Product launch, gaming, promotion, event, marketing, announcements, brand showcases
@@ -21,6 +21,10 @@ Friendly, approachable, human-centered
- Hearts, smiling faces, friendly icons - Hearts, smiling faces, friendly icons
- Warm gradient overlays - Warm gradient overlays
## Semantic Constraint
Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best For ## Best For
Personal growth, lifestyle, education, human stories, emotion, community Personal growth, lifestyle, education, human stories, emotion, community
@@ -62,6 +62,7 @@ Visual composition:
- Decorative: [palette-specific elements that reinforce content theme] - Decorative: [palette-specific elements that reinforce content theme]
Color scheme: [primary, background, accent from palette definition, adjusted by mood] Color scheme: [primary, background, accent from palette definition, adjusted by mood]
Color constraint: Color values (#hex) and color names are rendering guidance only — do NOT display color names, hex codes, or palette labels as visible text in the image.
Rendering notes: [key characteristics from rendering definition — lines, texture, depth, element style] Rendering notes: [key characteristics from rendering definition — lines, texture, depth, element style]
Type notes: [key characteristics from type definition] Type notes: [key characteristics from type definition]
Palette notes: [key characteristics from palette definition] Palette notes: [key characteristics from palette definition]
+295 -63
View File
@@ -1,7 +1,7 @@
--- ---
name: baoyu-diagram name: baoyu-diagram
description: Generates publication-ready SVG diagrams — flowcharts, sequence/protocol diagrams, structural/architecture diagrams, and illustrative intuition diagrams — by writing real SVG code directly following a cohesive design system. Use whenever the user asks to "draw a flowchart", "draw a sequence diagram", "show the OAuth / TCP / auth protocol", "make an architecture diagram", "explain how X works visually", "画流程图", "画时序图", "画架构图", "画示意图", "画图", or wants a clean, embeddable vector diagram for articles, WeChat posts, slides, or docs. Output is a single self-contained .svg file that renders correctly in light and dark mode anywhere it is embedded. description: Generates publication-ready SVG diagrams from source material — flowcharts, sequence/protocol diagrams, structural/architecture diagrams, and illustrative intuition diagrams — by writing real SVG code directly following a cohesive design system. Analyzes input material to recommend diagram type(s), splitting strategy, and optional overview diagram, then generates after one-time confirmation. Use whenever the user asks to "draw a flowchart", "draw a sequence diagram", "show the OAuth / TCP / auth protocol", "make an architecture diagram", "explain how X works visually", "draw a diagram for this", "画流程图", "画时序图", "画架构图", "画示意图", "画图", or wants clean, embeddable vector diagrams for articles, WeChat posts, slides, or docs. Output is one or more self-contained .svg files that render correctly in light and dark mode anywhere they are embedded.
version: 1.0.0 version: 1.2.0
metadata: metadata:
openclaw: openclaw:
homepage: https://github.com/JimLiu/baoyu-skills#baoyu-diagram homepage: https://github.com/JimLiu/baoyu-skills#baoyu-diagram
@@ -9,23 +9,25 @@ metadata:
# Diagram Generator # Diagram Generator
Write **real SVG code** directly, following a consistent design system, the output is a single `.svg` file that is self-contained (embedded styles, auto dark-mode), editable by humans, scales to any size without quality loss, and embeds cleanly into articles, WeChat posts, slide decks, Notion, and markdown. Write **real SVG code** directly, following a consistent design system, the output is self-contained `.svg` files (embedded styles, auto dark-mode), editable by humans, scales to any size without quality loss, and embeds cleanly into articles, WeChat posts, slide decks, Notion, and markdown.
When given source material (topic descriptions, documents, technical specs, pasted content), the skill analyzes what diagrams would best convey the material, recommends diagram type(s) and whether the content should be split into multiple focused diagrams, confirms the plan once, then generates all diagrams.
This is not an image-generation skill — it does not call any LLM image model. Claude writes the SVG node-by-node, doing the layout math by hand so every diagram honors the rules in `references/`. This is not an image-generation skill — it does not call any LLM image model. Claude writes the SVG node-by-node, doing the layout math by hand so every diagram honors the rules in `references/`.
## Usage ## Usage
```bash ```bash
# Prompt for the topic if no argument given # Topic string — skill analyzes and proposes a plan
/baoyu-diagram
# Plain-text description
/baoyu-diagram "how JWT authentication works" /baoyu-diagram "how JWT authentication works"
# Path to a markdown file as source content # File path — skill reads, analyzes, and proposes a plan
/baoyu-diagram path/to/content.md /baoyu-diagram path/to/content.md
# Force a specific diagram type # Pasted content — prompts for input if no argument given
/baoyu-diagram
# Force a specific diagram type (skips type recommendation)
/baoyu-diagram "transformer attention" --type illustrative /baoyu-diagram "transformer attention" --type illustrative
/baoyu-diagram "Kubernetes architecture" --type structural /baoyu-diagram "Kubernetes architecture" --type structural
/baoyu-diagram "CI/CD pipeline" --type flowchart /baoyu-diagram "CI/CD pipeline" --type flowchart
@@ -41,9 +43,9 @@ This is not an image-generation skill — it does not call any LLM image model.
| Option | Values | | Option | Values |
|--------|--------| |--------|--------|
| `--type` | `flowchart`, `sequence`, `structural`, `illustrative`, `class`, `auto` (default — route on verb) | | `--type` | `flowchart`, `sequence`, `structural`, `illustrative`, `class`, `auto` (default — route on verb). When specified, forces this type for all diagrams — skips type recommendation. |
| `--lang` | `en`, `zh`, `ja`, `ko`, ... (default: match the user's language) | | `--lang` | `en`, `zh`, `ja`, `ko`, ... (default: match the user's language) |
| `--out` | Output file path (default: `diagram/{slug}/diagram.svg`) | | `--out` | Output file path. When set, the skill generates exactly one diagram at this path — analysis produces a single-diagram plan focused on the most important aspect of the material. |
## Diagram types ## Diagram types
@@ -108,11 +110,174 @@ Cycles, ERDs, and gantt charts are **out of scope for v1**. For cycles, draw the
## Workflow ## Workflow
### Step 1: Capture intent ### Step 1: Capture input
Read the user's prompt or content file. If the topic is missing, ask for it with AskUserQuestion. Read the user's prompt, content file, or pasted content. Note any flags (`--type`, `--lang`, `--out`).
**Before routing, extract these five things from the source:** | Input | Action |
|-------|--------|
| File path to `.md` / `.txt` | Read the file as source material |
| Pasted content or topic string | Capture as source material |
| No input at all | Ask with AskUserQuestion |
If `--out` is given, the skill will generate exactly one diagram at that path — the analysis in Step 2 produces a single-diagram plan focused on the most important aspect of the material.
### Step 2: Analyze material and produce plan
Analyze the source material and make three decisions:
#### Decision A: Type routing
For the input material, determine which diagram type(s) are appropriate using the routing table in "Diagram types."
| Situation | Action |
|-----------|--------|
| Only one type makes sense (clear verb signal, or `--type` given) | That type is the recommendation. No choice needed. |
| Multiple types could each produce a useful diagram from the same material | List the candidates with a one-sentence rationale for each. The user picks in Step 3. |
#### Decision B: Content splitting
Assess whether the material should produce one diagram or multiple sub-diagrams.
**Single diagram** when:
- Material is focused on one concept, one mechanism, one process
- Named elements count is manageable (under ~6 for flowchart, under ~4 actors for sequence, under ~3 containers for structural)
- One "After seeing this diagram, the reader understands ___" sentence covers the whole material
**Multiple sub-diagrams** when:
- Material covers 2+ independent mechanisms or processes
- Named element count exceeds comfortable limits for one diagram type
- Material has natural subsections that each deserve visual treatment
- Different parts of the material map to different diagram types
For each sub-diagram, determine: focus area, recommended type, named elements, and the "reader understands ___" sentence.
**What to diagram:**
- Core mechanisms the reader needs to *understand* (→ illustrative)
- Multi-step processes described in prose (→ flowchart)
- Multi-actor interactions (→ sequence)
- Architectural descriptions with containment or hierarchy (→ structural)
- Type hierarchies or data models (→ class)
- Comparisons between two approaches or systems (→ structural subsystem)
**What NOT to diagram:**
- Simple lists — a bullet list is already visual enough
- Concepts already shown in an existing image or figure
- Purely emotional or narrative passages with no underlying mechanism
- Content that is a single sentence or trivially simple
- Decorative filler — every diagram must earn its place with a concrete reader need
#### Decision C: Overview diagram
When the plan includes multiple sub-diagrams, assess whether an additional overview diagram that shows the big picture is worthwhile.
| Situation | Decision |
|-----------|----------|
| Sub-diagrams are parts of a coherent system, seeing how they relate adds value | Include an overview diagram (typically structural or illustrative) |
| Sub-diagrams cover independent topics that don't form a coherent whole | Skip the overview |
| Material is simple enough that sub-diagrams already cover everything | Skip the overview |
#### Plan output
Save the plan as `outline.md` (for multiple diagrams) or hold in memory (for single diagram).
**Single-diagram plan format:**
```markdown
## Diagram Plan
**Material**: [source description]
**Diagrams**: 1
**Type**: [type] (rationale)
**Named elements**: [list]
**Reader need**: "After seeing this diagram, the reader understands ___"
**Slug**: [slug]
```
**Multi-diagram plan format:**
```yaml
---
material: [source description]
slug: [material-slug]
diagram_count: N
language: en
---
```
Per-diagram entry:
```markdown
## Diagram 1: [focus area]
**Type**: [type] (rationale)
**Named elements**: [list]
**Reader need**: "After seeing this diagram, the reader understands ___"
**Slug**: [2-4 kebab-case words]
**Filename**: 01-{type}-{slug}/diagram.svg
## Diagram 2: [focus area]
...
## Overview diagram (if applicable)
**Type**: [structural/illustrative]
**Purpose**: Shows how diagrams 1-N relate as parts of a larger system
**Named elements**: [high-level elements]
**Slug**: overview-[slug]
**Filename**: overview-{type}-{slug}/diagram.svg
```
Requirements:
- Each diagram justified by a concrete reader need (the "After seeing this..." sentence)
- Type chosen per the routing table, not arbitrarily
- If input was pasted content, also save it as `source-{slug}.md` in the output directory
### Step 3: Confirm plan (one-time)
**Maximum 1 AskUserQuestion call for the entire workflow.** This is the only confirmation step — no further questions during generation.
| Plan shape | Confirmation |
|------------|-------------|
| Single diagram, obvious type (`--type` given, or clear verb signal) | **No confirmation.** Announce the type in one sentence and proceed to Step 4. |
| Single diagram, ambiguous type (multiple types viable) | **Lightweight.** "The material could work as [type A] (rationale) or [type B] (rationale). Which do you prefer?" |
| Multiple diagrams | **Full plan.** Show the numbered list of all planned diagrams with their types and purposes, plus overview if applicable. User can adjust (add/remove diagrams, change types, toggle overview) in one response. |
Language question: only include if material language differs from user's language and `--lang` is not given.
Example full plan confirmation:
```
I analyzed the material and recommend N diagrams [+ an overview]:
1. [Focus area] — [type] — "Reader understands ___"
2. [Focus area] — [type] — "Reader understands ___"
3. [Focus area] — [type] — "Reader understands ___"
[Overview: [type] — "Shows how 1-N relate as a system"]
Adjust the plan? (add/remove diagrams, change types, skip/add overview)
```
After confirmation (or after skipping confirmation for obvious plans), the plan is locked. Proceed to generation.
Save the finalized plan:
- Multiple diagrams: `diagram/{material-slug}/outline.md`
- Single diagram: plan is saved as `plan.md` beside the SVG in Step 5g
### Step 4: Load shared references
**Always read**:
- `references/design-system.md` — philosophy, typography, color palette, hard rules
- `references/svg-template.md` — the `<style>` + `<defs>` boilerplate to copy verbatim
- `references/layout-math.md` — text-width estimation, viewBox sizing, arrow routing
- `references/pitfalls.md` — the pre-save checklist
Per-type reference files are loaded inside the generation loop (Step 5b) since each diagram may have a different type.
### Step 5: Per-diagram generation loop
For each diagram in the confirmed plan (1 to N, overview diagram generated last):
#### 5a: Capture intent
Read the current diagram's plan entry. Extract or refine these five things from the source material:
1. **Named elements** — list every distinct actor, component, service, state, or phase explicitly named. Count them. If the count is 6+, plan multiple diagrams rather than cramming everything into one (see `flowchart.md` → "Planning before you write SVG"). 1. **Named elements** — list every distinct actor, component, service, state, or phase explicitly named. Count them. If the count is 6+, plan multiple diagrams rather than cramming everything into one (see `flowchart.md` → "Planning before you write SVG").
@@ -121,27 +286,17 @@ Read the user's prompt or content file. If the topic is missing, ask for it with
- Containment ("X is inside Y", zones, hierarchies) → structural signal - Containment ("X is inside Y", zones, hierarchies) → structural signal
- Multi-actor message exchange (A sends to B, B replies to C) → sequence signal - Multi-actor message exchange (A sends to B, B replies to C) → sequence signal
- Mechanism ("how does X produce Y") → illustrative signal - Mechanism ("how does X produce Y") → illustrative signal
More than one type present? Pick the dominant one, or plan two diagrams. More than one type present? Pick the dominant one, or flag for the plan.
3. **What the reader needs** — complete this sentence before routing: *"After seeing this diagram, the reader understands ___."* If you can't finish it, the topic is underspecified — ask. 3. **What the reader needs** — complete this sentence before routing: *"After seeing this diagram, the reader understands ___."* If you can't finish it, the topic is underspecified — ask.
4. **Label preview** — for each element name, count the characters. Latin titles >30 chars (CJK >16) will overflow a 180-wide box and need shortening. Draft the abbreviated form now, before layout math, so Step 4 uses real labels. 4. **Label preview** — for each element name, count the characters. Latin titles >30 chars (CJK >16) will overflow a 180-wide box and need shortening. Draft the abbreviated form now, before layout math, so Step 5d uses real labels.
5. **Language** — CJK vs. Latin. Affects text-width multipliers in Step 4 (15 px/char vs. 8 px/char for titles). Mixed content (CJK labels with some Latin terms) counts as CJK. 5. **Language** — CJK vs. Latin. Affects text-width multipliers in Step 5d (15 px/char vs. 8 px/char for titles). Mixed content (CJK labels with some Latin terms) counts as CJK.
### Step 2: Route the diagram type #### 5b: Load type reference
Match the user's phrasing to the table above. If `--type` is given, use it. Otherwise route on the verb. When genuinely ambiguous between flowchart and illustrative, default to **illustrative** — the reader almost always benefits more from intuition than from a list of steps. The type was determined in the plan. Load the matching reference file.
Tell the user which type you picked and why, in one sentence. Do not ask for confirmation — the type can always be changed with `--type` on a rerun.
### Step 3: Load references
**Always read**:
- `references/design-system.md` — philosophy, typography, color palette, hard rules
- `references/svg-template.md` — the `<style>` + `<defs>` boilerplate to copy verbatim
- `references/layout-math.md` — text-width estimation, viewBox sizing, arrow routing
- `references/pitfalls.md` — the pre-save checklist
**Read the one that matches the type**: **Read the one that matches the type**:
- `references/flowchart.md` - `references/flowchart.md`
@@ -154,36 +309,47 @@ Tell the user which type you picked and why, in one sentence. Do not ask for con
- `references/glyphs.md` — the shared glyph library, tool card icon set, operator icons, and dark-mode rules - `references/glyphs.md` — the shared glyph library, tool card icon set, operator icons, and dark-mode rules
**Read on demand for diagram type extensions:** **Read on demand for diagram type extensions:**
- `references/flowchart-poster.md` — when ≥3 poster-mode triggers fire in Step 4a (topic has a short name, named phases, parallel candidates, a loop termination mechanic, overflow annotations, or a footer quote) - `references/flowchart-poster.md` — when ≥3 poster-mode triggers fire in Step 5d (topic has a short name, named phases, parallel candidates, a loop termination mechanic, overflow annotations, or a footer quote)
- `references/flowchart-phase-bands.md` — when the prompt describes a multi-phase sequential operation where each phase contains parallel tools or steps and outcomes propagate between phases ("phase 1/2/3", "attack phases", "phased workflow with tools") - `references/flowchart-phase-bands.md` — when the prompt describes a multi-phase sequential operation where each phase contains parallel tools or steps and outcomes propagate between phases
- `references/structural-network.md` — when drawing network topology: zone containers, wired/wireless device connectivity, security zones (DMZ / VPC / firewall) - `references/structural-network.md` — when drawing network topology: zone containers, wired/wireless device connectivity, security zones
- `references/structural-matrix.md` — when drawing a comparison matrix: feature table, ✓/✗ cells, side-by-side "option × attribute" grid - `references/structural-matrix.md` — when drawing a comparison matrix: feature table, ✓/✗ cells, side-by-side grid
### Step 3.5: Check the patterns library #### 5c: Check patterns library
If the user's topic matches a known AI-system pattern (multi-agent research, message bus, shared state, agent with skills, contextual retrieval), there is a pre-cooked starter plan in `references/patterns/`. Scan `references/patterns/README.md` for a pattern name that matches the prompt. If one matches, load that pattern file and use its mermaid reference + baoyu SVG plan as the starting point for Step 4 — the node list, widths, and arrow routing are already drafted, and you only need to adapt labels. If the topic matches a known AI-system pattern, there is a pre-cooked starter plan in `references/patterns/`. Scan `references/patterns/README.md` for a pattern name that matches. If one matches, load that pattern file and use its mermaid reference + baoyu SVG plan as the starting point for Step 5d.
If nothing matches, skip this step and plan from scratch in Step 4. Do not force a near-miss: two patterns that share a surface name may have genuinely different topologies, and using the wrong pre-plan is worse than planning from scratch. If nothing matches, skip and plan from scratch in Step 5d. Do not force a near-miss.
### Step 4: Plan on paper first #### 5d: Plan on paper
Before writing any SVG, draft a short layout plan. Do the math once, correctly, so the SVG comes out right on the first pass. Before writing any SVG, draft a short layout plan. Do the math once, correctly, so the SVG comes out right on the first pass.
**4a. Extract structure from the source** — don't just transcribe the user's bullets into boxes. Read the source looking for these elements. Not every element will be present, but every present element should land in the diagram: **5d-0. Draft the Mermaid sketch first** — write a Mermaid code block that captures the **structural intent** of the diagram: which nodes exist, how they connect, what direction they flow, and any grouping (subgraphs). This is the single source of truth for *what* to draw; everything after it (coordinates, widths, arrows) answers *how*.
Rules for the Mermaid sketch:
- Use the Mermaid dialect that best matches the diagram type: `flowchart TD/LR` for flowcharts, `sequenceDiagram` for sequence, `classDiagram` for class, `flowchart` with subgraphs for structural/illustrative.
- Include every node, every edge, every label, and every subgraph/container. If a node won't appear in the Mermaid, it won't appear in the SVG.
- Edge labels must match the final SVG labels — write them now, not later.
- Keep it concise: the sketch is a structural contract, not a rendering. Mermaid can't express baoyu's visual design (colors, rounded rects, dark mode), so don't try — those come in 5d-ii and 5e.
- For patterns that have a Mermaid reference in `references/patterns/`, start from that reference and adapt it to the specific topic.
Save the Mermaid block in the plan file. When writing SVG in Step 5e, **cross-check every node and edge against this Mermaid sketch** — if the sketch has it, the SVG must have it; if the SVG adds something the sketch doesn't have, update the sketch first.
**5d-i. Extract structure from the source** — don't just transcribe bullets into boxes. Read the source looking for these elements. Not every element will be present, but every present element should land in the diagram:
- **Mechanism name** — does the topic have a short, nameable identity (Autoreason, AutoResearch, OAuth, JWT auth, Reflexion loop)? If yes, that's a candidate `.title`. - **Mechanism name** — does the topic have a short, nameable identity (Autoreason, AutoResearch, OAuth, JWT auth, Reflexion loop)? If yes, that's a candidate `.title`.
- **Framing question** — does the source contain a "why does this exist" sentence? Usually in the form *"Problem X has no Y, so we built Z to …"*. That's a candidate subtitle. - **Framing question** — does the source contain a "why does this exist" sentence? That's a candidate subtitle.
- **Phases** — do the stages naturally cluster into 24 named groups (setup / loop body / judgment / convergence)? Each cluster is a candidate `.eyebrow` section. - **Phases** — do the stages naturally cluster into 24 named groups? Each cluster is a candidate `.eyebrow` section.
- **Anchor inputs** — is there a constant input (the task prompt, a dataset, a knowledge base) that every stage references? That's a candidate anchor box above the main flow. - **Anchor inputs** — is there a constant input (the task prompt, a dataset, a knowledge base) that every stage references? That's a candidate anchor box above the main flow.
- **Parallel candidates** — at some point, does the process generate N alternatives that are then compared? "Produce A, B, AB and pick the best" is a candidate fan-out + judge pattern. **Watch for the implicit "keep unchanged" candidate** — many iterative methods include "the incumbent wins by default" as one of the options, even when the source doesn't name it explicitly. - **Parallel candidates** — at some point, does the process generate N alternatives that are then compared? **Watch for the implicit "keep unchanged" candidate.**
- **Loop scope + termination** — which boxes are inside a loop that repeats? What is the *specific* termination rule — streak counter, convergence check, iteration cap, quality threshold? That's a candidate left-rail loop bracket + a dedicated termination box. - **Loop scope + termination** — which boxes are inside a loop that repeats? What is the *specific* termination rule? That's a candidate left-rail loop bracket + a dedicated termination box.
- **Per-box context that won't fit in a subtitle**"sees task + A + critique / adversarial / fresh context" is too long for a 5-word subtitle but too important to drop. Those are candidate right-column `.anno` annotations. - **Per-box context that won't fit in a subtitle**those are candidate right-column `.anno` annotations.
- **Quotable hook** — does the source end with a test result, a quote, or a memorable framing? That's a candidate footer `.caption`. - **Quotable hook** — does the source end with a test result, a quote, or a memorable framing? That's a candidate footer `.caption`.
- **Role categories** — how many *distinct kinds* of operation does the process have (draft / critique / synthesize / judge)? This determines the color budget. Identity is a category, not a sequence. - **Role categories** — how many *distinct kinds* of operation does the process have? This determines the color budget. Identity is a category, not a sequence.
Write the answers to these in the plan file. If ≥3 of them land, you're building a **poster flowchart** — load `references/flowchart-poster.md` and follow its coordinate budget. Otherwise, it's a simple flowchart and the linear-top-down pattern applies. Write the answers to these in the plan file. If ≥3 of them land, you're building a **poster flowchart** — load `references/flowchart-poster.md` and follow its coordinate budget. Otherwise, it's a simple flowchart and the linear-top-down pattern applies.
**4b. Draft the layout:** **5d-ii. Draft the layout:**
1. **List the nodes / regions / shapes** with their full label text (title + optional subtitle). 1. **List the nodes / regions / shapes** with their full label text (title + optional subtitle).
- Simple flowchart: ≤5 nodes. - Simple flowchart: ≤5 nodes.
@@ -202,9 +368,13 @@ Write the answers to these in the plan file. If ≥3 of them land, you're buildi
5. **Map arrows** and verify none cross an unrelated box. Use L-bends where a straight line would collide. (Sequence messages are always straight horizontal lines — no L-bends. Fan-out candidates converge to a common `ymid` channel just above the judge box.) 5. **Map arrows** and verify none cross an unrelated box. Use L-bends where a straight line would collide. (Sequence messages are always straight horizontal lines — no L-bends. Fan-out candidates converge to a common `ymid` channel just above the judge box.)
6. **Compute viewBox height**: `H = max_y + 20` where `max_y` is the bottom of the lowest element. Poster flowcharts routinely reach H=800950 — don't force them to be compact. 6. **Compute viewBox height**: `H = max_y + 20` where `max_y` is the bottom of the lowest element. Poster flowcharts routinely reach H=800950 — don't force them to be compact.
Save this plan to `diagram/{slug}/plan.md` so iteration runs can re-read it. Save this plan (including the Mermaid sketch from 5d-0):
- One diagram: `diagram/{slug}/plan.md`
- Multiple diagrams: `diagram/{material-slug}/NN-{type}-{slug}/plan.md`
### Step 5: Write the SVG #### 5e: Write the SVG
**Start from the Mermaid sketch in the plan.** Walk the sketch node-by-node, edge-by-edge, and translate each element into SVG using the coordinates and widths computed in 5d-ii. The Mermaid sketch is the structural checklist — every node and edge in it must appear in the SVG. If you find yourself adding an element that isn't in the sketch, stop and update the sketch first so the plan stays authoritative.
Emit a single `<svg width="100%" viewBox="0 0 680 H">` element. Copy the `<style>` + `<defs>` block from `svg-template.md` **verbatim** — don't abbreviate or edit the color ramp definitions. Then add visual elements in z-order: Emit a single `<svg width="100%" viewBox="0 0 680 H">` element. Copy the `<style>` + `<defs>` block from `svg-template.md` **verbatim** — don't abbreviate or edit the color ramp definitions. Then add visual elements in z-order:
@@ -220,7 +390,9 @@ Typography rules:
- Sentence case everywhere — "User login" not "User Login" - Sentence case everywhere — "User login" not "User Login"
- Every `<text>` element gets a class (`t`, `ts`, or `th`) — never hardcode fill colors on text - Every `<text>` element gets a class (`t`, `ts`, or `th`) — never hardcode fill colors on text
### Step 6: Run the pre-save checklist #### 5f: Run the pre-save checklist
**MermaidSVG consistency check** (run before the pitfalls checklist): re-read the Mermaid sketch from the plan. For every node in the sketch, confirm the SVG has a corresponding `<rect>` + `<text>`. For every edge, confirm a `<path>` or `<line>` connects the correct pair. Missing elements are bugs — fix them before continuing.
Walk through every item in `references/pitfalls.md`. The top failures to catch every time: Walk through every item in `references/pitfalls.md`. The top failures to catch every time:
@@ -236,25 +408,19 @@ Walk through every item in `references/pitfalls.md`. The top failures to catch e
If any item fails, fix the SVG before saving. Don't rationalize past a failure — the checklist exists because these bugs are silent: the SVG is valid but looks wrong when rendered. If any item fails, fix the SVG before saving. Don't rationalize past a failure — the checklist exists because these bugs are silent: the SVG is valid but looks wrong when rendered.
### Step 7: Save output #### 5g: Save and report progress
If `--out` is given, save the SVG there and skip the scaffolded directory. Otherwise use the default layout: Save the SVG and plan:
- One diagram: `diagram/{slug}/plan.md` + `diagram.svg`
- Multiple diagrams: `diagram/{material-slug}/NN-{type}-{slug}/plan.md` + `diagram.svg`
``` **Backup rule**: if `diagram.svg` already exists at the target path, rename the existing one to `diagram-backup-YYYYMMDD-HHMMSS.svg` before writing the new file — never overwrite prior work silently.
diagram/{topic-slug}/
├── source-{slug}.md # optional: user's input content if provided
├── plan.md # layout sketch from Step 4
└── diagram.svg # final output
```
- **Slug**: 24 kebab-case words derived from the topic. **Multiple diagrams progress**: after each diagram, report progress: "Generated 2/4: 02-illustrative-jwt-token-structure".
- **Backup rule**: if `diagram.svg` already exists at the target path, rename the existing one to `diagram-backup-YYYYMMDD-HHMMSS.svg` before writing the new file — never overwrite prior work silently.
- **Plan**: always save `plan.md` from Step 4 beside the SVG so the next iteration can re-read it.
- **Source**: if the user pasted source content, save it as `source-{slug}.md` in the same directory.
### Step 8: Report ### Step 6: Report
Tell the user in 4-6 lines: **One diagram** — tell the user in 4-6 lines:
- Diagram type picked (and one-sentence why) - Diagram type picked (and one-sentence why)
- Node count / complexity - Node count / complexity
- viewBox dimensions - viewBox dimensions
@@ -262,10 +428,76 @@ Tell the user in 4-6 lines:
- Output file path - Output file path
- One suggestion for how to preview it (e.g., "Open in Chrome for light/dark check") - One suggestion for how to preview it (e.g., "Open in Chrome for light/dark check")
**Multiple diagrams**:
```
Diagram Generation Complete!
Material: [source description]
Language: [lang]
Diagrams: X generated
Results:
- 01-sequence-jwt-auth-flow — "Reader understands the auth handshake"
- 02-illustrative-jwt-token-structure — "Reader understands token anatomy"
- 03-flowchart-token-refresh — "Reader understands the refresh cycle"
[- overview-structural-jwt-system — "Reader sees how all parts connect"]
Output: diagram/{material-slug}/
Preview: Open any .svg in Chrome for light/dark check
```
## Output structure
### One diagram
```
diagram/{slug}/
├── source-{slug}.md # optional: saved input material
├── plan.md # layout sketch from Step 5d
└── diagram.svg # final output
```
### Multiple diagrams
```
diagram/{material-slug}/
├── source-{slug}.md # saved input material
├── outline.md # plan from Step 2 with all diagram entries
├── 01-{type}-{slug}/
│ ├── plan.md # layout sketch for this diagram
│ └── diagram.svg # final SVG
├── 02-{type}-{slug}/
│ ├── plan.md
│ └── diagram.svg
├── 03-{type}-{slug}/
│ ├── plan.md
│ └── diagram.svg
└── overview-{type}-{slug}/ # optional: overview diagram
├── plan.md
└── diagram.svg
```
- **Slug**: 24 kebab-case words derived from the topic or concept.
- **Backup rule**: if `diagram.svg` already exists at the target path, rename the existing one to `diagram-backup-YYYYMMDD-HHMMSS.svg` before writing the new file.
- **Plan**: always save `plan.md` beside the SVG so the next iteration can re-read it.
- **Source**: if the user pasted source content, save it as `source-{slug}.md` in the output directory.
- **Numbering**: NN prefix (01, 02, ...) matches the plan order.
- **Outline**: when generating multiple diagrams, always save `outline.md` from Step 2 so the generation can be resumed or individual diagrams can be regenerated.
## Modification
| Action | Steps |
|--------|-------|
| **Regenerate one diagram** | Re-read `outline.md` → find the entry → re-run Step 5 for that diagram only → update the SVG |
| **Add a diagram** | Identify focus area → add entry to `outline.md` → run Step 5 for the new entry |
| **Remove a diagram** | Delete the `NN-{type}-{slug}/` directory → remove entry from `outline.md` |
| **Change type** | Update the plan entry or re-run with `--type` → regenerate |
## Core principles ## Core principles
- **Draw the mechanism, not a diagram about the mechanism** (illustrative). **Draw the sequence, not the architecture** (flowchart). **Draw the containment, not the flow** (structural). **Draw the conversation, not the steps** (sequence). Picking the wrong type is the single biggest failure mode — more harmful than any layout bug. - **Draw the mechanism, not a diagram about the mechanism** (illustrative). **Draw the sequence, not the architecture** (flowchart). **Draw the containment, not the flow** (structural). **Draw the conversation, not the steps** (sequence). Picking the wrong type is the single biggest failure mode — more harmful than any layout bug.
- **One design system, always.** No `--style` flag, no alternate themes, no per-topic visual variants. The cohesive look across every diagram is the product — if a reader sees two baoyu diagrams in different articles, they should feel they came from the same hand. Any request to "use a different style" is a request to break this principle; push back and ask what the underlying need is instead. - **One design system, always.** No `--style` flag, no alternate themes, no per-topic visual variants. The cohesive look across every diagram is the product — if a reader sees two baoyu diagrams in different articles, they should feel they came from the same hand. Any request to "use a different style" is a request to break this principle; push back and ask what the underlying need is instead. All diagrams in a run share the same design system — no per-diagram style overrides.
- **Self-contained output.** Every SVG carries its own styles and dark-mode rules. The reader should never need to edit anything after pasting it into their article. - **Self-contained output.** Every SVG carries its own styles and dark-mode rules. The reader should never need to edit anything after pasting it into their article.
- **Math before markup.** SVG has no auto-layout. Every coordinate is hand-computed. A diagram that "almost fits" has a bug — fix the math, don't nudge pixels. - **Math before markup.** SVG has no auto-layout. Every coordinate is hand-computed. A diagram that "almost fits" has a bug — fix the math, don't nudge pixels.
- **Color encodes meaning, not position.** Five steps in a flowchart are not five colors. All five are gray unless one specific step deserves emphasis — in which case it gets the accent color. - **Color encodes meaning, not position.** Five steps in a flowchart are not five colors. All five are gray unless one specific step deserves emphasis — in which case it gets the accent color.
@@ -22,7 +22,7 @@ Vibrant neon colors on dark background. High-energy, futuristic, eye-catching.
## Semantic Constraint ## Semantic Constraint
Vibrant neon color palette on dark background. Colors should glow against the dark base. High contrast, futuristic feel. Use neon sparingly — too many glowing elements become chaotic. Let dark background breathe. Vibrant neon color palette on dark background. Colors should glow against the dark base. High contrast, futuristic feel. Use neon sparingly — too many glowing elements become chaotic. Let dark background breathe. Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best Paired With ## Best Paired With
@@ -22,7 +22,7 @@ Warm earth tones on soft peach background. Cozy, inviting, no cool colors.
## Semantic Constraint ## Semantic Constraint
Warm-only color palette, no cool colors (no blue, green, purple). Earth tones throughout. Evokes comfort, warmth, and trust. All colors should feel like autumn sunlight. Warm-only color palette, no cool colors (no blue, green, purple). Earth tones throughout. Evokes comfort, warmth, and trust. All colors should feel like autumn sunlight. Do NOT render color names, hex codes, or role labels as visible text in the image.
## Best Paired With ## Best Paired With
@@ -46,6 +46,45 @@ export function stripWrappingQuotes(value: string): string {
return value.trim(); return value.trim();
} }
const HTML_ENTITIES: Record<string, string> = {
amp: "&",
apos: "'",
gt: ">",
lt: "<",
nbsp: " ",
quot: '"',
};
function decodeHtmlCodePoint(codePoint: number, fallback: string): string {
if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
return fallback;
}
return String.fromCodePoint(codePoint);
}
function decodeHtmlEntities(value: string): string {
return value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (entity, body: string) => {
const normalized = body.toLowerCase();
if (normalized.startsWith("#x")) {
return decodeHtmlCodePoint(Number.parseInt(normalized.slice(2), 16), entity);
}
if (normalized.startsWith("#")) {
return decodeHtmlCodePoint(Number.parseInt(normalized.slice(1), 10), entity);
}
return HTML_ENTITIES[normalized] ?? entity;
});
}
export function cleanSummaryText(value: string): string {
return decodeHtmlEntities(stripWrappingQuotes(value))
.replace(/<script\b[\s\S]*?<\/script>/gi, " ")
.replace(/<style\b[\s\S]*?<\/style>/gi, " ")
.replace(/<br\s*\/?>/gi, " ")
.replace(/<\/?[a-z][a-z0-9:-]*(?:\s+[^>]*)?>/gi, " ")
.replace(/\s+/g, " ")
.trim();
}
export function toFrontmatterString(value: unknown): string | undefined { export function toFrontmatterString(value: unknown): string | undefined {
if (typeof value === "string") { if (typeof value === "string") {
return stripWrappingQuotes(value); return stripWrappingQuotes(value);
@@ -94,10 +133,11 @@ export function extractSummaryFromBody(body: string, maxLen: number): string {
.replace(/\*(.+?)\*/g, "$1") .replace(/\*(.+?)\*/g, "$1")
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/`([^`]+)`/g, "$1"); .replace(/`([^`]+)`/g, "$1");
const summaryText = cleanSummaryText(cleanText);
if (cleanText.length > 20) { if (summaryText.length > 20) {
if (cleanText.length <= maxLen) return cleanText; if (summaryText.length <= maxLen) return summaryText;
return `${cleanText.slice(0, maxLen - 3)}...`; return `${summaryText.slice(0, maxLen - 3)}...`;
} }
} }
@@ -45,19 +45,24 @@ export function loadCodeThemeCss(themeName: string): string {
} }
export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string { export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string {
const escapeHtmlAttribute = (value: string) => value
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const lines = [ const lines = [
"<!doctype html>", "<!doctype html>",
"<html>", "<html>",
"<head>", "<head>",
' <meta charset="utf-8" />', ' <meta charset="utf-8" />',
' <meta name="viewport" content="width=device-width, initial-scale=1" />', ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
` <title>${meta.title}</title>`, ` <title>${escapeHtmlAttribute(meta.title)}</title>`,
]; ];
if (meta.author) { if (meta.author) {
lines.push(` <meta name="author" content="${meta.author}" />`); lines.push(` <meta name="author" content="${escapeHtmlAttribute(meta.author)}" />`);
} }
if (meta.description) { if (meta.description) {
lines.push(` <meta name="description" content="${meta.description}" />`); lines.push(` <meta name="description" content="${escapeHtmlAttribute(meta.description)}" />`);
} }
lines.push(` <style>${css}</style>`); lines.push(` <style>${css}</style>`);
if (codeThemeCss) { if (codeThemeCss) {
@@ -4,6 +4,7 @@ import path from "node:path";
import process from "node:process"; import process from "node:process";
import { import {
cleanSummaryText,
extractSummaryFromBody, extractSummaryFromBody,
extractTitleFromMarkdown, extractTitleFromMarkdown,
parseFrontmatter, parseFrontmatter,
@@ -47,8 +48,9 @@ export async function convertMarkdown(
} }
const author = stripWrappingQuotes(frontmatter.author ?? ""); const author = stripWrappingQuotes(frontmatter.author ?? "");
let summary = stripWrappingQuotes(frontmatter.description ?? "") const frontmatterSummary = stripWrappingQuotes(frontmatter.description ?? "")
|| stripWrappingQuotes(frontmatter.summary ?? ""); || stripWrappingQuotes(frontmatter.summary ?? "");
let summary = cleanSummaryText(frontmatterSummary);
if (!summary) { if (!summary) {
summary = extractSummaryFromBody(body, 120); summary = extractSummaryFromBody(body, 120);
} }
@@ -46,6 +46,45 @@ export function stripWrappingQuotes(value: string): string {
return value.trim(); return value.trim();
} }
const HTML_ENTITIES: Record<string, string> = {
amp: "&",
apos: "'",
gt: ">",
lt: "<",
nbsp: " ",
quot: '"',
};
function decodeHtmlCodePoint(codePoint: number, fallback: string): string {
if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
return fallback;
}
return String.fromCodePoint(codePoint);
}
function decodeHtmlEntities(value: string): string {
return value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (entity, body: string) => {
const normalized = body.toLowerCase();
if (normalized.startsWith("#x")) {
return decodeHtmlCodePoint(Number.parseInt(normalized.slice(2), 16), entity);
}
if (normalized.startsWith("#")) {
return decodeHtmlCodePoint(Number.parseInt(normalized.slice(1), 10), entity);
}
return HTML_ENTITIES[normalized] ?? entity;
});
}
export function cleanSummaryText(value: string): string {
return decodeHtmlEntities(stripWrappingQuotes(value))
.replace(/<script\b[\s\S]*?<\/script>/gi, " ")
.replace(/<style\b[\s\S]*?<\/style>/gi, " ")
.replace(/<br\s*\/?>/gi, " ")
.replace(/<\/?[a-z][a-z0-9:-]*(?:\s+[^>]*)?>/gi, " ")
.replace(/\s+/g, " ")
.trim();
}
export function toFrontmatterString(value: unknown): string | undefined { export function toFrontmatterString(value: unknown): string | undefined {
if (typeof value === "string") { if (typeof value === "string") {
return stripWrappingQuotes(value); return stripWrappingQuotes(value);
@@ -94,10 +133,11 @@ export function extractSummaryFromBody(body: string, maxLen: number): string {
.replace(/\*(.+?)\*/g, "$1") .replace(/\*(.+?)\*/g, "$1")
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/`([^`]+)`/g, "$1"); .replace(/`([^`]+)`/g, "$1");
const summaryText = cleanSummaryText(cleanText);
if (cleanText.length > 20) { if (summaryText.length > 20) {
if (cleanText.length <= maxLen) return cleanText; if (summaryText.length <= maxLen) return summaryText;
return `${cleanText.slice(0, maxLen - 3)}...`; return `${summaryText.slice(0, maxLen - 3)}...`;
} }
} }
@@ -45,19 +45,24 @@ export function loadCodeThemeCss(themeName: string): string {
} }
export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string { export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string {
const escapeHtmlAttribute = (value: string) => value
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const lines = [ const lines = [
"<!doctype html>", "<!doctype html>",
"<html>", "<html>",
"<head>", "<head>",
' <meta charset="utf-8" />', ' <meta charset="utf-8" />',
' <meta name="viewport" content="width=device-width, initial-scale=1" />', ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
` <title>${meta.title}</title>`, ` <title>${escapeHtmlAttribute(meta.title)}</title>`,
]; ];
if (meta.author) { if (meta.author) {
lines.push(` <meta name="author" content="${meta.author}" />`); lines.push(` <meta name="author" content="${escapeHtmlAttribute(meta.author)}" />`);
} }
if (meta.description) { if (meta.description) {
lines.push(` <meta name="description" content="${meta.description}" />`); lines.push(` <meta name="description" content="${escapeHtmlAttribute(meta.description)}" />`);
} }
lines.push(` <style>${css}</style>`); lines.push(` <style>${css}</style>`);
if (codeThemeCss) { if (codeThemeCss) {
@@ -194,6 +194,54 @@ async function pasteFromClipboardInEditor(session: ChromeSession): Promise<void>
await sleep(1000); await sleep(1000);
} }
async function prepareEditorPasteTarget(
session: ChromeSession,
context: string,
options: { clickEditor?: boolean } = {},
): Promise<void> {
await session.cdp.send('Target.activateTarget', { targetId: session.targetId }).catch(() => {});
await sleep(100);
if (options.clickEditor) {
await clickElement(session, '.ProseMirror');
await sleep(200);
}
const ready = await evaluate<boolean>(session, `
(function() {
const editor = document.querySelector('.ProseMirror');
if (!editor) return false;
const active = document.activeElement;
const selection = window.getSelection();
const selectionInEditor = !!selection && selection.rangeCount > 0 && !!selection.anchorNode && editor.contains(selection.anchorNode);
const focusInEditor = !!active && (active === editor || editor.contains(active));
if (selectionInEditor || focusInEditor) return true;
if (${JSON.stringify(Boolean(options.clickEditor))}) {
editor.focus();
const nextActive = document.activeElement;
return nextActive === editor || editor.contains(nextActive);
}
return false;
})()
`);
if (ready) return;
const activeElement = await evaluate<string>(session, `
(function() {
const el = document.activeElement;
if (!el) return '(none)';
const id = el.id ? '#' + el.id : '';
const className = typeof el.className === 'string' && el.className ? '.' + el.className.split(/\\s+/).join('.') : '';
return el.tagName.toLowerCase() + id + className;
})()
`);
throw new Error(`Body editor is not focused before ${context}; active element: ${activeElement}`);
}
async function parseMarkdownWithPlaceholders( async function parseMarkdownWithPlaceholders(
markdownPath: string, markdownPath: string,
theme?: string, theme?: string,
@@ -567,6 +615,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
console.log(`[wechat] Copying HTML content from: ${effectiveHtmlFile}`); console.log(`[wechat] Copying HTML content from: ${effectiveHtmlFile}`);
await copyHtmlFromBrowser(cdp, effectiveHtmlFile, contentImages); await copyHtmlFromBrowser(cdp, effectiveHtmlFile, contentImages);
await sleep(500); await sleep(500);
await prepareEditorPasteTarget(session, 'body content paste', { clickEditor: true });
console.log('[wechat] Pasting into editor...'); console.log('[wechat] Pasting into editor...');
await pasteFromClipboardInEditor(session); await pasteFromClipboardInEditor(session);
await sleep(3000); await sleep(3000);
@@ -608,6 +657,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
await sleep(200); await sleep(200);
console.log('[wechat] Pasting image...'); console.log('[wechat] Pasting image...');
await prepareEditorPasteTarget(session, 'inline image paste');
await pasteFromClipboardInEditor(session); await pasteFromClipboardInEditor(session);
await sleep(3000); await sleep(3000);
await removeExtraEmptyLineAfterImage(session); await removeExtraEmptyLineAfterImage(session);
@@ -620,6 +670,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
console.log(`[wechat] Pasting image: ${img}`); console.log(`[wechat] Pasting image: ${img}`);
await copyImageToClipboard(img); await copyImageToClipboard(img);
await sleep(500); await sleep(500);
await prepareEditorPasteTarget(session, 'leading image paste');
await pasteInEditor(session); await pasteInEditor(session);
await sleep(2000); await sleep(2000);
await removeExtraEmptyLineAfterImage(session); await removeExtraEmptyLineAfterImage(session);
@@ -627,6 +678,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
} }
console.log('[wechat] Typing content...'); console.log('[wechat] Typing content...');
await prepareEditorPasteTarget(session, 'content typing');
await typeText(session, content); await typeText(session, content);
await sleep(1000); await sleep(1000);
@@ -46,6 +46,45 @@ export function stripWrappingQuotes(value: string): string {
return value.trim(); return value.trim();
} }
const HTML_ENTITIES: Record<string, string> = {
amp: "&",
apos: "'",
gt: ">",
lt: "<",
nbsp: " ",
quot: '"',
};
function decodeHtmlCodePoint(codePoint: number, fallback: string): string {
if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
return fallback;
}
return String.fromCodePoint(codePoint);
}
function decodeHtmlEntities(value: string): string {
return value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (entity, body: string) => {
const normalized = body.toLowerCase();
if (normalized.startsWith("#x")) {
return decodeHtmlCodePoint(Number.parseInt(normalized.slice(2), 16), entity);
}
if (normalized.startsWith("#")) {
return decodeHtmlCodePoint(Number.parseInt(normalized.slice(1), 10), entity);
}
return HTML_ENTITIES[normalized] ?? entity;
});
}
export function cleanSummaryText(value: string): string {
return decodeHtmlEntities(stripWrappingQuotes(value))
.replace(/<script\b[\s\S]*?<\/script>/gi, " ")
.replace(/<style\b[\s\S]*?<\/style>/gi, " ")
.replace(/<br\s*\/?>/gi, " ")
.replace(/<\/?[a-z][a-z0-9:-]*(?:\s+[^>]*)?>/gi, " ")
.replace(/\s+/g, " ")
.trim();
}
export function toFrontmatterString(value: unknown): string | undefined { export function toFrontmatterString(value: unknown): string | undefined {
if (typeof value === "string") { if (typeof value === "string") {
return stripWrappingQuotes(value); return stripWrappingQuotes(value);
@@ -94,10 +133,11 @@ export function extractSummaryFromBody(body: string, maxLen: number): string {
.replace(/\*(.+?)\*/g, "$1") .replace(/\*(.+?)\*/g, "$1")
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/`([^`]+)`/g, "$1"); .replace(/`([^`]+)`/g, "$1");
const summaryText = cleanSummaryText(cleanText);
if (cleanText.length > 20) { if (summaryText.length > 20) {
if (cleanText.length <= maxLen) return cleanText; if (summaryText.length <= maxLen) return summaryText;
return `${cleanText.slice(0, maxLen - 3)}...`; return `${summaryText.slice(0, maxLen - 3)}...`;
} }
} }
@@ -45,19 +45,24 @@ export function loadCodeThemeCss(themeName: string): string {
} }
export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string { export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string {
const escapeHtmlAttribute = (value: string) => value
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const lines = [ const lines = [
"<!doctype html>", "<!doctype html>",
"<html>", "<html>",
"<head>", "<head>",
' <meta charset="utf-8" />', ' <meta charset="utf-8" />',
' <meta name="viewport" content="width=device-width, initial-scale=1" />', ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
` <title>${meta.title}</title>`, ` <title>${escapeHtmlAttribute(meta.title)}</title>`,
]; ];
if (meta.author) { if (meta.author) {
lines.push(` <meta name="author" content="${meta.author}" />`); lines.push(` <meta name="author" content="${escapeHtmlAttribute(meta.author)}" />`);
} }
if (meta.description) { if (meta.description) {
lines.push(` <meta name="description" content="${meta.description}" />`); lines.push(` <meta name="description" content="${escapeHtmlAttribute(meta.description)}" />`);
} }
lines.push(` <style>${css}</style>`); lines.push(` <style>${css}</style>`);
if (codeThemeCss) { if (codeThemeCss) {