跳转至

MkDocs Wiki 写作

本项目用 MkDocs Material / Zensical 渲染 Markdown。每种语法直接演示使用 —— AI 读 markdown 源文件即看到写法,人类访问 wiki 看到渲染效果,一份内容两个视图,不再"写法/效果"双写。

这一页把 MkDocs Material + pymdownx 常用的语法全过一遍,验证 Zensical 是否原样兼容。

1. 选项卡 ===(pymdownx.tabbed)

import pandas as pd
df = pd.read_csv("data.csv")
library(readr)
df <- read_csv("data.csv")
SELECT * FROM data;

2. 提示框 !!!(admonition)

补充说明

这是一段补充说明。

注意

这是警告性内容。

说明

这是信息介绍。

提示

这是操作建议。

完成

这是成功标记。

3. 可折叠块 ???(pymdownx.details)

点击展开:命令参数
  • -h: 人类可读格式
  • -T: 显示文件系统类型
无标题类型也可以
SELECT * FROM table WHERE id = 1;

折叠块里嵌套选项卡

多语言代码示例
print("hello")
cat("hello")

4. 代码注解 # (1)(content.code.annotate)

with open('file.sql') as f:  # (1)
    sql = f.read()
params = {"game_cd": 1041}  # (2)
  1. 说明第一处标注 —— 打开 SQL 文件并读取内容。
  2. 说明第二处标注 —— 传入游戏 ID 参数。

非代码场景下用 { .annotate }

工作时间 9:30-18:30 (1)

  1. 最多可提前 30 分钟下班。

5. 代码行高亮 hl_lines(pymdownx.highlight)

import pandas as pd
from jinja2 import Template
import os, time
os.chdir(os.path.dirname(os.path.abspath(__file__)))
os.environ['TZ'] = 'Asia/Shanghai'

连续行用 -

def main():
    a = 1
    b = 2
    c = a + b
    return c

6. Mermaid 图表(pymdownx.superfences)

流程图:

graph TD
    A[数据采集] --> B{数据类型}
    B -->|结构化| C[写入数据库]
    B -->|非结构化| D[文本处理]

时序图:

sequenceDiagram
    participant 用户
    participant 服务端
    用户->>服务端: 发起请求
    服务端-->>用户: 响应数据

7. 脚注 [^1](footnotes)

文件最大只能 20MB1,单条消息长度有限制2

8. 内联代码与链接

行内 code 高亮、{==高亮文本==}(critic 扩展,本项目未开启,预期不渲染)。

普通链接 Zensical 与自动链接 https://zensical.org/

9. 嵌入交互单页(iframe + 静态 SPA)

需要展示交互效果(组件演示、可视化、小工具)时,把纯客户端的自包含单页放进本 skill 的 assets/,文章里用原生 <iframe> 嵌入。单页随 wiki 一起发布到对象存储,跟站点同域 —— 无跨域限制、国内速度、AI 顺 URL 还能直接 GET 到未压缩的源码读懂实现。下面就是一个活例(React 迷你 CRUD:搜索 / 筛选 / 分页 / 增改删 / 上下架 / toast):

真身在 assets/react-spa-demo.html。硬性规矩:

  • 纯客户端才能这样发布:对象存储是静态托管,带服务端(server functions / API)的应用嵌不了,只能外部部署后跨域 iframe
  • 自包含、不走 CDN:运行时库 vendor 进全站共享docs/vendor/(线上 /vendor/,各 skill 的 demo 复用;不放 docs/assets/ 是因为那会跟主题产物的 site/assets/ 混居),demo 里用根绝对路径 /vendor/xxx.js 引用。本例是 React 18 UMD + htm(React 19 起不再发 UMD 构建,免构建场景钉 18),国内访问不抖、断外网也能跑
  • demo 逻辑不压缩:单页是文本,线上有独立 URL,AI 可直接读。vite 构建产物(base: './' 后整个目录丢进 assets/)也能嵌,但 minified 对 AI 不可读,算二等公民
  • 免 JSX 构建:用 htm 的 tagged template 配 React UMD,零工具链,源码即真身
  • iframe src 用站点根绝对路径、且指到具体 .html 文件/skills/<skill>/assets/xxx.html):目录式 URL 下相对路径容易算错层级;样式上给定固定高度 + loading="lazy"
  • vite 目录产物的 src 同样写到 index.html/skills/<skill>/assets/xxx-demo/index.html),不写目录 URL xxx-demo/:目录 URL 能不能解析到入口取决于托管的索引文档配置,写死文件名才让 demo 的存活与托管配置无关,也让读 md 源的 AI 一眼看到入口文件而不用猜。活例:frontend-styles/reference/ 的 task1–task7 七篇(2026-08-04 从目录 URL 统一补成 index.html
assets/react-spa-demo.html —— 演示单页源码(自包含、未压缩)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>React 单页 demo:商品管理</title>
<!--
  react-spa-demo.html —— 本 wiki「嵌入交互单页」规范(skills/writing/mkdocs-wiki/index.md 第 9 节)的演示真身。

  约定的落实:
  - 纯客户端:全部状态在内存里,无任何服务端依赖,静态托管(对象存储)直接可跑
  - 自包含:运行时库 vendor 在全站共享的 /vendor/(仓库 docs/vendor/)、根绝对路径引用,不走 CDN
    · React 18 UMD(React 19 起官方不再发 UMD 构建,免构建场景钉 18)
    · htm —— 1KB 的 tagged template,替代 JSX,从而不需要 Babel/打包器
  - demo 逻辑不压缩:本文件随 wiki 发布、有独立 URL,AI GET 到即可读懂全部实现

  内容:一个迷你「CRUD 表格」形状(呼应 skills/dashboard 的 open-dashboard 文章)——
  搜索 / 分类筛选 / 分页 / 新增 / 编辑 / 上下架 / 删除 / 统计卡片 / toast 反馈。
-->
<style>
  /* 设计令牌:亮暗两套,跟随系统 prefers-color-scheme */
  :root {
    --bg: #f6f7f9; --card: #ffffff; --fg: #1f2328; --muted: #6a737d;
    --border: #e4e7eb; --primary: #3564d7; --primary-fg: #ffffff; --danger: #c4372c;
    --chip-on-bg: #e3f4e8; --chip-on-fg: #176e34;
    --chip-off-bg: #f0f1f3; --chip-off-fg: #6a737d;
  }
  @media (prefers-color-scheme: dark) {
    :root {
      --bg: #15181c; --card: #1e2227; --fg: #e6e9ec; --muted: #8b949e;
      --border: #333a42; --primary: #5b8bf0; --danger: #e5695f;
      --chip-on-bg: #12351f; --chip-on-fg: #5fca86;
      --chip-off-bg: #2a2f36; --chip-off-fg: #8b949e;
    }
  }
  * { box-sizing: border-box; }
  body {
    margin: 0; background: var(--bg); color: var(--fg);
    font: 14px/1.6 system-ui, "PingFang SC", "Microsoft YaHei", sans-serif;
  }
  .page { max-width: 880px; margin: 0 auto; padding: 20px 16px 28px; }

  .head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
  .head h1 { margin: 0; font-size: 20px; }
  .sub { margin: 2px 0 0; color: var(--muted); font-size: 12px; }

  .stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 14px 0; }
  .card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; }
  .stat { padding: 10px 14px; }
  .stat-label { margin: 0; color: var(--muted); font-size: 12px; }
  .stat-value { margin: 2px 0 0; font-size: 20px; font-weight: 650; font-variant-numeric: tabular-nums; }

  .toolbar { display: flex; gap: 8px; margin-bottom: 10px; }
  .search { flex: 1; }
  input, select {
    padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px;
    background: var(--card); color: var(--fg); font: inherit;
  }
  input:focus, select:focus { outline: 2px solid var(--primary); outline-offset: -1px; }

  table { width: 100%; border-collapse: collapse; background: var(--card);
          border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
  thead th { text-align: left; font-size: 12px; color: var(--muted); font-weight: 500;
             padding: 9px 12px; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--bg) 55%, var(--card)); }
  tbody td { padding: 9px 12px; border-bottom: 1px solid var(--border); }
  tbody tr:last-child td { border-bottom: 0; }
  td.name { font-weight: 550; }
  .num { text-align: right; font-variant-numeric: tabular-nums; }
  .ops { text-align: right; white-space: nowrap; }
  .empty { text-align: center; color: var(--muted); padding: 26px 12px; }
  .warn { color: var(--danger); font-weight: 650; }

  .chip { display: inline-block; padding: 1px 9px; border-radius: 999px; font-size: 12px; }
  .chip-on { background: var(--chip-on-bg); color: var(--chip-on-fg); }
  .chip-off { background: var(--chip-off-bg); color: var(--chip-off-fg); }

  .btn { padding: 7px 12px; border: 1px solid var(--border); border-radius: 8px;
         background: var(--card); color: var(--fg); font: inherit; cursor: pointer; }
  .btn:hover:not(:disabled) { border-color: var(--primary); }
  .btn:disabled { opacity: .45; cursor: not-allowed; }
  .btn.primary { background: var(--primary); border-color: var(--primary); color: var(--primary-fg); }
  .link { border: 0; background: none; color: var(--primary); font: inherit; cursor: pointer; padding: 0 5px; }
  .link.danger { color: var(--danger); }

  .pager { display: flex; justify-content: space-between; align-items: center;
           margin-top: 10px; color: var(--muted); font-size: 13px; }
  .pager div { display: flex; gap: 8px; }

  .overlay { position: fixed; inset: 0; background: rgba(0,0,0,.42);
             display: flex; align-items: center; justify-content: center; padding: 16px; }
  .modal { background: var(--card); border: 1px solid var(--border); border-radius: 12px;
           padding: 18px 20px; width: 100%; max-width: 380px; display: flex; flex-direction: column; gap: 10px; }
  .modal h2 { margin: 0 0 2px; font-size: 16px; }
  .modal label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
  .modal input, .modal select { width: 100%; }
  .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .err { margin: 0; color: var(--danger); font-size: 12px; }
  .actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }

  .toast { position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
           background: var(--fg); color: var(--bg); padding: 8px 16px; border-radius: 999px;
           font-size: 13px; box-shadow: 0 4px 16px rgba(0,0,0,.25); }
