diff --git a/docs/news-morning-templates.md b/docs/news-morning-templates.md
new file mode 100644
index 0000000..70c1e85
--- /dev/null
+++ b/docs/news-morning-templates.md
@@ -0,0 +1,118 @@
+# 每日新闻早报模板:news001 / news002
+
+模板定义集中在 [`news-morning-templates.mjs`](../news-morning-templates.mjs)。
+早报页由 Agent 按 spec 生成 HTML,仓库里没有静态页模板文件,所以「模板」= 交给 Agent 的版式与内容规范。
+
+| 模板 | 版本 | 状态 | 说明 |
+|------|------|------|------|
+| `news001` | `2026-09-18-refined` | frozen | 2026-09-18 定稿版式,已上生产。**只读,不允许就地修改**;要改版式请新开 `news003`。 |
+| `news002` | `2026-09-21-subscriber` | active | 订阅向改版:事件唯一化、每条配图、报头版式、深色模式。 |
+
+## 切换模板
+
+```bash
+# 环境变量(默认 news001)
+H5_WECHAT_NEWS_MORNING_TEMPLATE_ID=news002
+```
+
+也可以写进 DB 配置 `h5_wechat_admin_config.news_morning_draft` 的 `templateId` 字段,
+DB 值优先于环境变量。非法值一律回落到 `news001`。
+
+## news001 的三个实测问题
+
+以生产页 `daily-news-0918.html` 为样本(`node scripts/preview-news002-template.mjs` 旁的分析脚本可复现):
+
+1. **重复选题**。49 张新闻卡片里只有约 20 个独立事件。「王毅同鲁比奥通电话」出现 4 次,
+ 「东博会南宁开幕」5 次,「OpenAI 霍奇猜想」5 次。
+ 根因是版式把「23 个栏目 × 固定条数」当硬指标,当天独立事件不够时 Agent 就换个标题再写一遍。
+2. **来源不可核验**。49 张卡片只引用了 **7 个 URL**,且全是 `finance.sina.com.cn/stock/cpbd/`
+ 这类栏目首页,不是文章永久链接。读者点进去看不到对应报道。
+3. **零配图**。整页 0 个 ``,信息密度高但没有视觉落点,移动端阅读疲劳。
+
+## news002 的对应设计
+
+### 1. 事件唯一化(结构性修复)
+
+- 栏目分 `core` / `flex` 两档。`core` 必出;`flex` 只有拿到**未被使用过的独立事件**才输出,
+ 没料就整节删除,连同导航锚点一起去掉。**是事件决定栏目,不是栏目决定事件。**
+- 全页独立事件上限 `NEWS002_MAX_STORY_CARDS = 28`(news001 实际产出 49)。
+- 每张卡片必须带 `data-event-key="{事件指纹}"`,指纹 = 核心主体 + 核心动作,
+ 中英文报道共用同一指纹,全页不得重复。
+
+### 2. 运行时去重闸门
+
+prompt 层的「禁止重复」无法验证,所以在落盘后再做一次程序化清理:
+[`wechat-news-morning-dedup.mjs`](../wechat-news-morning-dedup.mjs),由 worker 在生成成功后调用。
+
+判重优先级:
+
+1. `data-event-key` 完全相同
+2. 来源 URL 归一化后相同 —— **仅当它是文章永久链接时**。
+ `isArticlePermalink()` 会排除栏目首页/`index.*`,否则 news001 那种「7 个栏目首页」会把整页误删。
+3. 标题相似 —— `isSameNewsEvent()` 用字符二元组重合度(阈值 0.55)。
+ 仓库既有的 `isDuplicateNewsMorningStory()` 按空格分词,对中文标题基本失效,所以这里另做一层。
+
+`history` / `knowledge` 不参与判重;`weather` / `brief` / `deepdive` 不会被删空。
+闸门只删卡片和空栏目,**不改写正文**,DOM 保持 div 配平。
+
+### 3. 每条新闻配图
+
+链路:SearXNG `img_src`/`thumbnail` → `normalizeSearchResult()` 透传 `image`/`publishedAt`
+→ 预取 brief 里以 `图:{URL}` 单独成行 → Agent 写进 `.card .media > img`。
+
+- 有图:`loading="lazy"` + `referrerpolicy="no-referrer"` + `onerror` 降级为占位
+- 无图:`