A layout is a declarative themeJson(color palette + inline-style template for each tag); the renderer uses it to deterministically render
Markdown into WeChat inline-style HTML. Generate / modify a valid themeJson from the description,
self-check + preview locally, thensave it back to the server as the default; when publishing is pinned to --theme / config.mdTheme , also save a local file.
Rendering / saving themes are bothfree and do not deduct credits。
⚠️ Four hard red lines (read first, uphold throughout)
Red line 1 · WeChat compatibility: heading decorations can only be attached to the text-bearing element itself
The WeChat Official Account editor will strip out empty <section>/<span>that have no text and rely purely on inline-block width/height + background to hold a color block.
Heading lead bars / emphasis vertical bars / underlinesmust be written directly as inline style on the heading element itself,Neveruse standalone empty decoration blocks.
- ✅ Correct:
elements.h2.stylewrite directly inborder-left:4px solid {{accent}};border-bottom:1px solid {{border}};padding:0 0 8px 11px; - ❌ Wrong: use
wrapBeforeto inject an empty<section style='width:32px;height:4px;background:…'>as a color bar (no text → gets stripped).
Red line 2 · WeChat compatibility: do not wrap the whole article in a decorations.articleWrap outer frame
decorations.articleWrap A light-background rounded card wrapping the entire body will become anabrupt border。
articleWrap.before / after leave it as an empty string; achieve "breathing room" with paragraph spacing (margin / line-height) and localized cards (such as a rounded blockquote card).
Red line 3 · Safety: themeJson gets inlined into the WeChat draft, and the server will reject invalid themes
The server POST/PUT/render will all validateTheme, but directly 400. After generating, first run
scripts/validate-theme.mjs self-check — the specific validation rules (top-level keys,
required palette/page fields, forbidden style list, size limit) are governed by it; do not keep a separate copy of the list here that would drift.
The official WeChat editor spec has eight more patterns that are "not rejected but render incorrectly" (position、text-align:start, fixed pixel widths, custom font stacks, gradients under text…);
see the end of references/theme-schema.md for the list and readability baseline; the validator issues warnings for four of them.
Red line 4 · Data safety: before making changes, you must back up to disk; no backup, no changes
Changing a theme is anin-place overwrite(PUT / POST + isDefault:true); the server keeps no historical versions, so if you break it there is no second place to recover it from.
The GET command in step 1 already includes the disk backup in the command itself —copy it verbatim without deleting; note down the backup file path, and if you break it, follow
references/deploy-scope.mdthe "Rollback" section to PUT it back.
Contract: server-side theme CRUD + rendering (based on render-routes.ts in the doubaoya.com main repo)
⚠️ Read dby-gateway/references/protocol.md first before sending requests (authentication, how to get the key,
envelope and error codes are all in that one). This skill's own six theme routes (GET/POST /api/wechat/theme、
GET /api/wechat/themes、POST /api/wechat/render、PUT/DELETE /api/wechat/theme/:id)
request bodies and responses → read references/crud.md; skip it if not needed.
export DOUBAOYA_API_KEY="dyh_你的密钥"
BASE="${DOUBAOYA_BASE_URL:-https://doubaoya.com}"
AUTH="Authorization: Bearer $DOUBAOYA_API_KEY"
render's theme resolution priority themeJson(if passed, use this one) > themeId >
My default theme > fallback benya-clean, sothe theme being previewed or edited is passed as themeJson;
one default per person,isDefault:true when the server, in a transaction, flips all my other themes' isDefault to the opposite.
Closed-loop steps
1. Read the starting theme and back it up to disk (red line 4, copy it as-is, don't delete)
# a) 读我当前的默认主题(有就在它上面改)并强制落盘备份——主题体在 data.theme.themeJson
BACKUP="/tmp/dby-theme-backup-$(date +%s).json"
curl -s -H "$AUTH" "$BASE/api/wechat/theme" | tee "$BACKUP"
echo "已备份到 $BACKUP —— 改坏了见 references/deploy-scope.md「回滚」一节"
# b) 或列出内置主题,挑一个复制起步(推荐 benya-clean——已按红线 1 / 2 改造过)
curl -s -H "$AUTH" "$BASE/api/wechat/themes"
This skill also ships a WeChat-safe starter template
themes/theme.example.json(warm-orange editorial style; just copy it and change the color scheme).
2. Modify themeJson according to the user's colloquial description
Translate "make it warm orange / headings need a vertical bar / make quotes into cards / make the text bigger / make the layout look like so-and-so's account" into valid themeJson.
For the full field list see references/theme-schema.md; stick to the four red lines above. Changing the color scheme is easiest:only touch palette eight keys。
3. Self-check locally first
Write the generated theme to a local file and run it through this skill's built-in zero-dependency validator:
node scripts/validate-theme.mjs /tmp/my-theme.json # 有硬错误按提示改;exit 0 = 合法
4. Preview (change one version, look at one version)
Render via API → save locally as .html and open:
# 用编辑中的 themeJson 渲染样例文章,拿回 HTML 存本地打开肉眼看
curl -s -H "$AUTH" -H 'Content-Type: application/json' \
-d "$(jq -n --arg md "$(cat sample.md)" --slurpfile t /tmp/my-theme.json \
'{markdown:$md, title:"预览", themeJson:$t[0]}')" \
"$BASE/api/wechat/render" | jq -r '.data.html' > /tmp/preview.html
open /tmp/preview.html # macOS;别的平台用浏览器打开
sample.mddon't include#level-1 heading:titlewill be inserted as an h1, and if the body has another one, two big headings show up. The HTML returned by render and the body text finally saved into the draftare identical word for word。data.warningsif there is any (such as unknown{{token}}) take a look as well.
→ If this machine already has dby-publish installed and you want to preview locally with "edit the file and rerun to reskin", read references/live-preview.md,
if you don't need it, don't read it. For modifying themeJson itself, go by the API rendering route above.
5. ⚠️ Effective scope of persistence (theme source for the two rendering routes)
→ When the theme is tuned and you want to save it back, read references/deploy-scope.md—it answers "after saving, which route will change and
which won't," and gives the complete commands for saving back to the server / writing to a local file / rolling back if you break it.
Saving back to the server and setting it as default is the main route(POST/PUT + isDefault:true): doubaoya.com web layout
studio,POST /api/wechat/render, andthose that haven't pinned a local theme's dby-publish publishingall read it.
Still need to write a local theme fileonly in three cases: when publishing with --theme / config.mdThemepinned,
when offline or fetch fails and you need a fallback, or when using an old pipeline that lacks auto-fetch.
After writing it, when reporting to the userstate each one's effective scope truthfully, don't mix them up。
themeJson structure (quick reference)
→ When you actually need to write / modify themeJson fields, read references/theme-schema.md(top-level keys,
elements supported tags,{{token}} interpolation,li.marker / img.figureStyle / hr.html these positions),
if you don't need it, don't read it.If you're only changing the color scheme, modify palette eight keys is enough, the rest automatically follow via {{token}} .
The theme contract is governed by this package's
scripts/validate-theme.mjs(run it, don't rely on the docs); this skill'sreferences/theme-schema.md+themes/theme.example.jsonis enough to independently complete one theme change.
Next step (first see what end state the user wants)
Don't push forward by default—stop at whichever stage the user's desired end state is at:
| The end state the user wants | Is this step enough | Next step |
|---|---|---|
| Just want to change the default layout to what they want | ✅ Enough,This is where it ends(takes effect automatically on every future render) | — |
| Also want touse this theme set to lay out a certain article body and save it into your own WeChat Official Account drafts | Not enough | dby-publish(saving back to the server-side default layout will pull it automatically; for a local theme file use --theme to point to it) |
| Already have laid-out image-text content, just want it to go into drafts | Not enough | dby-publish |
| The article body is not written yet | Not enough | dby-api(pull viral article samples on the same theme and then write the article body, via api.gzh.hotArticle) |
| The article body has not passed the prohibited-word check | Not enough | dby-banned-words(compare across three platforms at once, produce a safely rewritten version) |
| Can't say clearly which step to reach | — | dby(hop-by-hop navigation of the WeChat Official Account flywheel) |