</style>
</head>
<body>
<div id="root"></div>

<script src="/vendor/react.production.min.js"></script>
<script src="/vendor/react-dom.production.min.js"></script>
<script src="/vendor/htm.umd.js"></script>
<script>
"use strict";
const { useState, useMemo, useEffect } = React;
const html = htm.bind(React.createElement);   // htm 替代 JSX:html`<${组件} 属性=${值} />`

const PAGE_SIZE = 5;
const CATS = ["数码", "服饰", "食品", "家居"];
const SEED = [
  { id: 1,  name: "无线降噪耳机",        cat: "数码", price: 899,  stock: 42,  on: true  },
  { id: 2,  name: "机械键盘 87 键",      cat: "数码", price: 399,  stock: 18,  on: true  },
  { id: 3,  name: "4K 显示器 27 寸",     cat: "数码", price: 1499, stock: 7,   on: true  },
  { id: 4,  name: "纯棉基础 T 恤",       cat: "服饰", price: 79,   stock: 230, on: true  },
  { id: 5,  name: "防晒轻薄外套",        cat: "服饰", price: 199,  stock: 0,   on: false },
  { id: 6,  name: "羊毛混纺围巾",        cat: "服饰", price: 129,  stock: 64,  on: true  },
  { id: 7,  name: "云南小粒咖啡豆 500g", cat: "食品", price: 68,   stock: 96,  on: true  },
  { id: 8,  name: "冻干草莓脆 100g",     cat: "食品", price: 25,   stock: 150, on: true  },
  { id: 9,  name: "山核桃仁礼盒",        cat: "食品", price: 158,  stock: 12,  on: false },
  { id: 10, name: "香薰蜡烛(雪松)",    cat: "家居", price: 89,   stock: 40,  on: true  },
  { id: 11, name: "折叠收纳箱三件套",    cat: "家居", price: 119,  stock: 55,  on: true  },
  { id: 12, name: "陶瓷马克杯 350ml",    cat: "家居", price: 39,   stock: 0,   on: true  },
];

function StatCard({ label, value }) {
  return html`
    <div class="card stat">
      <p class="stat-label">${label}</p>
      <p class="stat-value">${value}</p>
    </div>`;
}

// 新增 / 编辑共用一个弹窗:item 无 id 视为新增
function Modal({ item, cats, onSave, onClose }) {
  const isNew = !item.id;
  const [f, setF] = useState({
    name: item.name || "", cat: item.cat || cats[0],
    price: item.price ?? "", stock: item.stock ?? "",
  });
  const [err, setErr] = useState("");
  const set = k => e => setF({ ...f, [k]: e.target.value });

  const submit = e => {
    e.preventDefault();
    const price = Number(f.price), stock = Number(f.stock);
    if (!f.name.trim()) return setErr("名称不能为空");
    if (f.price === "" || !Number.isFinite(price) || price < 0) return setErr("价格要是非负数字");
    if (f.stock === "" || !Number.isInteger(stock) || stock < 0) return setErr("库存要是非负整数");
    onSave({ ...item, name: f.name.trim(), cat: f.cat, price, stock });
  };

  return html`
    <div class="overlay" onClick=${e => { if (e.target === e.currentTarget) onClose(); }}>
      <form class="modal" onSubmit=${submit}>
        <h2>${isNew ? "新增商品" : "编辑商品"}</h2>
        <label>名称
          <input value=${f.name} onChange=${set("name")} placeholder="商品名称" autoFocus />
        </label>
        <label>分类
          <select value=${f.cat} onChange=${set("cat")}>
            ${cats.map(c => html`<option key=${c} value=${c}>${c}</option>`)}
          </select>
        </label>
        <div class="row2">
          <label>价格(¥)
            <input type="number" min="0" step="0.01" value=${f.price} onChange=${set("price")} />
          </label>
          <label>库存
            <input type="number" min="0" step="1" value=${f.stock} onChange=${set("stock")} />
          </label>
        </div>
        ${err && html`<p class="err">${err}</p>`}
        <div class="actions">
          <button type="button" class="btn" onClick=${onClose}>取消</button>
          <button type="submit" class="btn primary">${isNew ? "创建" : "保存"}</button>
        </div>
      </form>
    </div>`;
}

