Opus 架构阶段 · 落地清单
配套 UPGRADE_PLAN.md。这是动代码前给用户过目的清单,不是已完成记录。 勾选规则:
[ ]待办 /[~]进行中 /[x]完成。改动应小步 git 提交(news-site-test 已有 git)。
最后更新:2026-07-04 · 状态:分支 feat/architecture-upgrade:A(骨架)、C2/D2/D3/D4/D6/D8、F(主题脚手架)、B slice 1(配置驱动首页板块)完成,均构建通过、B slice 2(分类进导航下拉)、taxonomy-tags、C1 去重收敛、E slice 1(逐篇翻译骨架)、D6/D7 收尾、E8 canonical。Opus 架构阶段基本完成;Fable 视觉皮肤(theme① 定稿 + 组件精修 + H7/H8)已交付 2026-07-04。 剩:E slice 2(站点级 EN 切换 + chrome i18n,需定英文措辞 + 配合 JSM)、JSM 侧集成(E6:注入 block id / 产出翻译 JSON / home_blocks 编写 / 社媒 feed)、H3 CORS 确认、H6 分页页瘦身。
续接指南(新 session 如何无缝接手)
- 分支:
feat/architecture-upgrade(master= 升级前基线还原点)。工作区应干净;git log --oneline看 28+ 提交,每个提交信息都写清了做了什么 + 验证结果。 - 构建/验证:仓库根
bundle exec jekyll build(产物_site/)。本 session 全程”改 → build → grep 比对基线”验证(标签/分类计数、单一 html/body/title、feed URL 等),未破坏任何输出。 - 文档三件套:本清单(逐项状态 + commit 号)、UPGRADE_PLAN.md(决策 / 板块 v4 / schema / 决策记录)、JSM_CONTRACTS.md(JSM 侧对接契约 + 行动清单)。三份都在 git 里。
- 当前:Opus 架构阶段基本完成。剩 E slice 2(站点级 EN 切换 + chrome ui-strings,需定英文)+ JSM 侧集成 + Fable 视觉皮肤。
- 注意:文章内容/版本由 JSM/用户在 JSM 里管,工作区可能出现内容文件增删(如某次
-v6删除是用户有意为之)——不要擅自提交别人的内容改动,发现异常先 surface 给用户。
A. 布局骨架(修”布局体系倒置”)
- A1 单一
_layouts/default.html:完整<html>/<head>/<body>骨架(wrapper+header+footer+共用脚本)+ `<main>JSM ↔ news-site-test 对接契约
面向 JSM 开发。news-site-test(Jekyll,站名「去共化」)负责渲染;JSM 负责写数据 / 生成产物 / 协调子站。本文件逐条列出 Jekyll 侧现在依赖或期待 JSM 提供的契约:格式、位置、当前状态、以及 JSM 需要做什么。
配套:UPGRADE_PLAN.md(总计划)、OPUS_CHECKLIST.md(Jekyll 侧进度)。 分支:
feat/architecture-upgrade。最后更新:2026-07-03。
契约总览
| # | 契约 | 位置 | 谁写 | Jekyll 侧状态 | JSM 侧状态 |
|---|---|---|---|---|---|
| 1 | 首页板块 home_blocks.yml |
_data/home_blocks.yml(仓库内) |
JSM | ✅ 渲染器已就绪 | ⬜ 待接管写入 |
| 2 | 子站 Latest Posts JSON | 各子站的公开 URL | JSM(latest_posts.py) |
✅ 通用组件已就绪 | ✅ 三个首页 feed 均已产出 |
| 3 | 逐篇翻译(i18n) | 文章 front matter + assets/i18n/*.json + 正文 data-i18n |
JSM | ✅ 前端骨架已就绪 | ⬜ 待做(最大一块) |
| 4 | Taxonomy 词条文件 | _tag/*.md、_category/*.md |
JSM | ✅ 消费中 | ✅ 已在管 |
| 5 | 文章版本身份 | 文章 front matter | JSM | ✅ 消费中 | ✅ 已在管 |
| 6 | URL / 主题 config 键 | _config.yml |
人/JSM | ✅ 消费中 | 可选 |
| 7 | Chrome 文案 ui-strings(未来) | _data/ui_strings.yml |
JSM | ⬜ 未建 | ⬜ 未来 |
图例:✅ 完成 · 🟡 部分 · ⬜ 未做
契约 1 —— 首页板块 _data/home_blocks.yml
JSM-owned artifact。 Jekyll 首页完全按此文件渲染;JSM 写它、并把每个子站的 Latest Posts JSON 注册为一个 source。加子站 = 往这里加一个 block,Jekyll 模板一行不改。
消费方:_layouts/home.html → _includes/blocks/render.html → _includes/blocks/{featured-swiper,subsite-feed,post-list}.html。
顶层结构
schema: home-blocks.v1 # 版本头,JSM 校验/升级用
blocks: # 数组;同一 slot 内按数组顺序渲染
- { type: …, slot: …, … }
block 字段
| 字段 | 必填 | 取值 | 说明 |
|—|—|—|—|
| type | 是 | featured-swiper / subsite-feed / post-list / series / archives / taxonomy-tags / taxonomy-cats | 未知 type 渲染器静默跳过(向前兼容) |
| slot | 是 | nav / hero-left / hero-right / main-top / main / sidebar / footer | 决定渲染到哪个区 |
| source | 视 type | own(自有 Jekyll 内容)/ 绝对 URL / 站内相对路径 | subsite-feed 用 URL;post-list/featured-swiper 用 own |
| variant | subsite-feed 必填 | focus-list / cards / compact / ticker | 未知 variant 按 compact 兜底。cards 渲染为可滑动 Swiper(桌面一屏 3 张 / 移动 2 张,两侧箭头翻页)——count 建议 3 的倍数(如 9) |
| count | 否 | 整数 | 显示条数;缺省 6。“子站少→count 大填满竖条;子站多→多 block 各 count 小” |
| title | 否 | 字符串 | 板块小标题(kicker) |
| more_url | 否(subsite-feed) | URL | 板块底部渲染「更多 →」,链到子站页面(feed 只显前几条时用;静态、独立于 feed 加载,feed 挂了链接仍在)。JSM 注册子站时一并提供其页面 URL |
| autoplay | featured-swiper | 布尔 | 默认 false(手动 swiper) |
| fill_count | 否(featured-swiper) | 整数 | swiper 下方渲染 N 条 featured: true 文章的文字链列表,把 hero-left 垫到与 hero-right 等高;缺省 0(不渲染) |
| paginate | post-list | 整数 | 每页条数;缺省 6 |
| id | 否(建议 JSM 加) | 稳定字符串 | JSM 编辑器做增删排序 diff 的稳定 key(渲染器目前不依赖,但建议带上) |
| count(series) | 否 | 整数 | 专题系列板块(type: series,2026-07-06;缺省 title「专题系列」、count 5):列出各系列(按最近文章先后,取前 count 个),每个链到 /series/<名称>/;系列数 > count 时显示「更多 →」链到 /series/ 总览页。系列来自文章 front matter series: 名称(+ 可选 series_index: N,缺省按日期先后计序);文章页标题下渲染「名称(一)」标记并链到系列页。系列为空时整块不渲染。per-系列页需 JSM 生成 _series/<名称>.md(layout: seriespage,series: 名称,可选 title/description)——同 tag/category 机制;/series/ 总览页由 Jekyll 从文章聚合,无需生成 |
| —(archives) | — | — | 往期回顾板块(2026-07-06,type: archives,可选 title 缺省「往期回顾」):各年度 + 本年度各月份做成横向 chip 流(满行换行),不显篇数。链接指向 JSM 生成的 _yearly(/YYYY/)/_monthly(/YYYY-MM/)页面;对应页面不存在时渲染为纯文本(JSM 补页后自动变链接) |
槽位与布局
nav→ 导航区(目前taxonomy-cats用;由 header.html 直接渲染分类下拉,暂不从 home_blocks 读取——见备注)hero-left/hero-right→ 首屏两栏(左轮播 / 右焦点 feed)main-top→ 主栏顶部(社媒卡片)main→ 主栏(最新文章列表)sidebar→ 右侧竖条,专用于子站 feed 板块栈(从上往下叠,adaptive count)footer→ 预留
当前默认(v4,Jekyll 侧手写,待 JSM 接管)
schema: home-blocks.v1
blocks:
- { type: featured-swiper, slot: hero-left, source: own, autoplay: false, title: 精选, fill_count: 2 }
- { type: subsite-feed, slot: hero-right, source: "https://de-ccp.org/daily/assets/data/picked/top-news.json", variant: focus-list, count: 9, title: 焦点头条 }
- { type: subsite-feed, slot: main-top, source: "https://de-ccp.org/i-pulse/assets/data/latest_posts.json", variant: cards, count: 9, title: 社媒快讯, more_url: "https://de-ccp.org/i-pulse/" }
- { type: post-list, slot: main, source: own, variant: list, paginate: 6, title: 最新文章 }
- { type: subsite-feed, slot: sidebar, source: "https://de-ccp.org/i-news/assets/data/latest_posts.json", variant: compact, count: 8, title: 新闻速览, more_url: "https://de-ccp.org/i-news/" }
- { type: subsite-feed, slot: sidebar, source: "https://de-ccp.org/rights-watch/assets/data/latest_posts.json", variant: compact, count: 8, title: 维权动态, more_url: "https://de-ccp.org/rights-watch/" }
JSM 要做
- 提供编辑/写入
_data/home_blocks.yml的能力(读子站注册表 → 生成 block;保持schema头)。 - 文件头保留
# JSM-owned artifact注释;写入用原子替换。 - (建议)给每个 block 生成稳定
id。
备注:
taxonomy-cats(分类导航下拉)目前由header.html直接用site.category渲染,未从 home_blocks 读取。若将来要让 JSM 控制导航分类,可扩展渲染器读slot: nav。
契约 2 —— 子站 Latest Posts JSON(subsite-feed 数据源)
通用组件 assets/js/subsite-feed.js 在浏览器端 fetch 每个 subsite-feed block 的 source URL 并渲染。这正是 JSM latest_posts.py 的产物,字段已对齐。
item 字段(每条)
[
{
"title": "标题", // 必填
"link": "https://…", // 必填,目标链接
"pubDate": "2026-07-03", // 可选,compact/cards 显示时间
"cover_img": "https://…", // 可选,cards 变体的图;缺失则 cards 降级为纯文字卡
"weight": 20260703000000 // 可选,排序权重(大在前)
}
]
(已核对真实 de-ccp.org/i-news/.../latest_posts.json,字段完全一致。)
组件行为
- 排序:
weight降序;并列或缺失时按pubDate降序。 - 变体用字段:
focus-list= title/link(首条大标题 + 其余列表);cards= cover_img/title/pubDate;compact= title/link/pubDate;ticker= title/link。 - 降级三态:加载中「载入中…」/ 空数组或空
暂无内容/ fetch 失败加载失败。空 feed 不报错、优雅显示空态。 - 安全:所有文本
escapeHtml;link/cover_img只放行http(s)://与站内/…(掐javascript:)。 - CORS:跨域 feed(de-ccp.org)需返回
Access-Control-Allow-Origin: *(GitHub Pages 默认满足)。
当前接线状态
| 板块 | source | 状态 |
|—|—|—|
| 焦点头条 | de-ccp.org/daily/assets/data/picked/top-news.json | ✅ 真实(旧 picked 格式,仅 title/link/weight,组件已兼容降级) |
| 新闻速览 | de-ccp.org/i-news/assets/data/latest_posts.json | ✅ 真实 |
| 社媒快讯 | de-ccp.org/i-pulse/assets/data/latest_posts.json | ✅ 真实(含 cover_img,cards 变体带图) |
feed 语言:当前三个子站 feed 全是中文内容——包括新闻速览/Formal News(它是从英语新闻转述翻译而来的中文新闻,不是英文)。所以没有”天生英文、免翻译”的子站。若要首页 feed 也能切英文,JSM 需为每个子站逐条翻译标题、产出并列的
latest_posts.en.json,subsite-feed.js在英文模式抓.en变体(home_blocks.yml加source_en或约定.en后缀)。属首页 i18n(E slice 2+),当前未做。
JSM 要做
- 社媒快讯真实 feed(
i-pulse)已上线并接线;三个首页 feed(daily / i-pulse / i-news)均为真实源。 - 长远把旧 picked
top-news.json(仅 title/link/weight)统一成标准latest_posts+ overrides 形态,全站只剩一种 feed 形状。 - (可选)
latest_posts输出加category字段,则 cards 可显示分类。
契约 3 —— 逐篇翻译 i18n(JSM 侧最大一块)
方案 A:中文永远是页面的真实 HTML(利中文 SEO),英文按需由 JS 从翻译 JSON 加载、作为副本插到中文块后,切换 中/英/双语。前端已全部就绪(assets/js/article-i18n.js + assets/css/article-i18n.css + _includes/article-lang-switcher.html,post/news 布局按 page.translations 门控挂载)。JSM 只需产出三样东西:front matter、翻译 JSON、正文 block id。
① 文章 front matter(JSM 写入 CN 文章)
lang: zh
default_lang: zh
translations:
en: /assets/i18n/<c_id>.en.json # 同源路径;可多语言 translations: { en: …, ja: … }
有 translations 时,布局才加载 i18n 资源、插入切换器、给 <h1> 加 data-i18n-title。
② 正文 block id(JSM 注入到 CN markdown)
每个可翻译的正文块要有稳定 data-i18n="<id>"。Kramdown 用 IAL 语法:
这是中文第一段。
{: data-i18n="b_a1c3" }
## 小标题
{: data-i18n="b_7f20" }
> 引用。
{: data-i18n="b_9e0d" }
关键约束:id 必须稳定、内容锚定,绝不能用位置序号(p1/p2/p3)。 否则作者中间插一段就全体错位、翻译全对不上(和 tag 手工维护烂掉是同一种病)。建议 JSM 像发 c_id 那样发短 uuid 或内容 hash,并在编辑时维护映射。
block 模型(建议 JSM 采纳的可翻译元素集):段落 <p>、标题 h2–h4、引用 blockquote、图说 figcaption、列表项(可选)。图片/表格本体共享不译(其 alt/caption 可另做)。
③ 翻译 JSON(JSM 生成,放同源 assets/i18n/)
{
"post_id": "<c_id>",
"lang": "en",
"title": "English Title", // 对应 data-i18n-title 的 <h1>
"blocks": {
"b_a1c3": "First English paragraph.", // 纯文本 → textContent(安全)
"b_7f20": "An English Heading",
"b_9e0d": { "type": "html", "content": "English with <a href=\"/x/\">link</a>." } // 需要链接/加粗时 → innerHTML
}
}
blocks的 key 必须与正文data-i18n一一对应。- 纯文本值 →
textContent(最安全);富文本用{ "type": "html", "content": … }→innerHTML(JSM 自产、同源、可信)。
④ 对齐校验(JSM check-script —— 命门)
JSM 发布时必须校验:正文里的每个 data-i18n 都有对应的 blocks key,反之亦然。否则文章一多,必然出现”中文有这段、英文漏翻”或”JSON 有翻译、正文没对应块”。这条是整个方案能不能长期不烂的关键。
前端已实现(JSM 无需管)
?lang=zh|en|both可分享链接 +localStorage记忆;<html data-article-lang>+ CSS 控三态;标题切换;按钮aria-pressed。- 中文原文始终在 DOM,切回中文零成本、不丢原文。
?lang=en变体的 SEO canonical 已自动指向干净中文 URL(jekyll-seo-tag),不稀释中文收录。
兼容:未翻译 / 部分翻译(前端已保证,JSM 无需特殊处理)
- 未翻译的文章:只要不写
translationsfront matter → 不出现切换按钮、不加载任何 i18n CSS/JS,就是普通中文页、零开销。所以”有的翻译、有的不翻译”是默认兼容的混合状态;JSM 只给真正翻了的文章写translations.en即可。 - 部分翻译:某篇有
translations.en但 JSON 只翻了一部分块 → 切英文时有译文的块显示英文、没译文的块自动保留中文(JS 只给有译文的块加隐藏类)。整篇 / 部分 / 不翻译三种都不会出错。
JSM 要做
- CN 文章 front matter 写
translations.en(+default_lang)。 - 注入稳定
data-i18nblock id(非位置式)+ 维护映射。 - 生成
assets/i18n/<c_id>.en.json(title + blocks,key 对齐正文)。 - check-script 校验正文
data-i18n↔ JSONblocks对齐。 - [ ](可给 JSM 编辑器加:翻译面板、”标记段落可翻译”命令、
?lang=预览)。
契约 4 —— Taxonomy 词条文件(冻结,勿改结构)
JSM 生成/维护,Jekyll 消费。
- 位置:
_tag/<name>.md、_category/<name>.md(对应 Jekylltag/categorycollection)。 - front matter 形状:
--- layout: tagpage # 或 categorypage tag: "六四" # 或 category: title: "标签: 六四" # 或 "分类: X" --- - 词条描述(可选):
_metas/tags.md前置里name: tags+<词条>: <描述>映射(tag-info.html 消费)。 - Jekyll 侧:标签/分类页、导航分类下拉、首页热门标签、
/tags/全部标签页都从这里来。加词条忘了建文件 → 页面缺失,所以由 JSM 保证词条文件齐全(含跨子站文章的引用)。
契约 5 —— 文章版本身份(冻结)
JSM 拥有版本模型,Jekyll 按它去重取最新版。
- 字段:
c_id、version_of、base_c_id、update_date。 update_date必须是完整时间戳%Y-%m-%d %H:%M:%S(Jekyll 去重 Liquid 按此格式比较;仅日期会在同日多版本时并列出错)。- Jekyll 侧去重已收敛到单一
_includes/latest-versions.html(home/all-tags/all-categories/tagpage/categorypage 共用)。post/news 页另用 O(n) 单点查找取”本文最新版”。 - H9 已实现、默认未启用(2026-07-06):JSM 发布新版本时自动给旧版打
superseded: true、删除最新版时撤标,并提供存量回填/对账 API(superseded-status/superseded-backfill)。Jekyll 侧_includes/latest-versions.html已双轨:_config.yml的version_dedup: superseded切 O(n)where_exp快路径,缺省liquid仍走 O(n²) 自纠错老路。启用顺序必须先回填后切 flag,步骤见 JSMdocs/SUPERSEDED_VERSIONS.md。
契约 6 —— config 键(_config.yml)
url/test_url:canonical 生产域 / JSM 测试链接域(JSM v0.31.19public_url_resolver)。Jekyll 的 SEO/sitemap 用url。ui_theme:主题选择键(用ui_theme而非 Jekyll 保留键theme)。缺省neutral;JSM 可写它来切换默认主题(可选值neutral/ink-wash)。lang: zh_CN:站点语言(jekyll-seo-tag 的 og:locale)。- 搜索范围开关
global_search(布尔)+search_peers(绝对 URL 列表):本站索引assets/data/search.json(构建时生成:latest-versions 去重 + 滤 hidden,字段title/url/date/tags)恒被搜;global_search: true时额外合并search_peers里各站点的 search.json(跨站全局搜索),false只搜本站。search_peers用绝对地址(各站点域/子路径不同)。生产同域(de-ccp.org)时同源;跨域抓取需目标资源域放行 CORS,否则该 peer 被search.js静默跳过(不影响本站结果)。子站 search.json 的url必须是带 repo 子路径的绝对地址{url}/{repo}{文章路径}(如https://de-ccp.org/i-news/2026/...,与 latest_posts.json 的link同规),样板用site.url+site.repo生成。JSM 维护:每加一个子站,往主站(及需要全局搜索的子站)search_peers追加一行其 search.json 绝对地址。 url: https://de-ccp.org(2026-07-05 起):主站与全部子站共用同一 url。子站标准形态(经 GitHub 实测,2026-07-05 修正):baseurl: ""(必须为空)+repo: "<仓库名>"(子路径由该自定义键表达,子站公开地址 ={url}/{repo}/)+src_url(子站引用主站 css/js 的域;测试期指https://test.qugonghua.org,上线改为https://de-ccp.org)。旧说法「子站baseurl: /<仓库名>」作废。子站样板:同级目录../subsite-template/(独立 git 仓库,README 有三步上手),新子站从它复制。
契约 7 —— Chrome 文案 ui-strings(未来,E slice 2 才需要)
站点级 EN⇄中 切换(导航按钮翻转整站 chrome 文案)需要一套英文 ui-strings。尚未实现(Jekyll 侧未建、JSM 侧未做)。若将来做,建议:
_data/ui_strings.yml:{ zh: {...}, en: {...} },key 覆盖导航(分类/联系/关于)、板块标题(头条/焦点头条/社媒快讯/最新文章/新闻速览/热门标签/全部标签)、分页(上一页/下一页/第 X 页)、文章标签(分类/标签/作者)、页脚等。- Jekyll 侧给 chrome 元素加
data-i18n-ui="<key>",一段小 JS 按站点语言偏好替换。 - 需要先定英文措辞(内容决策),宜由 JSM 统一管理(和它管 home_blocks / 翻译 JSON 一致)。
home_blocks.yml的title是数据,届时也需要 en 对照(title_en或 ui-strings 映射)。
JSM 行动清单(按优先级)
P0(让首页数据全通)
- 提供社媒快讯真实 feed URL(契约 2)。
- 接管写
_data/home_blocks.yml(契约 1)。
P1(上线中英翻译)
- i18n 三件套:front matter + 稳定
data-i18nid + 翻译 JSON + 对齐 check(契约 3)。
P2(长期健壮)
- 统一子站 feed 形状(弃旧 picked 格式);
latest_posts可选加category。 - 版本
superseded标记,解 O(n²)(契约 5 backlog)。 - 若要站点级 EN 切换:ui-strings(契约 7)。
冻结(勿破坏):契约 4(taxonomy 文件形状)、契约 5(c_id/version_of/update_date 格式)、契约 6(url/test_url/ui_theme)。
</div>
</div> </main> `。
- A2
head收敛:删掉home.html整份复制的<head>,home 改layout: default。 - A3 post/news/page 只留内容区(去掉各自
<body>);tag/category/archive 经page继承,无需改。 - A4
<html lang="zh-CN">(在 default)。 - 验证:
jekyll build通过;home/post/news/about/56 tag/10 category 均单一 html/head/body/title。commitf381d83。
B. 配置驱动主页板块(核心)
- B1
_data/home_blocks.yml(v4 默认组合 +schema: home-blocks.v1)。commit67035b6。 - B2
home.html读home_blocks、按slot分区、经blocks/render.html按type分派。 - [~] B3 板块 include(
_includes/blocks/):featured-swiper.html—— 自有 置顶+featured,手动 swiper(honourblock.autoplay)。subsite-feed.html+assets/js/subsite-feed.js—— 参数化data-feed-url/-variant/-count;变体focus-list/cards/compact/ticker。post-list.html—— 自有 posts,分页,复用latest-versions.html。taxonomy-tags:首页底部热门 15(all-tags.html加limit/title+ 空条目守卫)+ 「全部标签 →」→ 新/tags/页(列全部已用标签)。不进右侧栏(右侧栏留给子站板块)。taxonomy-cats→ 导航分类 ▾下拉(header.html,site.category10 项)+ 撤底部分类带。桌面 hover/focus 展开、移动端点击展开(header.js)。commita6b8343+ 触屏切换。
- B4 通用化
top-news.js→subsite-feed.js:读data-feed-url(del 硬编码 de-ccp)。 - B5
subsite-feedJSON 契约:title/link/pubDate/cover_img/weight;cover_img缺失降级;空/失败三态;空[]feed 优雅降级。 - B6 已核对 JSM
latest_posts.py(2026-07-03):item ={title, link, pubDate, cover_img, weight}。有图(cover_img)、有日期(pubDate);无category/excerpt→ 卡片先不放分类。JSM 有手动 overrides 层(置顶/改标题图)。
C. 版本去重收敛 + 死代码
- C1 版本去重收敛到共享
_includes/latest-versions.html(include.collection参数):home + all-tags + all-categories + tagpage + categorypage 全部复用;4 份内联 O(n²) 拷贝删除、计数逐一比对不变(中共 10 / 政治 33 / 52 标签 均一致)。post/news 的”取本文最新版”是另一种单点查找(O(n)),保留。终局仍是 JSM 产出已解析列表(H9)。 - C2 删除死过滤器
wrap_tables/add_post_img_class(post/news/page);表格包装/图片加类仍由 post/news 的客户端 JS 负责。commit34b918e。 - C3 分页收敛:单一 page-size 来源;清除 index.html 里已弃用的 jekyll-paginate-v2 残留配置。
D. Correctness bug(Fable 已在产物中确认)
- D1
/about/、/contact/各有根目录版 +_pages版争同一 URL —— 各留一份。 - D2 每页两个
<title>—— 已去掉布局手写的,只留 `
(随 A 段完成,commit f381d83`)。
- [~] D3 域名归一:robots.txt Sitemap 改走
absolute_url(=site.url),已修(commit34b918e)。top-news.js 硬编码de-ccp.org留 B4 一并处理。 - D4 中文 SEO(首要):
lang: zh_CN(og:locale现为zh_CN)+ 中文description;单一<title>见 D2。commit34b918e。 - D5 footer
site.versionvs config 里的ver—— 键名对齐。 - D6 无效 HTML:footer 多余
</p>、</br>→<br>、post/news 未闭合<span class="post-link-date">全部修复并验证(版本文章 span 6 开 6 闭平衡)。 - D7 调试注释全清(all-tags/all-categories/tagpage/categorypage/post/news);
_metas/tags.md误粘的 404 HTML + 测试项已删,保留 tag_info 数据。 - D8
@font-faceformattruetype→woff2(commitd610298);swiper/top-news 已移到 body 末尾defer(随 A 段)。
E. EN i18n(方案 A 先行 / B 后备)
- E1 文章页:
page.translations存在时 default.html 加载article-i18n.css+article-i18n.js(defer);post/news 插article-lang-switcher.html(中/English/双语)。commit4a9d4db。 - E2
article-i18n.js:按需 fetchpage.translations.en(同源),英文作副本插到中文块后(不覆盖);html[data-article-lang=zh|en|both]+ CSS 控三态;?lang=+ localStorage + 标题切换 + aria-pressed。 - [~] E3 标题
data-i18n-title已挂;正文块data-i18n前端已支持(临时夹具用 Kramdown IAL 验证通过)。稳定 id 由 JSM 分配(非位置式)——JSM 侧待做。 - [~] E4
/en404 死链已去(nav + 汉堡菜单)。剩 slice 2:站点级 EN⇄中 切换按钮 + chrome 文案(_dataui-strings,需定英文措辞、宜与 JSM 协调)+ 驱动文章默认语言。 - E5 安全:纯文本 block 用
textContent;type:"html"blockinnerHTML(JSM 生成、同源、可信)。 - E6 JSM 侧(后做):翻译 JSON 面板、自动生成
<c_id>.en.json、注入/维护稳定 block id、check-script 校验正文data-i18n↔ JSONblocks对齐。 - E7 B = backlog:独立 EN 页(SEO)+
hreflang,用同一 JSON 生成。 - E8 中文 SEO 护栏:jekyll-seo-tag 已输出干净
rel=canonical(page.url,不含?lang)→?lang=en变体自动归一到中文 URL,无需额外处理。已核验产物。
F. 主题化 + 交给 Fable 的皮肤接口(可切换主题,不写死单一主题)
- F1 token 契约
_sass/_tokens.scss:per-theme mixin 应用到:root+[data-theme](全套 –font/–paper/–surface/–band/–ink/–accent/–line/–radius)。commitd610298。 - F2 主题层:
themes/_neutral.scss(默认)+themes/_ink-wash.scss,各一@mixin;:root{@include theme-neutral}+[data-theme="neutral"|"ink-wash"]。加主题 = 一文件 + 一行。 - F3 桥接→直连:
_styles.scss/_blocks.scss硬编码色已全量转var(--token),旧$var桥接层删除(Fable 2026-07-04)。 - F4
<html data-theme="neutral">(ui_theme非保留键theme);两主题空壳都建;configui_theme:可选(缺省 neutral)。运行时切换/深色模式机制预留。commitd610298。 - F5 class 名稳定(JS/swiper/i18n 钩子未改名);Fable 皮肤已交付:token 正式值 + 组件精修 + 宋体标题(Noto Serif/Sans SC webfont 接入)+ 朱红
.hero-badge(唯一 –accent-2 用点)。运行时切data-theme="ink-wash"验证过 token 全翻转。 - F6 neutral/ink-wash 两主题成套值已定稿(Fable 2026-07-04;两主题 –ink-3 均加深至 AA)。
G. 验证
- G1
bundle exec jekyll build通过。 - G2 对比重写前后生成的 URL 清单,permalink 不变(外链不断)。
- G3 冻结的 JSM 契约(UPGRADE_PLAN §5)未被破坏。
H. Fable 审查补充(2026-07-03 并入)
- H1 【必须】主题键用
ui_theme:,不用 Jekyll 保留键theme:(撞 gem 主题查找会炸构建)。 - H2 【必须】Sass 3.7.4(jekyll-sass-converter 1.5.2):token/主题层只用
@import,禁@use/@forward。 - [~] H3 feed 三态 +
loading=lazy+ cardsobject-fit:cover/3:2 + 按变体预留 min-height 防 CLS(commit)已做。剩余:确认跨域(de-ccp)feed 的 CORS 头。 - H4
home_blocks.yml:schema: home-blocks.v1;渲染器未知 type/variant 静默跳过;同 slot 顺序=数组顺序;文件头注明 JSM-owned。(块id、空 slot 撑满布局待 slice 2。) - H5 feed 安全:
escapeHtml+safeUrl协议校验(仅 http/https、相对路径)。commit67035b6。 - H6 分页页(/page/2+):
home_blocks每块加first_page_only,第 2 页起只留列表。 - H7 图片纪律:首页缩略图(post-list + feed cards)统一
aspect-ratio: 3/2+object-fit:cover,无图时留 –band 占位框(Fable 2026-07-04)。 - H8 可达性:文字对比 ≥4.5:1(两主题 –ink-3 实测加深:neutral #767066=4.7:1、ink-wash #6f6555=5.0:1);swiper
keyboard模块启用(Swiper 11 a11y/aria 默认开);a:focus-visible全局 outline(Fable 2026-07-04)。 - H9 版本去重终局(JSM backlog):publish 时给旧版本打
superseded: true→ Liquid 退化为 O(n)where_exp;C1 的 include 认superseded标记走快路径。 - H10 杂项:
ver/port等 config 清理;posts(无尾斜杠)/news(有尾斜杠) permalink 保持原样(勿”统一”以免断链)。