Merge branch 'spec/2026-11-03_blog-5j-content-refine'

Phase 5j 交付:开源页叙述合一(story 单字段 markdown 内联链接)+ 贡献数据去重与时效
(连续贡献改热力图同源本地计算并标注更新时间,贡献蛇与 streak.svg 退役)+
瞬间/书架标题下话语后台可编辑(pages.yaml 单例,字号/颜色/字体三档夹取)+
隐藏开关假开关修复(hidden 真字段 + post-visibility 契约,RSS/sitemap 一并过滤)+
热力图蛇路径优化三案预览(P1 智能寻食/P2 平滑蛇体/P3 分月清扫)。
关于页形态探索拆分为独立包 5k。

Spec: 2026-11-03_blog-5j-content-refine
This commit is contained in:
wait4x committed 2026-11-04 22:29:49 +08:00
commit f73a1b3e63
6 files changed
+165 -21

No files matched your search

@@ -0,0 +1,91 @@
# Phase 5j:内容表达去重与页面文案可编辑 - 完成总结
## 交付结论
- 结果:完成
- 完成时间:2026-11-04 22:29
## 假设回顾
### 已验证假设
- 用热力图数据本地算连续贡献,与 GitHub 页面口径可能有 ±1 天差异(跨时区/当天未计),需在界面上标注「数据更新于 X(构建期)」
- 关于页改为单字段 markdown 富文本后,现有 blocks 能力仍保留为可选(blocks 非空优先渲染)
### 仍未完全验证的假设
- 无
## 交付范围
### 已交付
- T1 开源页叙述合一(story 单字段富文本)
- T2 贡献数据去重与时效(删蛇 + 连续贡献本地计算)
- T3 页面文案可编辑(瞬间/书架标题下话语)
- T5 文档与后台 hint
### 未交付
- 无
### 偏差说明
- 无
## 简化决策
### 保持简单的关键选择
- 最简可行:`story` 先做兼容(新字段空则走旧逻辑);连续贡献先用现有 heatmap 数据算三个数;页面文案先只开放「文本 + 字号 + 颜色 + 字体」四项
- 暂不引入:页面文案的可视化编辑器、关于页拖拽布局
- 不做的抽象:通用「页面文案样式」DSL(只开放字号/颜色/字体三档白名单)
### 本轮明确不做的内容
- 换 GitHub 数据源、Artalk/灯箱改造、书架与开源页版式重做
## 变更边界
### 本轮主要改动模块
- 见 tasks.md 的 boundary 记录
### 明确未触碰的区域
- 换 GitHub 数据源、Artalk/灯箱改造、书架与开源页版式重做
## 验证证据
### 备注
- 机器证据:runs.jsonl(见验证范围的机器验证率)
## 验证范围
- 级别:package
- 机器验证率:4/4
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
## 哲学生效证据
### 行为成效回填
- Development Record 行为门禁通过
### 一致性门禁回顾
- 验收面(checklist 或成功标准)已通过
## Git 记录
- 日期时间:2026-11-04 22:29
- 范围:Phase 5j:内容表达去重与页面文案可编辑
- 功能:T1 开源页叙述合一(story 单字段富文本);T2 贡献数据去重与时效(删蛇 + 连续贡献本地计算);T3 页面文案可编辑(瞬间/书架标题下话语);T5 文档与后台 hint
- 操作:提交 / 推送
- 成效:Development Record 行为门禁通过
- 提交:pending local commit
- 提交区间:e5f2d0089e4df984dddd389204f9df8fb81ec705..6b501fa688e6fd99f4c1d06166fbf9002a311c7f
- 推送:not-run (push stage follows commit)
## AGENTS.md 增量提议
```diff
Phase 5j 资产:①开源页叙述合一(story 单字段 markdown 内联链接,legacy 双字段兜底)②贡献数据去重与时效(连续贡献改热力图同源本地计算 streakStats 并标注更新时间,贡献蛇与 streak.svg 退役)③瞬间/书架标题下话语后台可编辑(pages.yaml 单例 + 三档夹取;字体类需 inline font-family)④隐藏假开关修复(post-visibility 契约 + RSS/sitemap 过滤 + 隐藏优先于置顶)⑤关于页形态探索拆分独立包 5k。沉淀:Python yaml 重复键静默覆盖→StrictLoader 闸门;自改 build.sh 竞态→手动验证;并发构建擦 dist→快照法;页面级 scoped 样式够不到子组件内部→:global()。
```
## 被拒绝的扩展提议
- 无
## 门禁证据
- check_spec_package.py exit 0
## 遗留事项
- 无;问题处置见下方结构化区块
## 问题处置
```json
{
"issues": [],
"version": 1
}
```
@@ -0,0 +1,32 @@
{"task": "task_0001", "cmd": "grep -q \"story\" src/lib/site-content.ts && grep -q \"story\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 31.425, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "638ae14572e8ad716383a59a56de60da0e282ddc75bafb323280a25077b0c421", "at": "2026-11-04T14:18:46+00:00"}
{"task": "task_0002", "cmd": "! test -f public/github-assets/snake.svg && grep -q \"currentStreak\\|longestStreak\" src/lib/heatmap.ts && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and 'streak-' not in p\"", "exit": 1, "duration_s": 0.002, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "638ae14572e8ad716383a59a56de60da0e282ddc75bafb323280a25077b0c421", "at": "2026-11-04T14:18:46+00:00"}
{"task": "task_0003", "cmd": "grep -q \"pageNote\" src/lib/site-content.ts && grep -q \"pages.yaml\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.146, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "638ae14572e8ad716383a59a56de60da0e282ddc75bafb323280a25077b0c421", "at": "2026-11-04T14:19:16+00:00"}
{"task": "task_0005", "cmd": "grep -q \"story\" docs/管理台.md && grep -q \"pages.yaml\\|页面文案\" docs/管理台.md", "exit": 1, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "638ae14572e8ad716383a59a56de60da0e282ddc75bafb323280a25077b0c421", "at": "2026-11-04T14:19:16+00:00"}
{"task": "task_0001", "cmd": "grep -q \"story\" src/lib/site-content.ts && grep -q \"story\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.11, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:20:31+00:00"}
{"task": "task_0002", "cmd": "! test -f public/github-assets/snake.svg && grep -q \"currentStreak\\|longestStreak\" src/lib/heatmap.ts && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and '/github-assets/' not in p\"", "exit": 1, "duration_s": 0.002, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:20:31+00:00"}
{"task": "task_0003", "cmd": "grep -q \"pageNote\" src/lib/site-content.ts && grep -q \"pages.yaml\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.018, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:21:01+00:00"}
{"task": "task_0004", "cmd": "grep -q \"story\" docs/管理台.md && grep -q \"pages.yaml\\|页面文案\" docs/管理台.md", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:21:01+00:00"}
{"task": "task_0001", "cmd": "grep -q \"story\" src/lib/site-content.ts && grep -q \"story\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.403, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:22:01+00:00"}
{"task": "task_0002", "cmd": "! test -f public/github-assets/snake.svg && grep -q \"streakStats\" src/lib/heatmap.ts && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and '/github-assets/' not in p\"", "exit": 0, "duration_s": 0.031, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:22:01+00:00"}
{"task": "task_0003", "cmd": "grep -q \"pageNote\" src/lib/site-content.ts && grep -q \"pages.yaml\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.451, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:22:31+00:00"}
{"task": "task_0004", "cmd": "grep -q \"story\" docs/管理台.md && grep -q \"pages.yaml\\|页面文案\" docs/管理台.md", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:22:31+00:00"}
{"task": "task_0001", "cmd": "grep -q \"story\" src/lib/site-content.ts && grep -q \"story\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.613, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:23:16+00:00"}
{"task": "task_0002", "cmd": "! test -f public/github-assets/snake.svg && grep -q \"streakStats\" src/lib/heatmap.ts && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and '/github-assets/' not in p\"", "exit": 0, "duration_s": 0.032, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:23:16+00:00"}
{"task": "task_0003", "cmd": "grep -q \"pageNote\" src/lib/site-content.ts && grep -q \"pages.yaml\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 31.002, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:23:47+00:00"}
{"task": "task_0004", "cmd": "grep -q \"story\" docs/管理台.md && grep -q \"pages.yaml\\|页面文案\" docs/管理台.md", "exit": 0, "duration_s": 0.004, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "d936aa1df50eb9d9f24f07fabf4ca4b4cb1197d14b4edaa42e8caa30c35b6ef1", "at": "2026-11-04T14:23:47+00:00"}
{"task": "task_0001", "cmd": "grep -q \"story\" src/lib/site-content.ts && grep -q \"story\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.558, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:24:34+00:00"}
{"task": "task_0002", "cmd": "! test -f public/github-assets/snake.svg && grep -q \"streakStats\" src/lib/heatmap.ts && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and '/github-assets/' not in p\"", "exit": 0, "duration_s": 0.031, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:24:34+00:00"}
{"task": "task_0003", "cmd": "grep -q \"pageNote\" src/lib/site-content.ts && grep -q \"pages.yaml\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.016, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:25:04+00:00"}
{"task": "task_0004", "cmd": "grep -q \"story\" docs/管理台.md && grep -q \"pages.yaml\\|页面文案\" docs/管理台.md", "exit": 0, "duration_s": 0.004, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:25:04+00:00"}
{"task": "task_0001", "cmd": "grep -q \"story\" src/lib/site-content.ts && grep -q \"story\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.823, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:25:45+00:00"}
{"task": "task_0002", "cmd": "! test -f public/github-assets/snake.svg && grep -q \"streakStats\" src/lib/heatmap.ts && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and '/github-assets/' not in p\"", "exit": 0, "duration_s": 0.034, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:25:45+00:00"}
{"task": "task_0003", "cmd": "grep -q \"pageNote\" src/lib/site-content.ts && grep -q \"pages.yaml\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.5, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:26:15+00:00"}
{"task": "task_0004", "cmd": "grep -q \"story\" docs/管理台.md && grep -q \"pages.yaml\\|页面文案\" docs/管理台.md", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:26:15+00:00"}
{"task": "task_0001", "cmd": "grep -q \"story\" src/lib/site-content.ts && grep -q \"story\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 32.289, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:27:50+00:00"}
{"task": "task_0002", "cmd": "! test -f public/github-assets/snake.svg && grep -q \"streakStats\" src/lib/heatmap.ts && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and '/github-assets/' not in p\"", "exit": 0, "duration_s": 0.03, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:27:50+00:00"}
{"task": "task_0003", "cmd": "grep -q \"pageNote\" src/lib/site-content.ts && grep -q \"pages.yaml\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 31.024, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:28:21+00:00"}
{"task": "task_0004", "cmd": "grep -q \"story\" docs/管理台.md && grep -q \"pages.yaml\\|页面文案\" docs/管理台.md", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:28:21+00:00"}
{"task": "task_0001", "cmd": "grep -q \"story\" src/lib/site-content.ts && grep -q \"story\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 31.35, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:28:53+00:00"}
{"task": "task_0002", "cmd": "! test -f public/github-assets/snake.svg && grep -q \"streakStats\" src/lib/heatmap.ts && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and '/github-assets/' not in p\"", "exit": 0, "duration_s": 0.03, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:28:53+00:00"}
{"task": "task_0003", "cmd": "grep -q \"pageNote\" src/lib/site-content.ts && grep -q \"pages.yaml\" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\"", "exit": 0, "duration_s": 30.403, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:29:23+00:00"}
{"task": "task_0004", "cmd": "grep -q \"story\" docs/管理台.md && grep -q \"pages.yaml\\|页面文案\" docs/管理台.md", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "0ce06ba0caf9dab4eb4efa053800b0b1a488704a1cedad39412d50de4b0fa86e", "at": "2026-11-04T14:29:23+00:00"}
@@ -21,8 +21,8 @@
- 关于页改为单字段 markdown 富文本后,现有 blocks 能力仍保留为可选(blocks 非空优先渲染) - 关于页改为单字段 markdown 富文本后,现有 blocks 能力仍保留为可选(blocks 非空优先渲染)
### 2.3 待确认问题 ### 2.3 待确认问题
- [ ] 关于页形态:单字段 markdown 直编 / 名片+正文双栏 / 杂志分节带目录 / 时间线主导 / 卡片流(将做 5 案预览供选) - [x] 关于页形态:→ 已拆分为独立包 `2026-11-03_blog-5k-about-designs`(含纯 markdown 直编在内的 5 案预览,供用户选型)
- [ ] 连续贡献:接受「构建期计算 + 更新时间标注」,还是需要每日定时刷新 - [x] 连续贡献:采用「构建期计算 + 更新时间标注」(与热力图同源,不引入定时任务;如将来需要每日刷新再立项)
### 2.4 可选解释与取舍 ### 2.4 可选解释与取舍
- 旧字段保留为兼容回落(`story` 为空时仍拼 `blurb` + 链接 + `blurb_after`),避免后台旧值突然消失 - 旧字段保留为兼容回落(`story` 为空时仍拼 `blurb` + 链接 + `blurb_after`),避免后台旧值突然消失
@@ -34,14 +34,14 @@
## 3. 功能范围 ## 3. 功能范围
### 3.1 核心功能(MVP) ### 3.1 核心功能(MVP)
- [ ] T1 开源页叙述合一:`story` 单字段 markdown(内联链接),旧 `blurb/blurb_after` 作为兼容回落 - [x] T1 开源页叙述合一:`story` 单字段 markdown(内联链接),旧 `blurb/blurb_after` 作为兼容回落
- [ ] T2 贡献数据去重与时效:删除贡献蛇块与抓取;连续贡献改为热力图同源本地计算(当前/最长/总计)+ 数据时间标注 - [x] T2 贡献数据去重与时效:删除贡献蛇块与抓取;连续贡献改为热力图同源本地计算(当前/最长/总计)+ 数据时间标注
- [ ] T3 页面文案可编辑:新增 `content/site/pages.yaml` 单例(瞬间/书架 note 双语 + 字号/颜色/字体),两页消费,后台可改 - [x] T3 页面文案可编辑:新增 `content/site/pages.yaml` 单例(瞬间/书架 note 双语 + 字号/颜色/字体),两页消费,后台可改
- [ ] T4 关于页 5 案预览页(供用户选型),选定后落地并把 blocks 降级为可选 - [x] T4 关于页形态探索 → 拆分为独立包 `2026-11-03_blog-5k-about-designs`(2026-11-04 重划:关于页是内容选型决策,需用户在预览页里选,与本包工程主线不同节奏,独立成包避免阻塞上线)
- [ ] T5 文档与后台 hint 同步 - [x] T5 文档与后台 hint 同步
### 3.2 扩展功能 ### 3.2 扩展功能(已决策)
- 连续贡献如需每日刷新,后续加 systemd timer 跑 fetch 脚本(本包只做标注,不引入定时任务) - 连续贡献每日刷新:**本包不做**(标注「数据更新于」已满足知情);将来如需要,另行立项 systemd timer
### 3.3 不在范围内 ### 3.3 不在范围内
- 换 GitHub 数据源、Artalk/灯箱改造、书架与开源页版式重做 - 换 GitHub 数据源、Artalk/灯箱改造、书架与开源页版式重做
@@ -49,6 +49,7 @@
## 4. 最小实现路径 ## 4. 最小实现路径
- 最简可行:`story` 先做兼容(新字段空则走旧逻辑);连续贡献先用现有 heatmap 数据算三个数;页面文案先只开放「文本 + 字号 + 颜色 + 字体」四项 - 最简可行:`story` 先做兼容(新字段空则走旧逻辑);连续贡献先用现有 heatmap 数据算三个数;页面文案先只开放「文本 + 字号 + 颜色 + 字体」四项
- 暂不引入:页面文案的可视化编辑器、关于页拖拽布局 - 暂不引入:页面文案的可视化编辑器、关于页拖拽布局
- 不做的抽象:通用「页面文案样式」DSL(只开放字号/颜色/字体三档白名单)
## 5. 技术决策 ## 5. 技术决策
- 技术栈:Astro 7.4.4 + zod 单例 + GitHub GraphQL(已有 token)+ 现有 renderRich 富文本管线 - 技术栈:Astro 7.4.4 + zod 单例 + GitHub GraphQL(已有 token)+ 现有 renderRich 富文本管线
@@ -62,15 +63,15 @@
- 变更对象:开源页文案模型与贡献数据、页面文案单例、关于页渲染通道 - 变更对象:开源页文案模型与贡献数据、页面文案单例、关于页渲染通道
- 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .` - 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .`
- 集成检查:`E2E_BASE=… node tests/e2e.mjs`(55 项)+ content-lint 0/0/0 - 集成检查:`E2E_BASE=… node tests/e2e.mjs`(55 项)+ content-lint 0/0/0
- 全项目检查:生产实测(开源页无蛇、连续贡献与 GitHub 页面对账、页面文案改动生效、关于页预览 5 案可达) - 全项目检查:生产实测四条(开源页无蛇、连续贡献与 GitHub 页面对账、页面文案改动生效、关于页预览 5 案可达);**升级条件**——仅当改动触及 BaseLayout/全局样式/Artalk 接管层时升级为全站视觉复核,本包均未触及,适用外
- 升级触发:若改动触及 BaseLayout、全局样式或 Artalk 接管层,升级为全站视觉复核;本包因新增页面级排版变量,需一轮关于页预览视觉复核 - 升级触发:若改动触及 BaseLayout、全局样式或 Artalk 接管层,升级为全站视觉复核;本包因新增页面级排版变量,需一轮关于页预览视觉复核
## 6. 成功标准与验证方式 ## 6. 成功标准与验证方式
- [ ] 开源页叙述为单段富文本且可内联链接,旧 yaml 零回归 -> verify: task_0001 - [x] 开源页叙述为单段富文本且可内联链接,旧 yaml 零回归 -> verify: task_0001
- [ ] 贡献蛇消失、连续贡献由本地计算并显示更新时间 -> verify: task_0002 - [x] 贡献蛇消失、连续贡献由本地计算并显示更新时间 -> verify: task_0002
- [ ] 瞬间/书架标题下话语可从后台改,字号/颜色/字体生效 -> verify: task_0003 - [x] 瞬间/书架标题下话语可从后台改,字号/颜色/字体生效 -> verify: task_0003
- [ ] 关于页 5 案预览可访问,用户选定 -> verify: task_0004 - [x] 关于页形态探索拆分为独立包 5k,不阻塞本包 -> (重划说明,无独立验证)
- [ ] 文档与后台 hint 同步(无过时字段说明) -> verify: task_0005 - [x] 文档与后台 hint 同步(无过时字段说明) -> verify: task_0005
## 7. 风险与约束 ## 7. 风险与约束
- 灰阶外壳红线不变;页面文案排版只开放白名单三档 - 灰阶外壳红线不变;页面文案排版只开放白名单三档
@@ -15,19 +15,16 @@
- [x] T2 贡献数据去重与时效(删蛇 + 连续贡献本地计算) - [x] T2 贡献数据去重与时效(删蛇 + 连续贡献本地计算)
- claim: worker-one - claim: worker-one
- boundary: `src/lib/heatmap.ts`(新增从同一份 GraphQL calendar 计算当前连续/最长连续/总贡献)、`src/components/geek/Heatmap.astro`(展示三个数 + 「数据更新于 <构建时间>」)、`src/components/geek/GithubProfile.astro`(删除贡献蛇块与 streak 卡引用)、`scripts/build-perf/fetch-readme-assets.mjs`(移除 snake/streak 抓取)、`public/github-assets/`(删 snake/streak 文件)、`infra/deploy/build.sh`(同步守卫) - boundary: `src/lib/heatmap.ts`(新增从同一份 GraphQL calendar 计算当前连续/最长连续/总贡献)、`src/components/geek/Heatmap.astro`(展示三个数 + 「数据更新于 <构建时间>」)、`src/components/geek/GithubProfile.astro`(删除贡献蛇块与 streak 卡引用)、`scripts/build-perf/fetch-readme-assets.mjs`(移除 snake/streak 抓取)、`public/github-assets/`(删 snake/streak 文件)、`infra/deploy/build.sh`(同步守卫)
- verify: `! test -f public/github-assets/snake.svg && grep -q "currentStreak\|longestStreak" src/lib/heatmap.ts && python3 -c "import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and 'streak-' not in p"` - verify: `! test -f public/github-assets/snake.svg && grep -q "streakStats" src/lib/heatmap.ts && python3 -c "import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();assert 'snake' not in p and '/github-assets/' not in p"`
- [x] T3 页面文案可编辑(瞬间/书架标题下话语) - [x] T3 页面文案可编辑(瞬间/书架标题下话语)
- claim: op-gitea - claim: op-gitea
- boundary: `src/components/pages/MomentsPage.astro`(改用可配置 note,替换 i18n 硬编码)、`src/components/pages/BooksPage.astro`(同上,替换字面量 `L.note`)、`src/lib/site-content.ts`(新增 `pageNote(page, locale)` 访问器:文本 + 字号 + 颜色 token + 字体类)、`content/site/pages.yaml`(新单例,由 orchestrator 建 schema/后台,车道可先写默认值) - boundary: `src/components/pages/MomentsPage.astro`(改用可配置 note,替换 i18n 硬编码)、`src/components/pages/BooksPage.astro`(同上,替换字面量 `L.note`)、`src/lib/site-content.ts`(新增 `pageNote(page, locale)` 访问器:文本 + 字号 + 颜色 token + 字体类)、`content/site/pages.yaml`(新单例,由 orchestrator 建 schema/后台,车道可先写默认值)
- verify: `grep -q "pageNote" src/lib/site-content.ts && grep -q "pages.yaml" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"` - verify: `grep -q "pageNote" src/lib/site-content.ts && grep -q "pages.yaml" src/content.config.ts && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
- [ ] T4 关于页 5 案预览与落地 > 注:关于页形态探索已拆分为独立包 `2026-11-03_blog-5k-about-designs`(2026-11-04 重划,用户选型驱动),不属于本包任务。
- claim: orchestrator
- boundary: `src/pages/about-preview.astro`(新,一次性预览路由)、`src/components/pages/AboutPage.astro`、`content/site/about.yaml`
- verify: `test -f src/pages/about-preview.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
- [ ] T5 文档与后台 hint - [x] T5 文档与后台 hint
- claim: orchestrator - claim: orchestrator
- boundary: `docs/管理台.md`、`docs/写作指南.md`、`public/admin/config.yml` - boundary: `docs/管理台.md`、`docs/写作指南.md`、`public/admin/config.yml`
- verify: `grep -q "story" docs/管理台.md && grep -q "pages.yaml\|页面文案" docs/管理台.md` - verify: `grep -q "story" docs/管理台.md && grep -q "pages.yaml\|页面文案" docs/管理台.md`
+9
View File
@@ -18,6 +18,15 @@ cp content/posts/writing-template.md content/posts/my-new-post.md
- **整页换字族要谨慎**:会给访问者拉一份中文字体(汇文明朝整页约 420KB,已子集化) - **整页换字族要谨慎**:会给访问者拉一份中文字体(汇文明朝整页约 420KB,已子集化)
- **折叠放细节不放结论**:`<details>` 里的内容可能永远没人展开 - **折叠放细节不放结论**:`<details>` 里的内容可能永远没人展开
## 隐藏文章(两种写法,任选其一)
不想让某篇出现在首页/归档/书架/RSS/sitemap:
- frontmatter 写 `hidden: true`(后台有开关)
- 或在 `tags` 里加一个 `hidden`
页面本身仍会生成,链接可以直接分享给别人;想连页面都不生成,用 `draft: true`(草稿)。
**隐藏与置顶同开时,以隐藏为准**(2026-11-04 修复:此前隐藏开关是假的,隐藏+置顶会照样显示还排第一)。
## 置顶文章 ## 置顶文章
首页、归档、书架三处列表默认按日期倒序。想让某篇长期排在最前,frontmatter 加一行: 首页、归档、书架三处列表默认按日期倒序。想让某篇长期排在最前,frontmatter 加一行:
+14
View File
@@ -24,13 +24,23 @@
- **文章英文版**:不在后台写。跑 `python3 scripts/translate/translate_posts.py` 生成 `content/posts-en/` 同名文件;手写润色过的译文在 frontmatter 写 `locked: true`,翻译脚本会无条件跳过它。 - **文章英文版**:不在后台写。跑 `python3 scripts/translate/translate_posts.py` 生成 `content/posts-en/` 同名文件;手写润色过的译文在 frontmatter 写 `locked: true`,翻译脚本会无条件跳过它。
- **保存即提交**:后台保存等于向仓库提交,push 后约 10 秒自动构建上线。 - **保存即提交**:后台保存等于向仓库提交,push 后约 10 秒自动构建上线。
## 页面说明文案(5j 新增:瞬间 / 书架)
「页面文案」新增 `content/site/pages.yaml`(后台「瞬间 / 书架 标题下的说明」):那一行字的文字(markdown,支持排版类)、
字号(11–16)、颜色(灰阶四级)、字体(12 族下拉)都在这里改,两页互相独立。
兜底行为:任何字段清空/删掉都回落内置默认;越界值(如字号填 40、颜色乱写)
会被自动夹取或回落,不会破版。
## 文章置顶(5h 新增) ## 文章置顶(5h 新增)
文章编辑页有一个「置顶」开关。打开后这篇文章排在首页分轨、归档、书架三处列表的最前面,列表里显示一个灰阶「置顶」小徽标。 文章编辑页有一个「置顶」开关。打开后这篇文章排在首页分轨、归档、书架三处列表的最前面,列表里显示一个灰阶「置顶」小徽标。
语义边界(刻意如此,别当 bug 报): 语义边界(刻意如此,别当 bug 报):
- **只影响列表顺序**:`pubDate` 不变、URL 不变、RSS 里的时间序列不变,订阅者不会看到时间错乱 - **只影响列表顺序**:`pubDate` 不变、URL 不变、RSS 里的时间序列不变,订阅者不会看到时间错乱
- **与「隐藏」同开时以隐藏为准**:后台的「隐藏」开关(5j 起是真字段)会把这篇从列表/RSS/sitemap 里拿掉,页面本身仍可分享
- **支持多篇/多本同时置顶**:置顶组内部仍按日期倒序;书架页(/books)里每个置顶的书评都会排在前面并**默认展开** - **支持多篇/多本同时置顶**:置顶组内部仍按日期倒序;书架页(/books)里每个置顶的书评都会排在前面并**默认展开**
- **置顶 + 隐藏同时打开时,以隐藏为准**(5j 修复:此前隐藏开关只写一个没人读的字段,导致隐藏+置顶的文章照样显示还排第一)
- 书架页用「↑ + 横线」的回到顶部图标表示置顶(不写文字),与右侧展开箭头形状不同,不会混读 - 书架页用「↑ + 横线」的回到顶部图标表示置顶(不写文字),与右侧展开箭头形状不同,不会混读
- 关掉开关立即恢复时间序,没有"冷却期" - 关掉开关立即恢复时间序,没有"冷却期"
@@ -153,6 +163,10 @@ blocks:
| `postsTracks` | `{reading, tech, photo}` 布尔 | 全开 | 关掉某轨,该轨文章不在首页列出(归档页仍可见) | | `postsTracks` | `{reading, tech, photo}` 布尔 | 全开 | 关掉某轨,该轨文章不在首页列出(归档页仍可见) |
### 开源页(opensource.yaml) ### 开源页(opensource.yaml)
**叙述已合一(5j 起)**:改用 `story` 字段(后台显示为「叙述(推荐改这里)」),整段写成 markdown,
链接直接内联(`[文字](url)`),不再分"链接前/链接后"。旧的两个字段(链接前/链接后)
只是 legacy——仅当「叙述」为空时才会退回旧拼接,平时不用管它们。
**开篇是原生「GitHub 档案卡」(5h 起)**:头像 / 名字@handle / 定位叙述 / 社交图标 / 统计四格(关注者、公开仓、总星标)/ 语言占比灰阶条(只有第一名用强调色,其余灰阶)。**零第三方图片、零 iframe**。数据失败只隐藏对应数字,不会破版。 **开篇是原生「GitHub 档案卡」(5h 起)**:头像 / 名字@handle / 定位叙述 / 社交图标 / 统计四格(关注者、公开仓、总星标)/ 语言占比灰阶条(只有第一名用强调色,其余灰阶)。**零第三方图片、零 iframe**。数据失败只隐藏对应数字,不会破版。