function App() {
  const [rows, setRows] = useState(SEED);
  const [q, setQ] = useState("");
  const [cat, setCat] = useState("全部");
  const [page, setPage] = useState(1);
  const [editing, setEditing] = useState(null);   // null=关闭 | {}=新增 | 行对象=编辑
  const [toast, setToast] = useState("");

  useEffect(() => {
    if (!toast) return;
    const t = setTimeout(() => setToast(""), 2200);
    return () => clearTimeout(t);
  }, [toast]);

  const filtered = useMemo(
    () => rows.filter(r =>
      (cat === "全部" || r.cat === cat) &&
      r.name.toLowerCase().includes(q.trim().toLowerCase())),
    [rows, q, cat],
  );
  const pages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
  const cur = Math.min(page, pages);
  const view = filtered.slice((cur - 1) * PAGE_SIZE, cur * PAGE_SIZE);
  const stockValue = rows.reduce((s, r) => s + r.price * r.stock, 0);

  const save = item => {
    if (item.id) {
      setRows(rows.map(r => (r.id === item.id ? item : r)));
      setToast(`已保存「${item.name}」`);
    } else {
      const id = Math.max(0, ...rows.map(r => r.id)) + 1;
      setRows([{ ...item, id, on: true }, ...rows]);
      setToast(`已创建「${item.name}」`);
    }
    setEditing(null);
  };
  const toggle = r => {
    setRows(rows.map(x => (x.id === r.id ? { ...x, on: !x.on } : x)));
    setToast(`「${r.name}」已${r.on ? "下架" : "上架"}`);
  };
  const remove = r => {
    if (!window.confirm(`删除「${r.name}」?`)) return;
    setRows(rows.filter(x => x.id !== r.id));
    setToast(`已删除「${r.name}」`);
  };

  return html`
    <div class="page">
      <header class="head">
        <div>
          <h1>商品管理</h1>
          <p class="sub">React 18 + htm 自包含单页 · 数据在内存里,刷新即重置</p>
        </div>
        <button class="btn primary" onClick=${() => setEditing({})}>+ 新增商品</button>
      </header>

      <div class="stats">
        <${StatCard} label="商品总数" value=${rows.length} />
        <${StatCard} label="在售" value=${rows.filter(r => r.on).length} />
        <${StatCard} label="库存货值" value=${"¥ " + stockValue.toLocaleString("zh-CN")} />
      </div>

      <div class="toolbar">
        <input class="search" placeholder="搜索商品名…" value=${q}
               onChange=${e => { setQ(e.target.value); setPage(1); }} />
        <select value=${cat} onChange=${e => { setCat(e.target.value); setPage(1); }}>
          <option value="全部">全部分类</option>
          ${CATS.map(c => html`<option key=${c} value=${c}>${c}</option>`)}
        </select>
      </div>

      <table>
        <thead>
          <tr><th>名称</th><th>分类</th><th class="num">价格</th><th class="num">库存</th><th>状态</th><th class="ops">操作</th></tr>
        </thead>
        <tbody>
          ${view.map(r => html`
            <tr key=${r.id}>
              <td class="name">${r.name}</td>
              <td>${r.cat}</td>
              <td class="num">¥ ${r.price.toLocaleString("zh-CN")}</td>
              <td class="num">${r.stock === 0 ? html`<span class="warn">0</span>` : r.stock}</td>
              <td><span class=${"chip " + (r.on ? "chip-on" : "chip-off")}>${r.on ? "在售" : "下架"}</span></td>
              <td class="ops">
                <button class="link" onClick=${() => setEditing(r)}>编辑</button>
                <button class="link" onClick=${() => toggle(r)}>${r.on ? "下架" : "上架"}</button>
                <button class="link danger" onClick=${() => remove(r)}>删除</button>
              </td>
            </tr>`)}
          ${view.length === 0 && html`
            <tr><td class="empty" colSpan="6">没有匹配的商品 —— 换个关键词或清空筛选试试</td></tr>`}
        </tbody>
      </table>

      <footer class="pager">
        <span>共 ${filtered.length} 件 · 第 ${cur} / ${pages} 页</span>
        <div>
          <button class="btn" disabled=${cur <= 1} onClick=${() => setPage(cur - 1)}>上一页</button>
          <button class="btn" disabled=${cur >= pages} onClick=${() => setPage(cur + 1)}>下一页</button>
        </div>
      </footer>

      ${editing !== null && html`
        <${Modal} item=${editing} cats=${CATS} onSave=${save} onClose=${() => setEditing(null)} />`}
      ${toast && html`<div class="toast">${toast}</div>`}
    </div>`;
}

ReactDOM.createRoot(document.getElementById("root")).render(html`<${App} />`);
</script>
</body>
</html>

