博客主题开发-常见缺陷清单与自检手册
AiKlog 主题开发 · 常见缺陷清单与自检手册
版本:v1.1(2026-09-15,对齐《第三方完整交付版》v1.1) 来源:EmForum 主题 v1.1.0 代码审查实录(发现 5 处问题)+ 手册配套脚本在在产主题上的二次验证(brutal / parchment,又发现 2 类同类问题) 配套文档:《AiKlog 博客主题开发规范(第三方完整交付版)》v1.0 · 配套工具:同目录
theme-lint.sh使用方式:提交主题包之前,请逐条过一遍第 2 节和第 4 节,并跑一遍第 3 节的脚本。
0. 为什么要出这份文档
规范(《第三方完整交付版》)已经把「协议怎么接、数据怎么取、样式怎么放」写清楚了,所以协议级错误(不注册、无样式、字段臆造)在这一代主题里基本绝迹——EmForum 审查中 13 条协议项全部通过。
但协议正确 ≠ 交付合格。本次审查暴露出的 5 个问题有个共同特征:
它们都不会让构建失败,也不会在控制台报警,代码看起来完全正常,只有真正点进去用、或者逐行读命名与文案时才会发现。
这类问题最危险——测试测不出来,用户会先发现。
更能说明问题的是:本手册配套的自检脚本写出来之后,第一时间就查出了两个我自己的主题的问题(brutal 的令牌漏报 5 个 + 悬空类 1 个,parchment 的历史遗留死 CSS)。也就是说,这不是「某人不够细心」的问题,而是人眼在这类检查上天然不可靠——必须交给工具。
问题分级:
| 级别 | 含义 | 判定标准 | |---|---|---| | 🔴 功能隐患 | 可能直接导致跳转异常、白屏、功能失效 | 用户可复现的错 | | 🟠 语义失真 | 文案 / 命名 / 视觉宣称了数据不存在的能力 | 「说的是 A,做的是 B」 | | 🟡 规范偏差 | 不影响运行,但违反规范或埋下维护陷阱 | 台账不一致、悬空样式 |
1. 问题总览
| # | 问题 | 级别 | 后果 | 规范依据 |
|---|---|---|---|---|
| 1 | 归档页年份锚点写 href="#y-2026" | 🔴 | 宿主 hash 路由被篡改,可能跳转/白屏 | §3.1 · §8.3 |
| 2 | 搜索页把「最新」标成「热门文章」 | 🟠 | 承诺了契约中不存在的能力 | §2.2 · §4.8 |
| 3 | 侧栏类名 ef-hot / 序号榜单配色 | 🟠 | 命名与视觉双重误导维护者与读者 | §2.4 · §4.7 |
| 4 | manifest.tokens 漏报 4 个令牌 | 🟡 | 令牌台账失真 | §1.1 · §5.2 |
| 5 | 悬空类 .ef-feed / .ef-side | 🟡 | 样式静默失效,靠继承「碰巧」正常 | §5.1 |
2. 逐条详解
2.1 🔴 锚点写入 location.hash —— 与宿主 hash 路由冲突
错误写法(EmforumArchiveView.vue 初版):
<!-- ❌ 危险:宿主的 hash 就是路由状态 -->
<nav v-if="years.length > 1" class="ef-chips">
<a v-for="y in years" :key="y" class="ef-chip" :href="'#y-' + y">{{ y }} 年</a>
</nav>
<section v-for="y in years" :id="'y-' + y" class="ef-year"> ... </section>
为什么错
宿主的 SPA 公开页地址形态是 /app#/blog?view=public —— # 后面是路由状态,不是滚动锚点。点击 #y-2026 会把地址改成 /app#y-2026,这等于把一段非法路由塞给 router:
- 路由匹配不到
y-2026→ 可能被兜到 404 / 首页,或渲染空白; - 即使配了 scrollBehavior,
hashchange也会被当作一次导航,与 Vue Router 自身的滚动管理打架; - 页面刷新后地址栏残留在
#y-2026,用户回到站点直接看到异常页。
这类 bug 在开发阶段几乎不可能自己发现——它只在真实地址栏环境(非 iframe 预览、非 file:// 打开)下才复现。
⚠️ 注意它的两种写法都要查:原生锚点 href="#y-2026",以及 Vue 绑定拼接 :href="'#y-' + y"——后者容易被漏掉(本手册的脚本第一版就漏了,已修)。
正确写法:
<!-- ✅ 用 button + scrollIntoView,全程不碰 location.hash -->
<nav v-if="years.length > 1" class="ef-chips">
<button v-for="y in years" :key="y" type="button" class="ef-chip" @click="jumpTo(y)">
{{ y }} 年
</button>
</nav>
<section v-for="y in years" :id="'y-' + y" :key="y" class="ef-year"> ... </section>
/* 年份跳转:滚动到对应 section,不改动 location.hash */
function jumpTo(y) {
const el = document.getElementById('y-' + y)
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
通用规则(按宿主路由形态选做法)
| 宿主路由 | 页内跳转的正确做法 |
|---|---|
| hash 路由(#/blog?...)—— AiKlog 当前形态 | button + scrollIntoView(),严禁把锚点写进 href 或 location.hash |
| history 路由(/blog/...) | 可用 href="#锚点",但建议同样用 scrollIntoView 以统一行为 |
| 站内页面跳转 | 一律用 ctx.postUrl() / 相对路径,不要手拼 #/p/ |
2.2 🟠 文案宣称「热门」,数据其实是「最新」
错误写法(EmforumSearchView.vue 初版第 29 行):
<!-- ❌ 没输关键词时展示的是「按 updated_at 倒序的最新 8 篇」,标题却写「热门文章」 -->
<span class="ef-feed">
{{ searched ? `关键词「${keyword}」的搜索结果` : '热门文章' }}
</span>
为什么错
两层问题,第二层更严重:
- 文不对题:该位置渲染的是
posts按时间倒序的前 8 条,与「热门」无关; - 臆造能力:「热门」隐含浏览量 / 点赞 / 评论数做排序依据,而契约
posts条目只有token / path / preview / created_at / file{name, slug, updated_at}(§2.2 明确「逐字段核对,禁止臆造」)——契约里根本没有这些计数字段。
⚠️ 这一条容易被当成「文案小事」忽略。但站在用户视角:点进「热门」看到的是最新文章,用户会认为你的站排序坏了 / 数据造假。首屏文案的可信度直接决定读者对整站的信任。
正确写法:
<!-- ✅ 标题描述真实数据;未检索时如实说明并提供行动指引 -->
<span class="ef-feed">
{{ searched ? `关键词「${keyword}」的搜索结果` : '最新更新 · 输入关键词开始检索' }}
</span>
通用规则
- 文案中的每一个形容词,必须能被某个真实字段证明;
- 拿不准时用中性词:最新 / 近期 / 全部 / 本篇 —— 而不是 热门 / 最火 / 推荐 / 精选(除非系统真有对应字段);
- 特别警惕这几个高危词:热门、排行、热搜、精选、浏览量、点赞、阅读时长、评论数、封面图 —— 它们在契约里完全不存在,任何使用都是臆造;
- 一个例外要说清楚:
file.author(作者对象)属于系统侧待透出字段(规范 §10-10),主题方允许使用(作者页聚合),但必须做可选链降级 —— 它「存在但当前可能为空」,与上面「根本不存在」是两回事,不要混淆。
2.3 🟠 命名与配色暗示「排名」,实现却是时间序
错误写法(EmforumSide.vue + style.css 初版):
<!-- ❌ 类名 ef-hot / ef-rank 在宣称"热度" -->
<ul class="ef-hot">
<li v-for="(p, i) in latest" :key="p.token">
<span class="ef-rank">{{ i + 1 }}</span>
<a class="ef-hot-t" :href="postHref(ctx, p)">{{ postTitle(p) }}</a>
<span class="ef-hot-v">{{ postDate(p, 'short') }}</span>
</li>
</ul>
/* ❌ 前三名配红/金/青"榜单色",视觉在强化"排名"语义 */
.ef-hot li:nth-child(1) .ef-rank { color: var(--th-hot); }
.ef-hot li:nth-child(2) .ef-rank { color: var(--th-accent-2); }
.ef-hot li:nth-child(3) .ef-rank { color: var(--th-accent); }
数据侧其实只是 posts 按更新时间取前 5 —— 既无热度,也无排名。
为什么错(比 2.2 更隐蔽)
- 类名是写给维护者的契约。半年后有人读到
ef-hot,会以为这里有热度计算的逻辑,改需求时按「热门」的心智去动它,越改越乱; - 配色是写给读者的语义。红 / 金 / 青前三名 = 排行榜的通用视觉语言,读者会自动理解为「这三篇最火」;
- 两者叠加,等于在代码和界面上同时植入了一个不存在的功能。
正确写法:
<!-- ✅ 类名如实描述:latest;序号统一弱化,不做榜单色 -->
<ul class="ef-latest">
<li v-for="(p, i) in latest" :key="p.token">
<span class="ef-latest-no">{{ i + 1 }}</span>
<a class="ef-latest-t" :href="postHref(ctx, p)">{{ postTitle(p) }}</a>
<span class="ef-latest-v">{{ postDate(p, 'short') }}</span>
</li>
</ul>
/* ✅ 序号统一弱化灰,不区分名次 */
.ef-latest-no { font-style: italic; color: #c3ccd8; min-width: 22px; text-align: center; }
通用规则
命名描述事实,不描述期望。
| 实际数据 | 应该叫 | 不应该叫 |
|---|---|---|
| 按 updated_at 取前 N | latest / recent | hot / top / rank / trending |
| 按人工置顶取 | pinned | hot |
| 按分类聚合 | by-cat | channel-hot |
配色同理:榜单配色(前三名差异色)只在真有排名数据时使用。
补充案例:同一规则也适用于版面文案。brutal 主题曾把首篇大卡标为「本期精选 / Editor's pick」(英文角标 Featured),而数据只是 posts[0] —— 已改为「头条 / Top story」。区别在于:「精选」宣称编辑挑选(不可证),「头条」描述版面位置(可证,就是排在最前的那条)。描述位置安全,描述品质危险。
2.4 🟡 manifest.tokens 漏报令牌
错误状态:manifest.js 声明 11 个令牌,style.css 实际定义 15 个 —— 漏报:
--th-accent-soft (强调色极浅底)
--th-accent-2-soft (点缀色半透明底)
--th-navy-2 (头部渐变中间色)
--th-hot (警示色)
为什么错
tokens 在规范 §1.1 里的定位是主题的令牌台账(「令牌名: 用途说明」)。它服务于:后台据此生成主题可编辑项、文档自动生成、跨主题一致性比对。台账与实际不符 → 后续任何基于台账的自动化都建立在错的数据上。
这个错特别容易犯:维护过程中新增一条样式变量,往往只记得写 CSS、忘了回填 manifest。在产主题 brutal 也漏了 5 个(--th-bw / --th-bw-2 / --th-sh / --th-sh-lg / --th-sh-sm,全部是硬边框与硬阴影尺寸变量)——不是偶发,而是结构性遗忘,所以必须用工具卡。
正确写法:manifest.js 的 tokens 与 style.css 的 --th-* 定义逐项一致(数量与命名都对齐)。
2.5 🟡 悬空类(模板有类名、CSS 无规则)
错误状态:
| 类名 | 出现位置 | CSS 状态 |
|---|---|---|
| .ef-feed | 列表页 / 分类页 / 搜索页 / 作者页 | 无规则,靠父级继承「碰巧」能看 |
| .ef-side | EmforumSide.vue 的 <aside> | 完全无定义 |
为什么错
不是「少写一条样式」这么简单:它意味着你写下的结构意图没有被样式兑现。当前显示正常是因为父容器的字号 / 颜色恰好被继承下来 —— 一旦父级样式调整,这里会毫无征兆地塌掉,而排查时没人会想到一个「看起来有样式」的类根本没规则。
正确写法:
.ef-feed { font-weight: 700; color: var(--th-ink-2); letter-spacing: 0.5px; }
.ef-side { min-width: 0; } /* 防 grid 子项被内容撑破 */
同类问题:
brutal的.bt-hero-main(hero 左栏 grid 子项)也无定义 —— 已补min-width: 0。grid 子项忘记min-width: 0是高频疏漏,长标题和搜索框会直接把列宽撑破。
3. 自查脚本(theme-lint.sh)
同目录提供可执行脚本 theme-lint.sh,复制到主题目录运行即可:
cd web/src/themes/<id>
bash theme-lint.sh # 前缀自动推断
bash theme-lint.sh ef- # 或显式指定类名前缀
echo $? # 0 = 通过;1 = 存在必修项
3.1 脚本全文
#!/usr/bin/env bash
# AiKlog 主题交付自检脚本
# 用法: cd web/src/themes/<id> && bash theme-lint.sh [类名前缀]
# 覆盖: 悬空类 / 死 CSS / tokens 台账 / hash 路由锚点 / 高危文案词 / 契约外字段
set -u
PREFIX="${1:-}"
fail=0
say() { printf '\n\033[1m%s\033[0m\n' "$1"; }
# 扫描代码(自动剔除注释);注意用 ENVIRON 传正则 —— awk -v 会吞掉反斜杠,使 \.cover 退化成 .cover
scan() {
RE="$1" awk '
FNR == 1 { inblk = 0; inhtml = 0 }
/\/\*/ { inblk = 1 }
inblk { if (/\*\//) inblk = 0; next }
/<!--/ { inhtml = 1 }
inhtml { if (/-->/) inhtml = 0; next }
/^[[:space:]]*(\/\/|\*)/ { next }
$0 ~ ENVIRON["RE"] { printf "%s:%d: %s\n", FILENAME, FNR, $0 }
' *.vue 2>/dev/null
}
# ── 1. 类名交叉比对(悬空类 + 死 CSS)────────────────────────────
say "[1/5] 类名交叉比对"
if [ -z "$PREFIX" ]; then
PREFIX=$(grep -ho 'class="[^"]*"' *.vue 2>/dev/null \
| sed 's/class="//;s/"//' | tr ' ' '\n' \
| grep -o '^[a-z][a-z]*-' | sort | uniq -c | sort -rn | head -1 | awk '{print $2}')
fi
echo " 使用类名前缀: ${PREFIX:-<未能推断,请作为第 1 个参数传入>}"
if [ -n "$PREFIX" ]; then
grep -o "\.${PREFIX}[a-z0-9-]*" style.css | sed 's/^\.//' | sort -u > /tmp/_css.txt
grep -ho 'class="[^"]*"' *.vue | sed 's/class="//;s/"//' | tr ' ' '\n' \
| grep "^${PREFIX}" | sort -u > /tmp/_used.txt
empty_used=$(comm -23 /tmp/_used.txt /tmp/_css.txt)
empty_css=$(comm -13 /tmp/_used.txt /tmp/_css.txt)
echo " ── 悬空类(模板有 · CSS 无):"
if [ -n "$empty_used" ]; then echo "$empty_used" | sed 's/^/ [X] /'; fail=1; else echo " [OK] 无"; fi
echo " ── 死 CSS(CSS 有 · 模板无):"
if [ -n "$empty_css" ]; then echo "$empty_css" | sed 's/^/ [!] /'; else echo " [OK] 无"; fi
fi
# ── 2. tokens 台账一致性 ──────────────────────────────────────
say "[2/5] manifest.tokens 台账"
if [ -f manifest.js ] && [ -f style.css ]; then
grep -o "\-\-th-[a-z0-9-]*" manifest.js | sort -u > /tmp/_decl.txt
grep -o "\-\-th-[a-z0-9-]*:" style.css | sed 's/:$//' | sort -u > /tmp/_def.txt
miss=$(comm -13 /tmp/_decl.txt /tmp/_def.txt)
fake=$(comm -23 /tmp/_decl.txt /tmp/_def.txt)
echo " ── 定义了但未声明(漏报):"
if [ -n "$miss" ]; then echo "$miss" | sed 's/^/ [X] /'; fail=1; else echo " [OK] 无"; fi
echo " ── 声明了但未定义(虚报):"
if [ -n "$fake" ]; then echo "$fake" | sed 's/^/ [X] /'; fail=1; else echo " [OK] 无"; fi
else
echo " [!] 未找到 manifest.js 或 style.css,跳过"
fi
# ── 3. hash 路由锚点(硬失败项)────────────────────────────────
# 覆盖两种真实写法:「href="#y-2026"」与 :href="'#y-' + y"(Vue 绑定拼接)
say "[3/5] hash 路由锚点(宿主为 #/blog,禁止把锚点写进 hash)"
hits=$( { scan 'href=[^[:space:]]*#'; scan 'location\.hash[[:space:]]*='; } )
if [ -n "$hits" ]; then echo "$hits" | sed 's/^/ [X] /'; fail=1; else echo " [OK] 无"; fi
# ── 4. 高危文案词(需人工确认数据来源)──────────────────────────
say "[4/5] 高危文案词(命中不等于错,需人工确认有无字段支撑)"
hits=$(scan '热门|排行|热搜|精选|推荐|浏览量|阅读时长|热度')
if [ -n "$hits" ]; then echo "$hits" | sed 's/^/ [!] /'; else echo " [OK] 无"; fi
# ── 5. 契约外字段 ────────────────────────────────────────────
say "[5/5] 契约外字段(posts 条目只有 token/path/preview/created_at/file.*)"
hits=$(scan '\.views|\.likes|\.comments|\.cover|\.summary|\.readTime|\.comnum')
if [ -n "$hits" ]; then echo "$hits" | sed 's/^/ [!] /'; else echo " [OK] 无"; fi
echo " 注: file.author 属系统侧待透出字段(规范 §10-10),允许使用,但必须可选链降级。"
say "== 结论 =="
if [ "$fail" -eq 0 ]; then echo "[OK] 自动检查项全部通过([!] 项仍需人工确认)"; else echo "[X] 存在必须修复项,见上方标记"; fi
exit "$fail"
3.2 已知盲区(务必人工复核)
脚本只做机械可判定的检查,以下情况它会给误报或漏报:
| 盲区 | 表现 | 处理方式 |
|---|---|---|
| 动态拼接类名 | :class="'bt-pat-' + name" 拼出的类,静态扫描看不到 → 被误报为死 CSS | 命中死 CSS 时先确认是否有动态拼接(brutal 的 bt-pat-a~f 即此类,实为误报) |
| 前缀自动推断可能取错 | 主题用 bt- 而非常见的全名前缀 | 显式传参 bash theme-lint.sh bt- |
| 只扫 .vue | helpers.js / index.js 里若含类名或文案,不在检查范围 | 人工抽查辅助文件 |
| 注释已剔除 | 这是特性(避免「解释『不要这样写』的注释被当成违规」),但也意味着注释里的真实问题不会报 | 正常情况无需担心 |
| 文案词命中 ≠ 有错 | [!] 项只代表「需要你确认数据来源」 | 逐条人工判断,见第 2.2 节 |
3.3 脚本实测记录(验证它真的有效)
脚本产出后做了 4 组对照测试,全部符合预期:
| 组 | 对象 | 预期 | 实际结果 | 退出码 |
|---|---|---|---|---|
| A | emforum 1.1.1(已修复版) | 全绿、零误报 | ✅ 5 项全 OK,无 [!] | 0 |
| B | emforum 1.1.0(含缺陷版) | 精确命中已知缺陷 | ✅ 命中悬空类 ef-feed/ef-side、令牌漏报 4 个、hash 锚点(定位到 EmforumArchiveView.vue:25)、「热门文章」文案 | 1 |
| C | brutal(在产主题) | 未知 | ✅ 查出 2 类真实问题:令牌漏报 5 个、悬空类 bt-hero-main;另发现「本期精选」文案不一致 | 1 → 修复后 0 |
| D | parchment(内置主题) | 未知 | ✅ 查出 1 处历史遗留死 CSS(pa-comments),已记录待清理 | 0 |
开发脚本本身也踩了两个坑并已修正,一并记录以免重犯: ① 第一版把注释里的示例文本当成违规(如注释写着「不要写
href="#y-2026"」反被判违规)→ 增加注释剔除; ② 正则用awk -v传参导致反斜杠被吞,\.cover退化成.cover,把bt-cover之类的类名误报成契约外字段 → 改用ENVIRON传参。
4. 提交前自检清单
在规范 §9 验收清单基础上,新增本次暴露的 6 项(标 ★)。
4.1 构建与协议
- [ ]
npm run build零错误零警告 - [ ] console 无
[themes] 主题缺少 entry/id警告 - [ ]
index.js模块加载即registerTheme(非回调式) - [ ]
manifest用title/desc(不是name/description) - [ ] 入口组件内
import './style.css';选择器全部挂.xx-root - [ ] ★
manifest.tokens与style.css实际--th-*逐项一致
4.2 数据契约
- [ ] 数据全部
inject('themeContext'),无 props - [ ] 仅使用真实字段:
token / path / preview / created_at / file{name, slug, updated_at} - [ ] 不臆造
views / likes / comments / cover / summary / readTime / comnum - [ ]
file.author(待透出字段)使用处全部可选链降级,缺数据时给出明确提示而非伪造 - [ ] 不自行重排
posts - [ ] 三态齐全:loading / error / 空态
- [ ] 首篇不重复(头条/推荐位与网格池互斥)
4.3 文案与语义(★ 本次新增区块)
- [ ] ★ 界面上每个形容词都能被真实字段证明;无「热门 / 排行 / 精选」类不可验证文案
- [ ] ★ 类名描述事实(
latest/pinned)而非期望(hot/rank) - [ ] ★ 榜单配色(前三名差异色)仅在真有排名数据时使用
- [ ] 标签云
v-if="tags.length",渲染t.name,一期输出非链接<span> - [ ] 站名用
<div>,不占<h1>
4.4 交互与路由
- [ ] ★ 全站无把锚点写进 hash 的写法(
href="#..."与:href="'#' + x"都要查) - [ ] ★ 页内跳转一律
button+scrollIntoView() - [ ] 文章点击走
ctx.postUrl(post)进入 SPA 文章页#/post/{token}(随主题走、可带评论等插件);分享 / SEO 场景用ctx.postSsrUrl(post)跳 SSR 页/{slug} - [ ] AI 问答读
reply,429 有友好提示,空输入禁用按钮
4.5 样式
- [ ] Reset 四件套齐全(含
.xx-root *,别漏通配符本身) - [ ] ★ 无悬空类、无死 CSS(
theme-lint.sh输出为空,动态类误报已人工排除) - [ ] grid 子项加
min-width: 0(长内容撑破列宽的高频疏漏) - [ ] 移动端断点正常,侧栏 / 网格降列正确
- [ ] 切换其它主题后无样式残留
4.6 接入与部署
- [ ]
web/src/themes/all.js已追加import '@/themes/<id>'(集中注册;列表页 BlogView 与文章页 BlogPostView 共用) - [ ]
settings.go白名单已加<id>(控制台能保存成功) - [ ] 已交付
server/data/themes/<id>/ssr.css+manifest.json(一期必修,缺此文章页不跟随主题) - [ ] 投放后
/blog?theme=<id>与/{slug}?theme=<id>气质一致,无「列表深色、文章亮色」割裂
5. 建议补进规范附录 C 的条目
以下 6 条建议追加到《第三方完整交付版》附录 C「常见错误与反面教材」(可直接粘贴):
| 错误 | 后果 | 正确做法 |
|---|---|---|
| 把页内锚点写进 href="#x" 或 location.hash | 宿主 hash 路由被篡改,可能跳转/白屏 | button + scrollIntoView(),不碰 hash |
| 文案写「热门 / 精选」而数据只有时间序 | 承诺不存在的能力,用户认为排序坏了 | 文案描述真实字段;「描述位置安全,描述品质危险」(头条 ✓ / 精选 ✗) |
| 类名用 hot / rank 表达时间序列表 | 命名与视觉双重误导后续维护 | 按事实命名(latest / pinned) |
| manifest.tokens 与实际令牌不一致 | 令牌台账失真,自动化全部建立在错数据上 | 逐项对齐,用 comm 比对(结构性遗忘,须工具卡) |
| 模板有类名、CSS 无规则(悬空类) | 样式静默失效,父级一动就塌 | 类名交叉比对,补齐或删除 |
| grid 子项未加 min-width: 0 | 长标题/输入框撑破列宽,窄屏溢出 | 所有 grid/flex 子项显式 min-width: 0 |
附录:本次核对通过的部分(反向清单)
为避免对方误判,此处列出审查中确认无误、无需改动的项——这些是做得好、应当保持的地方:
| 项 | 核对方式 | 结论 |
|---|---|---|
| 注册方式 / manifest 字段 / inject 取数 / 进 SPA 文章页(ctx.postUrl 返回 #/post/{token}) | 逐行对照 minimal 参考实现 | ✅ 全合规 |
| 契约纪律 | 全量扫描字段访问 | ✅ 未臆造任何字段 |
| 死 CSS | 74 个类名 × 11 个模板全量交叉比对 | ✅ 无 |
| 动态 :class 绑定 | 逐个核对 .ef-chip.on / .ef-btab.on / .ef-pagenum.on / .ef-avatar.c1-c3 / .ef-cloud-tag.big | ✅ 全部有对应规则 |
| 外部链接可达性 | 对照 routes.go | ✅ feed.xml / sitemap.xml / blog/ask 均已注册 |
| SSR 变量覆盖 | 对照 public_html.go 的 :root 与附录 A 钩子表 | ✅ 均对得上,无变量写空 |
| 作者页降级处理 | 读代码 | ✅ 无作者字段时显示明确提示而非伪造数据(处理得很干净,保持) |
| 标签云降级 | 读代码 | ✅ 空时整块隐藏,非链接输出 |
本手册由 AiKlog 侧整理,基于 EmForum v1.1.0 审查实录,并经配套脚本在 brutal / parchment 上二次验证。后续每轮主题审查发现的新问题会持续追加,形成累计的交付质量基线。