写作规范

  • 同一功能有 Python / R 两种写法 → 用选项卡 ===
  • 内容很长但非必读 → 用折叠 ???
  • 重要注意事项 → 用提示框 !!!(warning 易犯错误、tip 建议、note 补充)
  • 系统架构或交互流程 → 用 Mermaid 图
  • 交互效果演示 → iframe 嵌自包含静态单页(纯客户端、真身进 assets/、vendor 不走 CDN、逻辑不压缩,规矩与活例见第 9 节
  • AI 生成概念配图 → 用白底黑色马克笔草图,一张图只讲一个概念
  • 引用外部一手资料:有存档、有引用、有分析。三步缺一不可:① 真身 clone / 下载进文章 assets/ 存档(注明出处与获取日期),文中 ??? abstract 折叠 + --8<-- 展示原文;② 正文摘关键原句(> 引用块);③ 每处引用必须跟自己的分析 —— 只贴引用不给分析,等于没消化。例外:引用的是活跃开源仓库、且原文的关键契约(Invariants / 不变量 / API)已经在正文里消化成自己的语言,可以省掉真身存档,改用钉具体 commit hash 的 GitHub 永链代替。前提是永链钉的是 commit 而不是 branch(内容冻结不随上游变),且读者不依赖点开链接就能理解正文——链接只做溯源。活例:Dashboard skill 的 open-dashboard 蓝本吸收
  • 收录外部 git 仓库:只取核心文件,不整仓 clone 进来。挑对理解主题真正关键的那几个文件复制进 assets/ 存档,其余一律不进 docs —— 本 wiki 不追求完整镜像,追求核心,因为人也要读;要完整原貌,顺钉 commit 的永链自己去 GitHub。活例:Dashboard 蓝本吸收曾把 84 篇原文全量镜像进 assets/,后来删到只剩 PATTERNS.md + backends/CONTRACT.md 两份切不进单篇文章的系统级契约。唯一例外是吸收型 skill:行为定义文件(主文件 + 它引用的 references)必须收齐才能跑,见附录的吸收规矩
  • markdown 层级缩进 统一用 4 个空格(折叠块 / 选项卡 / 提示框的子内容必须缩进 4 空格才被识别)。注意:这只指 markdown 写作时的缩进,代码块内部的 Python / 其他语言缩进原样保留,不受影响
  • 代码块语言标识统一用 python(不用 py
  • 新增页面必须在 mkdocs.ymlnav: 中注册
  • 每篇新页面必须被某个祖先索引页用 markdown 链接直接链到 —— nav 只是给人的策展层,不在 AI 链路上,漏挂链接的文章对 AI 等于不存在。索引页指 index.md / MIRROR.md / SKILL.md 三种,写一行带钩子的链接即可。deploy.sh 在构建前跑 scripts/check-links.py 强制校验,不过不许部署
  • 不给挪走的文件留旧地址存根页。要保住旧 URL 就别挪文件;挪了就直接删原文件,让 404 诚实暴露。存根页是天生的孤儿(无人链接、不进 nav),留着只会逼校验开豁免口子,而每个豁免口子都是下一次漏挂链接的藏身处(2026-07-30 依此清掉了 posts/animated-ppt/reference/deck-skill.mdskills/data-visualization/reference/f1-rung-bars.md 两页存根)

文章配图:白底黑色马克笔草图

概念配图不负责装饰,只负责让一个抽象关系更快被看懂。需要 AI 新生成概念图时,统一画成白底黑色马克笔随手草图;每张图只表达一个概念。 截图、数据图表、历史材料等有证据作用的图片不套这套风格。

视觉硬规矩

  • 媒介 → 黑色马克笔或手机手写笔,不用蜡笔、水彩或铅笔
  • 线条 → 实心黑线,粗细略有变化;允许重描、越界、歪斜和接缝不齐
  • 造型 → 物体压成基础几何形,只留最关键的识别特征
  • 构图 → 主体居中,占画面约 60–70%,四周留大量空白
  • 色彩 → 纯黑线、纯白背景,不填色,不画阴影、灰阶或排线
  • 气质 → 像不擅长画画的人临时拿笔解释概念——直接、笨拙、未经修饰
  • 信息量 → 一张图只讲一个概念,不堆文字、标签、箭头或装饰
  • 避免 → 工整图标感、儿童绘本感、毛茸茸的蜡笔颗粒、刻意卖萌、专业插画质感

生成与入库

  1. 先列清文章里真正需要画的概念——能用一句话说清的图才画;一句话里有两个关系就拆成两张。
  2. 每个概念单独生成,不让模型在一张画布里拼多张图或多个分镜。
  3. 逐张检查纯白背景、纯黑线、无文字、无阴影、主体比例和留白;不合格只改一个最明显的问题再生成。
  4. 最终图片放进文章自己的 assets/,不要只留在生成工具的默认目录。文件名用语义化 kebab-case,如 random-sample-marker-doodle.png
  5. Markdown 用相对路径引用,alt text 写这张图解释的概念,不重复“白底黑色马克笔”这类视觉信息:

    ![从同一总体反复抽样,会得到略有不同的样本](assets/repeated-samples-marker-doodle.png)
    
  6. PNG 等二进制文件按仓库现有 .gitattributes 走 git-lfs;照常 git add、commit、push,不把图片转成 base64 塞进 markdown。

可直接复用的 prompt 骨架:

Use case: scientific-educational
Asset type: landscape illustration for a concise Chinese wiki article
Primary request: <只写一个要解释的概念,以及画面里必须出现的对象>
Scene/backdrop: pure white background
Style/medium: black marker or phone stylus doodle, drawn quickly by an adult
who is bad at drawing while explaining a concept; solid black strokes with
slightly uneven thickness; crooked outlines, imperfect proportions, visible
retracing, overshoot, and mismatched joins; objects reduced to basic geometry
Composition/framing: landscape 3:2, subject centered and occupying about
60–70 percent of the canvas, very large blank margins
Color palette: pure black linework only on pure white
Constraints: no text, no labels, no numbers, no arrows, no fill, no gray,
no shadows, no hatching, no decoration, no watermark
Avoid: polished icon design, children's picture-book style, cute expression,
crayon grain, pencil texture, watercolor, professional illustration,
geometric precision, dense infographic layout

活例:《抽样:检验之前,先把样本抽对》用四张图分别讲随机抽取、重复抽样、分层抽样和抽样框遗漏;《用西瓜搞懂准确率、精确率和召回率》把同一风格用于分类指标。

文风

本 wiki 的行文默认口吻,AI 代笔时照此写:

  • 先结论后展开:段落第一句就是论点;标题尽量直接说出结论(「nav 随便重排,链接图纹丝不动」,不是「关于 nav 的说明」)
  • 短句,破折号接推理:一句话说一件事;因果和转折用「——」接在句内,不起「因此 / 然而 / 值得注意的是」的套话头
  • 要点一行一条,prompt 式:每条是可直接执行的指令或可检验的判断,不写「小标题:多行解释」的两段式
  • 加粗只给结论和硬规矩,不给气氛词;形容词能删则删
  • 统一术语不换词:真身、坑、规矩、钉 commit、软链……全站同一个词指同一件事,AI 顺链接连读多篇不用重新对齐概念
  • 技术词保留英文原形(commit、nav、snippet、iframe),叙述用中文,不音译不生造
  • 规矩必须带活例:立一条规范就指一个站内真实例子(「活例:Dashboard skill 的蓝本吸收」);没有活例的规矩先别写进规范
  • 没做的事就说没做:「做了再回来补」「待续」,不拿计划冒充成果(活例:手记里 CDN 那条、COS 部署篇的下一步节)
  • 写完对照 去 AI 味 的模式清单自查一遍

项目配置(附录)

下面以本 wiki 自己为例(基于 Zensical,跟 MkDocs Material 语法层面 100% 兼容)。

项目结构

zensical-wiki/
├── mkdocs.yml              # Zensical / MkDocs 共用配置
├── docs/                   # 所有文档源
│   ├── index.md
│   ├── posts/
│   └── skills/
├── deploy.sh               # 构建 + 同步 COS
└── pyproject.toml          # uv 管理 zensical 依赖

新增页面流程

  1. docs/ 下合适位置创建 .md
  2. 编写内容
  3. 必须mkdocs.ymlnav: 注册路径
  4. 必须在父级索引页(index.md / MIRROR.md / SKILL.md)加一行链接 —— 给 AI 的通路,见下节
  5. python3 scripts/check-links.py 自查(可选,deploy.sh 里会强制跑一遍)
  6. uv run zensical serve 本地预览(毫秒级热更新)
  7. ./deploy.sh 一键构建 + 同步到 COS

本 wiki 的 AI 入口是相对链接,不是 nav。所以「文章写完了、nav 也注册了」并不代表 AI 能读到它 —— 得有人在索引页里挂一行链接。这件事纯靠自觉会漏,所以做成部署闸门:deploy.shgit pull 之后、读凭证和构建之前跑校验,非零退出码被 set -e 拦下,部署中止。

查三项:

检查 规则 失败后果
父级链接 每篇 .md 必须被某个祖先目录index.md / MIRROR.md / SKILL.md 直接链到 阻断部署
死链 指向不存在的 .md 阻断部署
nav 注册 路径出现在 mkdocs.yml 里(MIRROR.md 按规范豁免) 阻断部署

写作时只需记两条例外,别为了过校验去改错地方:

  • 吸收型 skill 的 index.md 不许改 —— 它是上游原文照录。归档文件的链接挂在 MIRROR.md(目录里唯一自己写的文件)或分类索引上,校验认「祖先」而非「最近父级」正是为此。活例:xi-wen 的 9 份归档全靠 MIRROR.md 的「本地归档」一栏挂住
  • 上游原文里的死链是警告不是错误 —— 原文里的路径常是举例,本地不可能存在;往原文加豁免注释就是改原文。判据是同级或祖先目录有 MIRROR.md

豁免写在文件顶部(前 5 行内),理由跟着文件走、不躺在脚本白名单里:

<!-- link-check-ok: 为什么这篇不该上链路 -->

「已迁移存根」不是正当理由,本 wiki 不留存根页。

脚本真身与设计取舍(含它查不到什么)见 COS 部署篇 · 闸门一节

当前项目的 mkdocs.yml

mkdocs.yml 真身在仓库根(zensical 才读得到),它是项目配置、不是本 skill 的资产,所以不复制进 assets/。下面用 snippets 直接引用仓库根真身展示(源文件只一处,引用不算重复):

mkdocs.yml
site_name: 牛合天's wiki
site_url: https://wiki.liuhetian.work/
copyright: >
  Copyright &copy; 2026 Liu Hetian ·
  <a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">蜀ICP备2021023978号-2</a>

theme:
  name: material
  custom_dir: overrides   # 首页开屏模板 home.html 所在目录
  language: zh
  font: false   # 关掉 Google Fonts 远程引用(渲染阻塞,境内常超时);字体本地化见 extra_css
  features:
    - navigation.tabs
    - navigation.indexes
    - content.code.copy
    - content.code.annotate

extra_css:
  - vendor/fonts/fonts.css       # 本地 Inter + JetBrains Mono,替代 fonts.googleapis.com;只出 --md-text-font/--md-code-font
  - stylesheets/zx-tokens.css    # 全站令牌层:Atelier Zero 令牌 + --md-*/--color-* 映射(首页 home.css 也用它)
  - stylesheets/zx-theme.css     # 全站皮肤层:只写选择器,消费上一层的变量

extra_javascript:
  - vendor/mathjax-init.js   # MathJax 配置,须在真身之前
  - vendor/mathjax/tex-svg.js  # 本地化真身(3.2.2,SVG 输出免字体文件),不依赖外网 CDN
  - vendor/echarts-init.js   # ```echarts 代码块渲染器;真身按需动态加载,无图页面零开销

markdown_extensions:
  - admonition
  - attr_list
  - footnotes
  - md_in_html
  - pymdownx.arithmatex:
      generic: true
  - pymdownx.details
  - pymdownx.highlight:
      anchor_linenums: true
      line_spans: __span
      pygments_lang_class: true
  - pymdownx.inlinehilite
  - pymdownx.snippets:
      base_path:
        - docs   # docs 内引用去掉 docs/ 前缀(如 skills/.../x.md),避免 AI 把 docs/ 当 URL
        - .      # 回退到项目根:mkdocs.yml / deploy.sh 等真实文件仍可被 --8<-- 引用
      check_paths: true
  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format
        - name: echarts   # 声明式图表:fence 里写 ECharts option JSON,vendor/echarts-init.js 渲染
          class: echarts
          format: !!python/name:pymdownx.superfences.fence_code_format
  - pymdownx.tabbed:
      alternate_style: true

# zensical 的链接校验有并行竞态:同一份内容偶发(约 1/15 次构建)误报大量
# "page does not exist",产物 HTML 实测无差异(0.0.46 与 0.0.53 均复现)。
# 死链检查由 scripts/check-links.py 在部署前把关(会阻断部署),这里关掉不丢保障;
# invalid_link_anchors(锚点校验)check-links.py 不覆盖,保留 —— 若它也出现偶发误报,同样对待。
validation:
  invalid_links: false

nav:
  - 首页:
      - index.md
  - 文章:
      - posts/index.md
      - AI友好知识库:
          - posts/cos-wiki-deploy/index.md
          - 部署实操手册: posts/cos-wiki-deploy/reference/deploy.md
          - 建站手记: posts/cos-wiki-deploy/reference/wiki-build-log.md
      - 自建 git-lfs 后端: posts/git-lfs-cos.md
      - 预测项目闭环: posts/prediction-loop.md
      - AI 时代的产品经理: posts/ai-pm.md
      - 动画网页 PPT: posts/animated-ppt/index.md
      - 超级轻量的自用AI编程Harness框架: posts/ai-code-skeleton/index.md
      - Kindle 常显信息屏:
          - posts/kindle-dashboard/index.md
          - 越狱与显示通道: posts/kindle-dashboard/reference/jailbreak.md
          - 上报接口交接: posts/kindle-dashboard/reference/report-api.md
          - 决策日志: posts/kindle-dashboard/reference/decision-log.md
      - 工作方法:
          - posts/methods/index.md
          - 精力管理:
              - posts/methods/energy/index.md
              - 神经系统降载: posts/methods/energy/nervous-system-regulation.md
          - 推进执行:
              - posts/methods/execution/index.md
              - 如果—那么计划: posts/methods/execution/if-then-plans.md
              - 限制同时开工: posts/methods/execution/wip-limits.md
              - 每周清仓: posts/methods/execution/weekly-review.md
          - 做出决策:
              - posts/methods/decisions/index.md
              - 单向门与双向门: posts/methods/decisions/two-way-door-decisions.md
              - 事前验尸: posts/methods/decisions/premortem.md
  - 笔记:
      - notes/index.md
      - 概率与算法:
          - notes/probability/index.md
          - 12 枚硬币的奇偶性: notes/probability/coin-parity.md
          - 3 根绳子烧出 7 分钟: notes/probability/rope-timing.md
      - 统计学:
          - notes/statistics/index.md
          - 基础:
              - 描述统计: notes/statistics/data-description/index.md
              - 概率模型: notes/statistics/probability-models/index.md
              - 常见分布: notes/statistics/distributions/index.md
              - 抽样: notes/statistics/sampling/index.md
              - 抽样分布: notes/statistics/sampling-distributions/index.md
          - 统计推断:
              - 点估计: notes/statistics/estimation/index.md
              - 置信区间: notes/statistics/confidence-intervals/index.md
              - 假设检验: notes/statistics/hypothesis-testing/index.md
              - 组间比较: notes/statistics/group-comparisons/index.md
              - 功效与样本量: notes/statistics/power-sample-size/index.md
              - 分类数据: notes/statistics/categorical-data/index.md
              - 非参数方法: notes/statistics/nonparametric/index.md
              - 重抽样: notes/statistics/resampling/index.md
          - 统计模型:
              - 简单线性回归: notes/statistics/linear-regression/index.md
              - 多元回归: notes/statistics/multiple-regression/index.md
              - 方差分析: notes/statistics/anova/index.md
              - 时间序列: notes/statistics/time-series/index.md
          - 设计与实务:
              - 实验设计: notes/statistics/experimental-design/index.md
              - 因果推断: notes/statistics/causal-inference/index.md
              - 缺失数据: notes/statistics/missing-data/index.md
              - 贝叶斯统计: notes/statistics/bayesian/index.md
              - 统计分析工作流: notes/statistics/statistical-workflow/index.md
      - 机器学习:
          - notes/machine-learning/index.md
          - 准确率、精确率与召回率: notes/machine-learning/classification-metrics/index.md
      - Git:
          - notes/git/index.md
          - fork 后吸收上游: notes/git/fork-upstream-sync.md
          - stash 的心智模型: notes/git/stash.md
          - worktree 的心智模型: notes/git/worktree.md
  - Skills:
      - skills/index.md
      - FastAPI 后端:
          - skills/fastapi/index.md
          - 项目布局: skills/fastapi/reference/项目布局.md
          - 数据库: skills/fastapi/reference/数据库.md
          - RAG: skills/fastapi/reference/rag.md
          - MCP: skills/fastapi/reference/mcp.md
          - 优雅终止: skills/fastapi/reference/优雅终止.md
          - 定时任务: skills/fastapi/reference/定时任务.md
          - 配置管理: skills/fastapi/reference/配置管理.md
          - 对话前端: skills/fastapi/reference/对话前端.md
          - 运行时补丁: skills/fastapi/reference/运行时补丁.md
          - 日志管理: skills/fastapi/reference/日志管理.md
          - 测试: skills/fastapi/reference/测试.md
          - 部署: skills/fastapi/reference/部署.md
          - 监控: skills/fastapi/reference/监控.md
          - 事后检查: skills/fastapi/reference/事后检查.md
      - Dashboard 后台:
          - skills/dashboard/index.md
          - 表单:
              - 表单: skills/dashboard/reference/表单/表单.md
              - 向导表单: skills/dashboard/reference/表单/向导表单.md
              - 搜索下拉: skills/dashboard/reference/表单/搜索下拉.md
              - 文件上传: skills/dashboard/reference/表单/文件上传.md
              - 行内编辑: skills/dashboard/reference/表单/行内编辑.md
          - 列表与表格:
              - 表格: skills/dashboard/reference/列表与表格/表格.md
              - 卡片列表: skills/dashboard/reference/列表与表格/卡片列表.md
              - 紧凑列表: skills/dashboard/reference/列表与表格/紧凑列表.md
              - 无限滚动: skills/dashboard/reference/列表与表格/无限滚动.md
              - 虚拟表格: skills/dashboard/reference/列表与表格/虚拟表格.md
              - 列控制: skills/dashboard/reference/列表与表格/列控制.md
              - 筛选面板: skills/dashboard/reference/列表与表格/筛选面板.md
              - 保存视图: skills/dashboard/reference/列表与表格/保存视图.md
              - CSV 导入导出: skills/dashboard/reference/列表与表格/CSV导入导出.md
          - 富视图:
              - 看板: skills/dashboard/reference/富视图/看板.md
              - 日历: skills/dashboard/reference/富视图/日历.md
              - 树形: skills/dashboard/reference/富视图/树形.md
              - 时间线: skills/dashboard/reference/富视图/时间线.md
              - 主从视图: skills/dashboard/reference/富视图/主从视图.md
          - 详情与页面:
              - 详情页: skills/dashboard/reference/详情与页面/详情页.md
              - 记录选项卡: skills/dashboard/reference/详情与页面/记录选项卡.md
              - 关联记录: skills/dashboard/reference/详情与页面/关联记录.md
              - 多栏布局: skills/dashboard/reference/详情与页面/多栏布局.md
              - 设置页: skills/dashboard/reference/详情与页面/设置页.md
              - 图表页: skills/dashboard/reference/详情与页面/图表页.md
          - 展示与反馈:
              - 数据展示件: skills/dashboard/reference/展示与反馈/数据展示件.md
              - 反馈状态: skills/dashboard/reference/展示与反馈/反馈状态.md
              - 通知中心: skills/dashboard/reference/展示与反馈/通知中心.md
              - 审计日志: skills/dashboard/reference/展示与反馈/审计日志.md
          - 平台能力:
              - 权限门控: skills/dashboard/reference/平台能力/权限门控.md
              - 登录方式: skills/dashboard/reference/平台能力/登录方式.md
              - 全局搜索: skills/dashboard/reference/平台能力/全局搜索.md
              - i18n: skills/dashboard/reference/平台能力/i18n.md
              - 计费: skills/dashboard/reference/平台能力/计费.md
              - 实时刷新: skills/dashboard/reference/平台能力/实时刷新.md
      - 数据可视化:
          - skills/data-visualization/index.md
          - Lieflat Charts:
              - skills/data-visualization/lieflat-charts/index.md
              - 基础型 · Lupi Basics:
                  - F1 · 梯级柱状图: skills/data-visualization/lieflat-charts/reference/f1-rung-bars.md
                  - F2 · 发丝折线图: skills/data-visualization/lieflat-charts/reference/f2-hairline-line.md
                  - F3 · 发丝面积图: skills/data-visualization/lieflat-charts/reference/f3-hairline-area.md
                  - F4 · 刻线环形图: skills/data-visualization/lieflat-charts/reference/f4-tick-donut.md
                  - F5 · 刻线横条图: skills/data-visualization/lieflat-charts/reference/f5-tick-rows.md
                  - F6 · 并列梯级柱: skills/data-visualization/lieflat-charts/reference/f6-paired-rungs.md
                  - F7 · 堆叠梯级柱: skills/data-visualization/lieflat-charts/reference/f7-stacked-rungs.md
                  - F8 · 铅垂散点图: skills/data-visualization/lieflat-charts/reference/f8-plumb-scatter.md
                  - F9 · 梯级瀑布图: skills/data-visualization/lieflat-charts/reference/f9-rung-waterfall.md
                  - F10 · 点阵热力图: skills/data-visualization/lieflat-charts/reference/f10-dot-heat.md
                  - F11 · 刻线进度表: skills/data-visualization/lieflat-charts/reference/f11-tick-gauge.md
                  - F12 · 串珠哑铃图: skills/data-visualization/lieflat-charts/reference/f12-dumbbell-queue.md
              - 编辑型 · Lupi Editorial:
                  - L1 · 上线扇形图: skills/data-visualization/lieflat-charts/reference/l1-launch-fan.md
                  - L2 · 点阵级联图: skills/data-visualization/lieflat-charts/reference/l2-dot-cascade.md
                  - L3 · 条码棒棒糖图: skills/data-visualization/lieflat-charts/reference/l3-barcode-lollipop.md
                  - L4 · 弧形矩阵: skills/data-visualization/lieflat-charts/reference/l4-arc-matrix.md
                  - L5 · 径向汇聚图: skills/data-visualization/lieflat-charts/reference/l5-radial-convergence.md
                  - L6 · 贡献者星群: skills/data-visualization/lieflat-charts/reference/l6-cluster-field.md
                  - L7 · 品牌光谱: skills/data-visualization/lieflat-charts/reference/l7-brand-spectrum.md
                  - L8 · 点阵空间矩阵: skills/data-visualization/lieflat-charts/reference/l8-dotty-matrix.md
                  - L9 · 气泡年鉴: skills/data-visualization/lieflat-charts/reference/l9-bubble-almanac.md
                  - L10 · 径向拼布图: skills/data-visualization/lieflat-charts/reference/l10-radial-patchwork.md
                  - L11 · 趋势谱系图: skills/data-visualization/lieflat-charts/reference/l11-trend-lineage.md
                  - L12 · 归属柱廊图: skills/data-visualization/lieflat-charts/reference/l12-type-colonnade.md
                  - L13 · 沙漏流图: skills/data-visualization/lieflat-charts/reference/l13-hourglass-stream.md
                  - L14 · 百人点阵: skills/data-visualization/lieflat-charts/reference/l14-hundred-field.md
                  - L15 · 选票刻线图: skills/data-visualization/lieflat-charts/reference/l15-ballot-tally.md
              - 快读型 · Glance:
                  - G1 · 区间胶囊图: skills/data-visualization/lieflat-charts/reference/g1-range-capsules.md
                  - G2 · 花瓣玫瑰图: skills/data-visualization/lieflat-charts/reference/g2-petal-rose.md
                  - G3 · 粗体柱状图: skills/data-visualization/lieflat-charts/reference/g3-chunky-bars.md
                  - G4 · 点阵华夫图: skills/data-visualization/lieflat-charts/reference/g4-dot-waffle.md
                  - G5 · 象形柱状图: skills/data-visualization/lieflat-charts/reference/g5-pictorial-bar.md
                  - G6 · 小型环形关系图: skills/data-visualization/lieflat-charts/reference/g6-circular-graph.md
                  - G7 · 左右树状图: skills/data-visualization/lieflat-charts/reference/g7-tree-lr.md
                  - G8 · 雨幕双面积图: skills/data-visualization/lieflat-charts/reference/g8-rainfall-dual-area.md
                  - G9 · 散点变形图: skills/data-visualization/lieflat-charts/reference/g9-scatter-morph.md
                  - G10 · 发散条形图: skills/data-visualization/lieflat-charts/reference/g10-diverging-bar.md
                  - G11 · 小型力导向图: skills/data-visualization/lieflat-charts/reference/g11-force-graph.md
                  - G12 · 错落波形图: skills/data-visualization/lieflat-charts/reference/g12-stagger-wave.md
                  - G13 · 大切片图: skills/data-visualization/lieflat-charts/reference/g13-big-slice.md
                  - G14 · 单轴图: skills/data-visualization/lieflat-charts/reference/g14-single-axis.md
                  - G15 · 抖动条带图: skills/data-visualization/lieflat-charts/reference/g15-jitter-strip.md
                  - G16 · 动态条形竞赛: skills/data-visualization/lieflat-charts/reference/g16-bar-race.md
                  - G17 · 动态流图: skills/data-visualization/lieflat-charts/reference/g17-dynamic-stream.md
                  - G18 · 一笔绘制与计数器: skills/data-visualization/lieflat-charts/reference/g18-draw-in-plus-counter.md
              - 独立交互大图:
                  - B1 · 密集环形关系图: skills/data-visualization/lieflat-charts/reference/b1-circular-graph-dense.md
                  - B2 · 密集力导向图: skills/data-visualization/lieflat-charts/reference/b2-force-graph-dense.md
                  - B3 · 三段丝线图: skills/data-visualization/lieflat-charts/reference/b3-thread-triptych.md
      - 前端画布:
          - skills/canvas/index.md
          - L1 · 原生无限画布: skills/canvas/reference/l1-vanilla.md
          - L2 · React 手写画布: skills/canvas/reference/l2-react.md
          - L3 · 数据驱动节点图: skills/canvas/reference/l3-nodegraph.md
          - L4 · 白板: skills/canvas/reference/l4-whiteboard.md
      - 写作口味:
          - skills/writing/index.md
          - MkDocs Wiki 文档: skills/writing/mkdocs-wiki/index.md
          - 报纸版 HTML: skills/writing/newspaper/index.md
          - 滚动 deck 工程手册: skills/writing/deck/index.md
          - 去 AI 味:
              - skills/writing/qu-ai-wei/index.md
              - 51 条 AI 腔模式: skills/writing/qu-ai-wei/references/patterns.md
              - 平台场景规则: skills/writing/qu-ai-wei/references/platform-patterns.md
              - 品牌文案语体: skills/writing/qu-ai-wei/references/brand-voice.md
              - 白名单: skills/writing/qu-ai-wei/references/whitelists.md
              - 标点规范: skills/writing/qu-ai-wei/references/punctuation.md
              - 语序诊断: skills/writing/qu-ai-wei/references/syntax.md
              - 完整示例: skills/writing/qu-ai-wei/references/examples.md
              - 正面参考模型: skills/writing/qu-ai-wei/references/reference-models.md
              - 参考来源: skills/writing/qu-ai-wei/references/sources.md
          - 檄文:
              - skills/writing/xi-wen/index.md
              - 逐篇精读: skills/writing/xi-wen/references/逐篇精读.md
              - 骈句声律: skills/writing/xi-wen/references/骈句声律.md
              - 用典骂法库: skills/writing/xi-wen/references/用典骂法库.md
              - 戏仿范例: skills/writing/xi-wen/references/戏仿范例.md
              - 底本·为袁绍檄豫州文: skills/writing/xi-wen/references/originals/为袁绍檄豫州文.md
              - 底本·为李密檄洛州文: skills/writing/xi-wen/references/originals/为李密檄洛州文.md
              - 底本·为徐敬业讨武曌檄: skills/writing/xi-wen/references/originals/为徐敬业讨武曌檄.md
              - 底本·谕中原檄: skills/writing/xi-wen/references/originals/谕中原檄.md
              - 底本·讨粤匪檄: skills/writing/xi-wen/references/originals/讨粤匪檄.md
      - 前端风格收集:
          - skills/frontend-styles/index.md
          - PIP-BOY 琥珀终端: skills/frontend-styles/reference/pip-boy-terminal.md
          - Celestia 主题收藏卡: skills/frontend-styles/reference/celestia-collection.md
          - Verdant Glass 苔光用量台: skills/frontend-styles/reference/verdant-glass.md
          - Syzygy 克莱因蓝星穹: skills/frontend-styles/reference/syzygy-astral.md
          - NEXUS 2030 酸绿深空首屏: skills/frontend-styles/reference/nexus-2030.md
          - ATELIER 纸白动力学: skills/frontend-styles/reference/atelier-kinetic.md
          - PLATTER 平面音乐档案: skills/frontend-styles/reference/task1-platter.md
          - PARALLAX 三态作品档案: skills/frontend-styles/reference/task2-parallax-archive.md
          - LINE//SYSTEM 线性工业图形: skills/frontend-styles/reference/task3-line-system.md
          - WORLD FILES 档案袋叙事: skills/frontend-styles/reference/task4-world-files.md
          - PIXEL BLOOM 网格揭示: skills/frontend-styles/reference/task5-pixel-bloom.md
          - TINTORY 视觉考古编辑部: skills/frontend-styles/reference/task6-tintory.md
          - NEAT ANNOTATIONS 手绘标注标本: skills/frontend-styles/reference/task7-neat-annotations.md
          - One Hub 翡翠管理台: skills/frontend-styles/reference/onehub-berry-admin.md
          - Vercel Lanyard 可拖拽 3D 工牌: skills/frontend-styles/reference/vercel-lanyard.md
          - Open Design 官网拆解:
              - skills/frontend-styles/open-design/index.md
              - 滚动感知吸顶导航: skills/frontend-styles/open-design/headroom-nav.md
              - 标题逐词模糊入场: skills/frontend-styles/open-design/blur-text.md
              - 滚动入场编排: skills/frontend-styles/open-design/scroll-reveal.md
              - 宣言逐字点亮: skills/frontend-styles/open-design/statement-reveal.md
              - 滚动锁定步骤联动: skills/frontend-styles/open-design/scrolly-steps.md
              - 可拖拽贴纸: skills/frontend-styles/open-design/drag-sticker.md
              - 磁性 Dock 预览切换: skills/frontend-styles/open-design/magnetic-dock.md
              - 图标物理掉落: skills/frontend-styles/open-design/falling-chips.md
              - 点阵地球与贡献者轨道: skills/frontend-styles/open-design/globe-orbit.md
              - 贴纸统计卡与数字滚动: skills/frontend-styles/open-design/stat-cards.md
              - 页底渐进高斯模糊: skills/frontend-styles/open-design/gradual-blur.md
          - HAOQI 3D 官网拆解:
              - skills/frontend-styles/haoqi/index.md
              - 3D 开场专题: skills/frontend-styles/haoqi/01-opening.md
              - 第二个特效(预留): skills/frontend-styles/haoqi/02-effect.md
              - 第三个特效(预留): skills/frontend-styles/haoqi/03-effect.md
          - ORYZO 官网拆解:
              - skills/frontend-styles/oryzo/index.md
              - 蓝图开场动画: skills/frontend-styles/oryzo/blueprint-intro.md
              - 高斯泼溅渲染系统: skills/frontend-styles/oryzo/splat-system.md
              - 滚动叙事与场景切换: skills/frontend-styles/oryzo/scene-morph.md
              - Wearable 一幕: skills/frontend-styles/oryzo/wearable-scene.md
              - RGB 呼吸光边: skills/frontend-styles/oryzo/rgb-glow-border.md
      - 和 AI 协作:
          - skills/collab/index.md
          - CLAUDE.md 初始化模板: skills/collab/claude-md-template/index.md
          - 盘问我: skills/collab/grilling/index.md
          - 带文档盘问:
              - skills/collab/grill-with-docs/index.md
              - Domain Modeling: skills/collab/grill-with-docs/domain-modeling/SKILL.md
              - CONTEXT.md 格式: skills/collab/grill-with-docs/domain-modeling/CONTEXT-FORMAT.md
              - ADR 格式: skills/collab/grill-with-docs/domain-modeling/ADR-FORMAT.md
nav:
  - 首页: index.md                       # - 显示名: 路径
  - Skills:                              # 子板块(可嵌套)
      - FastAPI 后端:
          - skills/fastapi/index.md      # 不带显示名 = 用文件一级标题
          - reference:
              - 数据库: skills/fastapi/reference/数据库.md
  • - 显示名: 路径 → 自定义导航名
  • - 路径.md → 自动取文件一级标题
  • 路径相对于 docs/

内容放哪个板块:三问判定

顶层板块按用途和知识性质分——文章(完整可读的复盘、观点与方法整理)、笔记(专业科目的学习与推导)、Skills(给 AI 的成套资产);主题只做板块内的二级目录。新内容按顺序问三个问题:

  1. 是给 agent 直接挂载/参考的成套资产吗?→ Skills
  2. 属于算法、机器学习、编程语言等专业科目的学习、推导或题解吗?→ 笔记
  3. 其余完整可独立阅读的复盘、观点和方法整理 → 文章

文章和 skill 是共生不是成熟度递进:复盘归文章,复盘里沉淀出的可复用手册归 skill,正文互链——例:动画 PPT 复盘与它的工程手册。板块归属变了就把文件真的搬过去,让磁盘和分类保持一致。不留旧地址存根页 —— 要保住旧 URL 就别挪文件,挪了就直接删,理由见上文写作规范那条。

从 MkDocs Material 切换到 Zensical 几乎零成本

mkdocs.yml / markdown_extensions / 主题 features 全部继续工作。 命令 zensical build / zensical serve 代替 mkdocs build / mkdocs serve,速度快 4-5 倍。

按 Claude skill 标准组织 wiki 目录(本 wiki 沿用的规范)

本 wiki 的 "Skills" 系列直接沿用 Anthropic 官方 skill 标准目录布局, 让仓库结构跟 ~/.claude/skills/ 一一对应 —— AI 读到的目录形态就是它熟悉的 skill 形态。

目录分两层,角色不同。

分类目录writing/collab/ 这一层)只是装 skill 的普通文件夹:一个 index.md 做索引(几行链接),下面每个子目录是一个独立 skill。分类自己不是 skill,没有 assets/ / reference/

skill 目录 = 一个文件夹,标准三件套(reference 和 assets 必须平级):

路径 角色 等价于 skill 原型的
index.md 主入口(自写 skill 的主控;吸收型 skill 直接就是上游 SKILL.md 原文) SKILL.md
assets/ 资产真身(CSS / JS / SVG 等) assets/
reference/ 详细参考,按主题拆多个 .md只有大 skill 需要,见下) references/
scripts/ 可执行脚本(按需,wiki 这边一般不用) scripts/

怎么判断是"一个 skill 带 reference"还是"分类装多个 skill":看子页之间的关系。 FastAPI 的 14 个章节、Dashboard 的 36 个形状都服务同一个主题,是一个 skill 的按需加载参考 —— 用 index.md + reference/。 写作口味下的 MkDocs 文档 / 报纸版 / 去 AI 味互不相干,谁也不是谁的参考 —— 那是三个独立 skill,各占一个目录。 把独立 skill 塞成别人的 reference 是层级错位(本 wiki 2026-07 重构前就犯过这个错:qu-ai-wei 自带 9 份 references 的完整 skill,被压成"writing 的一篇 reference")。

示例:写作口味分类

docs/skills/writing/               # 分类目录
├── index.md                       # 分类索引,几行链接
├── mkdocs-wiki/                   # skill:MkDocs Wiki 写作(本页)
│   ├── index.md
│   └── assets/react-spa-demo.html
├── newspaper/                     # skill:报纸版 HTML
│   ├── index.md
│   └── assets/                    # newspaper.css / .js / favicon.svg / demo
└── qu-ai-wei/                     # skill:吸收自上游的去 AI 味
    ├── index.md                   # = 上游 SKILL.md 原文照录(真身就是入口)
    ├── references/                # 上游依赖文件,按原相对路径平级归档
    └── MIRROR.md                  # 来源与吸收说明(目录里唯一自己写的文件)

吸收外部 skill 的归档规矩:skill 目录镜像上游布局——主文件 SKILL.md 原文照录、仅改名为 index.md(真身就是入口,不另写落地文);它引用的全部依赖文件references/、依赖的其他 skill)按上游相对路径与 index.md 平级归档,主文件内部的相对链接因此原样有效;一律钉 commit 原文照录。安装脚本 / 多语言翻译 / CI 这类非行为定义可跳过,但取舍要写进 MIRROR.md(记上游仓库、commit 永链、吸收日期、漂移处置,是目录里唯一自己写的文件)。skill 目录名对齐上游 slug(如 qu-ai-weigrill-with-docs)。

人读的介绍压到分类索引一句话:吸收型 skill 的页面本身是给 AI 看的真身,人真正需要读的介绍——这个 skill 干什么、亮点在哪、吸收自谁——写在分类 index.md 的链接后面一句话即可,不单独维护落地文或中译页(2026-07-09 废除此前"落地文在前、SKILL.md 真身挂 nav 附件在后"的两层做法:人读内容太少,不值得两个页面)。nav 里吸收型 skill 的依赖文件(references/、依赖的其他 skill)跟大 skill 的 reference/ 同等待遇——展平进该 skill 的小节index.md(真身)在前、依赖平铺在后;无依赖的 skill(如 grilling)就是平条目。MIRROR.md 不进 nav,从分类索引可达。

上游 README 不归档 —— wiki 里不需要两个诉说者。README 是上游作者写给"在 GitHub 上决定要不要装的人"的介绍与安装说明,这两个任务在本 wiki 都不存在:向读者介绍这个 skill 的是分类索引里的一句话(自己的声音),定义行为的是归档的主文件。把 README 也归档,等于让上游作者在自己的 wiki 里再讲一遍,还要替它维护死链归一化。要溯源上游原貌,在 MIRROR.md 里留一条钉 commit 的 README 永链即可。

几条硬性约束

  • reference/ 的大 skill,index.md 只是入口/引导,写两三行 + 几个链接指向 reference/ 就够了 —— 不要--8<--reference/ 全文塞进 index.md。每个 reference 自己作为独立 URL 存在, index 是个目录页,不是合订本;单文 skill 的 index.md 本身就是全文
  • index.md 控制在 500 行以内(目录页性质的通常只有 10-20 行)
  • reference/assets/ 都是"分类目录"reference/ 放具体文章(展平进父级 nav,见下文),assets/ 只放资产真身(css/js/svg 等)。两者都不在 nav 里单独成层,因此不需要 reference/index.md / assets/index.md 占位页
  • reference/ 只保留一层深(不要 reference/X/Y.md,Claude 可能只 head -100 预览嵌套引用导致漏读)
  • 资产放它所属 skill 自己的 assets/,跟该 skill 的 reference/ 平级 —— 不要把多个 skill 的资产 混进分类级的共享 assets/(重构前 writing/assets/ 就这么混过,newspaper 的 css 和 qu-ai-wei 的真身挤在一起,归属只能靠文件名猜)
  • 长 reference 文件(>100 行)顶部加目录,方便 Claude 预览时看到全貌
  • assets/ 下的代码用注释自我说明(设计令牌、组件类等放 CSS 注释里),markdown 端只做引导,不要把代码细节用 markdown 重述
  • snippets 引用 assets 的代码块一律用 ??? abstract 折叠(默认收起),避免长配置/长 CSS 把文章撑到看不到正文
  • 原文一律放 assets/,markdown 不要手抄。展示给前端读者的方式是 --8<-- snippets 引用 assets 里的源文件(构建时把原文塞进 HTML),不算"写两份" —— 源文件只在 assets/ 一处,markdown 里只写 --8<-- "docs/.../assets/foo.css" 这样的引用。绝对禁止把 yaml/css/js 内容直接复制粘贴到 markdown 代码块里跟 assets/foo.yaml 同时存在 —— 那才是双份维护
  • 完整独立代码文件不单独成页、不进 nav:要展示完整文件,就在讲它的 reference 文章 / post 里??? abstract 折叠 + --8<-- 引真身(如文末折叠展示 --8<-- "deploy.sh")。正文只放教学片段,完整文件折叠在文末就近查看即可
  • 真身位置看它是否要被运行:skill 专属资产(newspaper.css / .js / .svg)真身放 assets/、可按文件 URL 直接下载;项目运行文件(deploy.sh / mkdocs.yml / pyproject.toml / deploy-cert/* 等)真身必须留仓库根才能跑、被 zensical 读、靠相对路径定位项目根 —— 文章里一律 --8<-- "mkdocs.yml" 引真身绝不复制一份进 assets/(复制 = 双写,会脱节)
  • markdown 写法不要"写法 + 效果"双写:每种语法直接演示使用即可 —— AI 读源文件看到的是 markdown 写法,前端读者看到的是渲染效果,一份内容自然两个视图

reference / assets 都不在 nav 单独成层 → 不需要它们的 index.md 占位

mkdocs 的 nav: 项必须指向 .md,所以一个目录想在 nav 里成为折叠节点,就得有个 index.md。 但本 wiki 的规范是:reference 展平进父级 nav、assets 根本不进 nav(只存真身),两层都不在 nav 出现, 于是 reference/index.md / assets/index.md 这两个占位页都不需要。 (skill 自己的入口 index.md 照常保留,那是 skill 的门面,不是目录占位。)

nav 一律把 reference 展平到父级

mkdocs.ymlnav: 不会自动反映文件系统的目录结构 —— 它完全是手写的视觉组织。 本 wiki 的规范是:reference 这一层始终不在 nav 里出现,下面的文件直接列到父级(skill 主目录下)。

写法

- 写作口味:                                               # 分类
    - skills/writing/index.md                            # 分类索引
    - MkDocs Wiki: skills/writing/mkdocs-wiki/index.md   # ← 每个 skill 的 index 直接列在分类级
    - 报纸版 HTML: skills/writing/newspaper/index.md
    - 去 AI 味:                                           # ← 吸收型 skill:index.md 即真身
        - skills/writing/qu-ai-wei/index.md              #    真身在前
        - 51 条 AI 腔模式: skills/writing/qu-ai-wei/references/patterns.md  # ← references 照样展平,
        - 白名单: skills/writing/qu-ai-wei/references/whitelists.md         #    9 份全列(此处略)
    # MIRROR.md / assets 不进 nav —— 从分类索引 / 页面内链接可达

- FastAPI 后端:                                          # 大 skill:reference 展平
    - skills/fastapi/index.md
    - 项目布局: skills/fastapi/reference/项目布局.md       # ← 直接放父级,
    - 数据库: skills/fastapi/reference/数据库.md           #    哪怕 14 个子页也展平
    - ...

效果

Skills
├── FastAPI 后端
│   ├── (主索引 = FastAPI 后端入口)
│   ├── 项目布局
│   ├── 数据库
│   └── ...                    # reference 直接展开,无 "reference" 折叠节点
└── 写作口味
    ├── (分类索引)
    ├── MkDocs Wiki
    ├── 报纸版 HTML
    └── 去 AI 味               # 吸收型 skill:入口即 SKILL.md 真身
        ├── (真身入口)
        ├── 51 条 AI 腔模式    # references 展平,跟 reference/ 同等待遇
        └── ...

为什么一律展平

  • 多一层"reference"折叠让读者多一次点击,体感"还没进去内容"
  • reference 数量多(如 FastAPI 14 个)时,让父级 nav 长 —— 但这正符合读者预期, 他们能一眼看到全部主题。多一层折叠反而隐藏信息
  • nav 视觉跟文件系统解耦是本设计的核心 —— 把 reference 这层从 nav 拿掉不影响
    • 文件位置:仍在 docs/skills/<skill>/reference/<topic>.md
    • URL:仍是 /skills/<skill>/reference/<topic>/
    • snippets 引用路径:仍是 docs/skills/<skill>/reference/<topic>.md
    • AI 读仓库看到的目录结构:仍是标准 skill 布局 index.md + reference/ + assets/

assets/ 不进 nav,无例外 —— 它只是资产真身(css/js/svg 等)的存放目录。 资产的原文展示放在讲它的 reference 文章里(??? abstract 折叠 --8<--),真身本身可按文件 URL 直接下载。 (吸收型 skill 的真身不存在"藏在 assets 里要不要进 nav"的纠结——它就是 index.md 本身,见上文吸收规矩。)

关键认知nav 是视觉组织,文件系统是真实结构,两者解耦。 怎么调 nav 都不改变文件 URL,也不影响 AI 读仓库时看到的 skill 标准布局。

保留名

禁止使用 reference / assets / scripts 作为 .md 文件名 —— 它们会跟同级目录撞 URL,内容被静默丢弃。

代码块复制按钮:主题内置,无需插件

mkdocs.ymlcontent.code.copy 即可。按钮不在静态 HTML 里——由主题 JS bundle 在浏览器端运行时注入:找到 <code> 最近的 <pre> 祖先、赋 id,把 clipboard 目标指向 #<id> > code,整条链路不依赖 .highlight wrapper(那个 wrapper 只有代码注解 annotate 才需要),所以 Zensical 生成的裸 <pre><code> 结构也照常工作。因此"查看页面源码没看到按钮"不代表功能没生效,浏览器里打开才见真章。


  1. 钉钉开放平台接口限制。 

  2. 来源:dingtalk.com/api。