Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d579c2281 | ||
|
|
783d74966e | ||
|
|
5c0e6ed900 | ||
|
|
a555a3e450 | ||
|
|
d37fe581c0 | ||
|
|
9b11070dd4 | ||
|
|
a98b2e83da | ||
|
|
3ec7e48727 | ||
|
|
eb5e3f851e | ||
|
|
35cb6e764e | ||
|
|
3c15dd47a5 | ||
|
|
1942d5b7f0 | ||
|
|
cf331ccf3d | ||
|
|
eead28f3a0 | ||
|
|
44cd62e9a2 | ||
|
|
97a56f3c89 | ||
|
|
dac34edd9a | ||
|
|
1737bf027c | ||
|
|
d4ddc08fbd | ||
|
|
b99e6e744b | ||
|
|
a3d6998de5 | ||
|
|
ac1eab3cf1 | ||
|
|
0d40b00f5a | ||
|
|
326b9bbd79 | ||
|
|
59895aca2b | ||
|
|
c5889f960b | ||
|
|
a3ddbbefdd | ||
|
|
e65d44df29 | ||
|
|
609ce1eb2e | ||
|
|
f6da3b3c32 | ||
|
|
160af791b5 | ||
|
|
0ef0fcd394 | ||
|
|
b72ecccc80 | ||
|
|
9a3a196eea | ||
|
|
4153c6d33f | ||
|
|
cb44d0b2d2 | ||
|
|
b41b4ba8a5 | ||
|
|
129b23cd2b | ||
|
|
6e343c80dc | ||
|
|
f73a1b3e63 | ||
|
|
19b765e5b1 | ||
|
|
6b501fa688 | ||
|
|
f581f5bc4f | ||
|
|
a2b5755680 | ||
|
|
7401b512e6 | ||
|
|
e5f2d0089e | ||
|
|
3151d20c66 | ||
|
|
49acb4cbd1 | ||
|
|
b3c3bcf49e | ||
|
|
34a78dacb9 | ||
|
|
d6dfe39db3 | ||
|
|
d9721cdbd8 | ||
|
|
1a486a20d2 | ||
|
|
58baad5a7b | ||
|
|
6b2e4d8666 | ||
|
|
7e0167a8bb | ||
|
|
545c91a0ff | ||
|
|
7149541a92 | ||
|
|
a868bf94fd | ||
|
|
a46430b088 | ||
|
|
1ce9cd65ea | ||
|
|
6e4d6c2f88 | ||
|
|
6abca9f412 | ||
|
|
c9fb46f5a9 | ||
|
|
edc34a8a8c | ||
|
|
83e0b863fc | ||
|
|
9df86cb05b | ||
|
|
d096d45c58 | ||
|
|
073e124344 | ||
|
|
d6e08a29f9 | ||
|
|
18ea5b17b8 | ||
|
|
d119d6b555 | ||
|
|
31fb52cb06 | ||
|
|
6061c16396 | ||
|
|
cfe508ff14 | ||
|
|
b8c5e1c86e | ||
|
|
907f64472a | ||
|
|
6293fe25d5 | ||
|
|
8d82aab213 | ||
|
|
029633f00f | ||
|
|
0ff4ede91f | ||
|
|
23303bd39a | ||
|
|
31eb44caee | ||
|
|
5c015c6577 | ||
|
|
138d3d976b | ||
|
|
d30b5decbb | ||
|
|
2526b98323 | ||
|
|
36643f5cf0 | ||
|
|
52188d5b12 | ||
|
|
fef2de1d37 | ||
|
|
bdaf02f9ca | ||
|
|
c7d1e7b975 | ||
|
|
550f6d15b4 | ||
|
|
d4fe273812 | ||
|
|
0b0307b12f | ||
|
|
3e377adc7f | ||
|
|
63c9c8ad4a | ||
|
|
8e74bead62 | ||
|
|
5bf0c327bf | ||
|
|
6538311387 | ||
|
|
cdd9c61641 | ||
|
|
e1fc8bd64c | ||
|
|
cc12e016ab | ||
|
|
ed473e996a | ||
|
|
c772236d1a | ||
|
|
a89c443c25 | ||
|
|
23b79a2473 | ||
|
|
8274180f81 | ||
|
|
3e09a362cc | ||
|
|
8debd99e61 | ||
|
|
be87917443 | ||
|
|
aaa9cd9274 | ||
|
|
2f7cbcf7bd | ||
|
|
6096a9dafe | ||
|
|
59eda420b3 | ||
|
|
740ec470fa | ||
|
|
3822e150a8 | ||
|
|
26bbb66a72 | ||
|
|
2cab243891 | ||
|
|
c5cdcd5bf9 | ||
|
|
b0a3881725 | ||
|
|
859cac19a2 | ||
|
|
8d221ab36e | ||
|
|
c4bdf46614 | ||
|
|
280654f9e3 | ||
|
|
328ab68f18 | ||
|
|
227bc24797 | ||
|
|
7c1462bde0 | ||
|
|
5c814f0866 | ||
|
|
8f01d825b7 | ||
|
|
f8562510e6 | ||
|
|
952e68e6ea | ||
|
|
46d248877f | ||
|
|
f5232b7c03 | ||
|
|
264148f0a6 | ||
|
|
5be09b6f32 | ||
|
|
de42fed52e | ||
|
|
845c45aa11 | ||
|
|
0e06c25f67 | ||
|
|
3b951d3de6 | ||
|
|
9702c9faf4 | ||
|
|
2e33a7cd2e | ||
|
|
3050b0b959 | ||
|
|
a761cf8129 | ||
|
|
bbb52b9e24 | ||
|
|
8571acdc8b | ||
|
|
127f349fb2 | ||
|
|
d8997c3c72 | ||
|
|
44626f8edc | ||
|
|
54769bcfdf | ||
|
|
541710b50a | ||
|
|
2b8353837a | ||
|
|
3b65cc47ec | ||
|
|
ca7d71325c | ||
|
|
d41fcc058c | ||
|
|
50c7595838 | ||
|
|
bb29c8abaf | ||
|
|
19b2390cbf | ||
|
|
983527a79d | ||
|
|
23ea8e5a55 | ||
|
|
57f19d855b | ||
|
|
a2395be379 | ||
|
|
90b8d0fbc0 | ||
|
|
4e24fe510f | ||
|
|
2bfe2ef3db | ||
|
|
814b138f89 | ||
|
|
c307285a25 | ||
|
|
8ec1712135 | ||
|
|
22930cfffd | ||
|
|
5d18591967 | ||
|
|
c1e3d30954 | ||
|
|
975cf5e58b | ||
|
|
7c5455278b | ||
|
|
6b35356e91 | ||
|
|
a9da765cb1 | ||
|
|
3941a34f04 | ||
|
|
3a59edc79a | ||
|
|
73c31ffb2a | ||
|
|
215760c38a | ||
|
|
55cfc73cb7 | ||
|
|
d00ca7e447 | ||
|
|
363cc616f7 | ||
|
|
0b6524f53c | ||
|
|
71b8985c17 | ||
|
|
3e73fe957e | ||
|
|
810cf44d3d | ||
|
|
0c569b0b31 |
No files matched your search
@@ -0,0 +1,95 @@
|
|||||||
|
# Phase 5f:SEO 解封与功能优化批 + 美化首批 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-11-01 09:51
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- nginx sudo 与 ESA 控制台由用户操作(本包只备好仓库侧与操作单)
|
||||||
|
- 视觉裁定为终态依据;实现与裁定冲突时以裁定为准并在完成总结记录
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- T1 SEO 仓库侧三件(robots.txt + admin noindex 预案 + 用户操作单)
|
||||||
|
- T2 localePath 双 en 修复 + 自指 canonical + 尾斜杠归一
|
||||||
|
- T3 构建期内容 lint + 生产冒烟 + build.sh 守卫挂载
|
||||||
|
- T4 reader 纯文本模式(html.reader + 命令面板 + r 键 + 持久化)
|
||||||
|
- T5 代码块窗条(P 卡:≥4 行灰阶窗+mono 标题+灰阶三点)
|
||||||
|
- T6 书评纸面板(Q 卡:圆角米灰纸卡+暗色暖棕+hairline 边框)
|
||||||
|
- T7 版式基底(K/L/O 卡:流距 token+灰阶补档+暗色 hairline 降档+微交互曲线+focus ring)
|
||||||
|
- T8 首屏 tagline + 图库质感(M/N 卡:身份语+圆角统一+暗色衬底)
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简单可行方案:token 值级 CSS 改动为主 + locale.ts 单函数修 + 两个构建脚本;无新依赖
|
||||||
|
- 暂不引入:blur-up 占位(需管线)、独立设置面板
|
||||||
|
- 不做的抽象:主题引擎化/样式体系重构(tokens 增量演化)
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- nginx/ESA 实际操作(用户侧);draft 路由 D、评论数 F、EXIF 卡 G、title H(本月批后续包)
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- nginx/ESA 实际操作(用户侧);draft 路由 D、评论数 F、EXIF 卡 G、title H(本月批后续包)
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:8/8
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-11-01 09:51
|
||||||
|
- 范围:Phase 5f:SEO 解封与功能优化批 + 美化首批
|
||||||
|
- 功能:T1 SEO 仓库侧三件(robots.txt + admin noindex 预案 + 用户操作单);T2 localePath 双 en 修复 + 自指 canonical + 尾斜杠归一;T3 构建期内容 lint + 生产冒烟 + build.sh 守卫挂载;T4 reader 纯文本模式(html.reader + 命令面板 + r 键 + 持久化);T5 代码块窗条(P 卡:≥4 行灰阶窗+mono 标题+灰阶三点);T6 书评纸面板(Q 卡:圆角米灰纸卡+暗色暖棕+hairline 边框);T7 版式基底(K/L/O 卡:流距 token+灰阶补档+暗色 hairline 降档+微交互曲线+focus ring);T8 首屏 tagline + 图库质感(M/N 卡:身份语+圆角统一+暗色衬底)
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:975cf5e58bf2846fc0c1e34a600725bcfe915f0e..23ea8e5a555a984cf0e8be46efecb718d26d3cdc
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
Phase 5f 教训与资产:①收官门禁代码化(spec_gate.py 首秀连环拦下五宗——E2E 口径错配/竞态雷/verify 管道笔误/5.1 双字段措辞/字段改名丢失,每次给出可执行下一步);②E2E_BASE 覆写为 spec 验收标准姿势;③视觉双轨闭环(token 方案→flash:max 像素验收);④? 浮层焦点竞态根治;⑤车道 verify 落点偏差随回执报告;⑥add -A 卷入在途车道第四次变体秒拆;⑦verify 断言禁用 -q 接管道;⑧spec 模板字段名不可创意改写(升级触发≠升级条件)。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:23+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 1, "duration_s": 0.669, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:23+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.06, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:23+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 1, "duration_s": 0.637, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:24+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 1, "duration_s": 0.675, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:25+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/ | grep -v node_modules", "exit": 1, "duration_s": 0.638, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:25+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 1, "duration_s": 0.656, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:26+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 1, "duration_s": 0.66, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:27+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:31:48+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 15.103, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:03+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.073, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:04+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 1, "duration_s": 14.313, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:18+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.413, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:32+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/ | grep -v node_modules", "exit": 1, "duration_s": 14.48, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:47+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 1, "duration_s": 14.192, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:33:01+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.446, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:33:15+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:34:48+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 14.521, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:02+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.056, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:02+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 0, "duration_s": 14.398, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:17+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.495, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:31+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/ | grep -v node_modules", "exit": 1, "duration_s": 14.653, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:46+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 0, "duration_s": 14.506, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:36:00+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.668, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:36:15+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:05+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 14.603, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:19+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.071, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:19+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 0, "duration_s": 14.487, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:34+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.324, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:48+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/", "exit": 0, "duration_s": 14.598, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:41:03+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 0, "duration_s": 15.156, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:41:18+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.487, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:41:32+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:01+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 14.593, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:16+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.058, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:16+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 0, "duration_s": 14.429, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:31+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.396, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:45+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/", "exit": 0, "duration_s": 14.486, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:59+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 0, "duration_s": 14.479, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:43:14+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.333, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:43:28+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:49:52+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 14.485, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:06+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.062, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:06+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 0, "duration_s": 14.474, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:21+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.495, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:35+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/", "exit": 0, "duration_s": 14.482, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:50+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 0, "duration_s": 14.59, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:51:04+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.452, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:51:19+00:00"}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Phase 5f:SEO 解封与功能优化批 + 美化首批 - 项目范围
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:①解除生产全站 noindex 封印并补齐 SEO 基建(事故级:上线至今搜索引擎不可见)②修双语暗伤(localePath 双 en/canonical/尾斜杠)③构建期内容 lint+生产冒烟防线 ④reader 纯文本模式 ⑤美化首批 K-Q(流距 token 化/灰阶补档/首屏 tagline/图库质感/微交互/代码块窗条/书评纸面板,全部经视觉模型裁定定案)
|
||||||
|
- **目标用户**:站长(写作/发布/被搜索到)与读者(阅读质感/双语导航正确性)
|
||||||
|
- **核心价值**:站点从"隐身可用"到"可被发现+有质感";发布链自带质检
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- noindex 来源=/etc/nginx/snippets/esa-origin-ssl.conf:11(我已 curl+grep 实锤);robots.txt 生产 404;ESA 头缓存 30 天
|
||||||
|
- localePath 在 en→en 场景产出 /en/en/...(404)
|
||||||
|
- Artalk 评论数端点已实测(GET /api/v2/comments?site_name=wait4x.online&limit=0 → count)
|
||||||
|
- 美化七问已由 glm-5.3-flash:max 逐图裁定(docs/research/08 §6)
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- nginx sudo 与 ESA 控制台由用户操作(本包只备好仓库侧与操作单)
|
||||||
|
- 视觉裁定为终态依据;实现与裁定冲突时以裁定为准并在完成总结记录
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 美化两案之争 → 视觉模型裁定完成(08 文档 §6),无悬案
|
||||||
|
- [x] 本批范围 → 用户 2026-10-26 全批(A/B/C/E+K-Q;R 热力图暗色与 S 并入 N 后的下一批)
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- 纸面板=全边框圆角卡(裁定)优于通栏 hairline——保留标题区外置结构
|
||||||
|
- 代码块窗条仅 ≥4 行块;三点换灰阶(红线优先于 macOS 梗)
|
||||||
|
- 图库亮色直贴/暗色 4% 衬底(双模式分判)
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 首屏 banner 化/大字标语(视觉裁定否决——克制红线)
|
||||||
|
- 热力图亮色换绿(裁定否决——全页最大色块压灰阶骨架)
|
||||||
|
- 页面标题 27→22px(裁定否决——层级锚点,要轻减字重不减号)
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 SEO 仓库侧三件:robots.txt 新建 + /admin 局部 noindex 预案 + 用户操作单(nginx/ESA)
|
||||||
|
- [x] T2 localePath 双 en 修复 + 自指 canonical + 尾斜杠归一
|
||||||
|
- [x] T3 构建期内容 lint(断链/图片/frontmatter/双语配对)+ 生产冒烟脚本 + build.sh 守卫挂载
|
||||||
|
- [x] T4 reader 纯文本模式(html.reader 单 class + 命令面板 reader + r 键 + localStorage)
|
||||||
|
- [x] T5 代码块窗条(P 卡:≥4 行灰阶窗+mono 标题+灰阶三点,单行裸 pill)
|
||||||
|
- [x] T6 书评纸面板(Q 卡:圆角米灰纸卡+暗色暖棕+hairline 边框防长文竖线感)
|
||||||
|
- [x] T7 版式基底(K/L/O 卡:正文流距 token 化+灰阶补档+暗色 hairline 降档+微交互曲线统一+focus ring 收尾)
|
||||||
|
- [x] T8 首屏 tagline + 图库质感(M/N 卡:IdCard 身份语≤20 字+图库圆角统一+暗色 4% 衬底)
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 无(R 热力图暗色补救列下一批)
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- nginx/ESA 实际操作(用户侧);draft 路由 D、评论数 F、EXIF 卡 G、title H(本月批后续包)
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简单可行方案:token 值级 CSS 改动为主 + locale.ts 单函数修 + 两个构建脚本;无新依赖
|
||||||
|
- 暂不引入:blur-up 占位(需管线)、独立设置面板
|
||||||
|
- 不做的抽象:主题引擎化/样式体系重构(tokens 增量演化)
|
||||||
|
|
||||||
|
## 5. 技术决策
|
||||||
|
- 技术栈:既有 Astro+tokens 体系;lint 用 node 脚本(复用 build-perf 目录与守卫模式)
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- 本轮允许改动:src/lib/locale.ts、src/layouts/BaseLayout.astro、src/styles/{tokens,global}.css、src/components/{CommandPalette,IdCard*}.astro、src/components/geek/Keyboard.astro、GalleryPage.astro、prose 相关组件、public/robots.txt、scripts/build-perf/、infra/deploy/build.sh、tests/e2e.mjs
|
||||||
|
- 本轮不应触碰:nginx/ESA(用户)、Header/Footer 结构、sw.js(无涉)、热力图组件
|
||||||
|
- Git integration branch:`spec/2026-10-26_blog-5f`
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:SEO 基建文件、locale 工具、构建链 lint 挂载、reader/美化涉及的样式与组件
|
||||||
|
- 快速检查:`pnpm astro build`
|
||||||
|
- 集成检查:`node tests/e2e.mjs`(33 项)+ 本包新增断言(canonical/localePath/reader/窗条/纸面板 computed-style)
|
||||||
|
- 全项目检查:生产 Lighthouse SEO≥95 复测(依赖用户 nginx/ESA 操作单完成)+ 移动溢出密集扫描;仅在 nginx/ESA 面、构建链或 Header 布局再变更时升级范围
|
||||||
|
- 升级触发:T7 灰阶补档若波及 Header/导航布局,或用户操作单完成后生产复测不达标,升级为全站密集扫描与 LH 多轮中位并记录理由
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] dist 每页含自指 canonical 且 en 页无 /en/en/ -> verify: task_0002
|
||||||
|
- [x] robots.txt 产物存在且含 Sitemap 行 -> verify: task_0001
|
||||||
|
- [x] 人为断链能让 build 输出 FAIL 清单(warn 一周策略首版即 fail 可议) -> verify: task_0003
|
||||||
|
- [x] reader 触发后 .bg-glow 隐藏/ICP 可见/刷新保持 -> verify: task_0004
|
||||||
|
- [x] 多行代码块有窗条与灰阶三点、单行无 -> verify: task_0005
|
||||||
|
- [x] 书评正文为圆角纸卡(computed border-radius+背景) -> verify: task_0006
|
||||||
|
- [x] 流距 token 生效(h2 前后距为变量值)+focus ring 统一 -> verify: task_0007
|
||||||
|
- [x] 首屏 tagline 渲染≤20 字+图库圆角统一 -> verify: task_0008
|
||||||
|
|
||||||
|
## 7. 风险与约束
|
||||||
|
- 灰阶红线:所有美化以 08 §6 裁定为终态依据
|
||||||
|
- build.sh 引用新脚本守卫先行(车道纪律);lint 首版 fail 可降 warn 一周(发布链安全优先)
|
||||||
|
- 多车道并行:T2/T3(op-gitea)、T5/T6(op-artalk)、T1/T4/T7/T8(orchestrator);共享文件按 boundary 划界,global.css/tokens 仅 orchestrator 动
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Phase 5f:SEO 解封与功能优化批 + 美化首批 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] T1 SEO 仓库侧三件(robots.txt + admin noindex 预案 + 用户操作单)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: 新建 public/robots.txt(Allow 全站;Disallow /admin/ /draft/;Sitemap 行);docs/上线检查单.md 追加「SEO 解封操作单」节(nginx snippet 行号/内部 vhost 补回清单/ESA purge+TTL+绕缓存三动作);nginx/ESA 实操归用户
|
||||||
|
- verify: `test -f public/robots.txt && grep -q "Sitemap: https://wait4x.online/sitemap-index.xml" public/robots.txt && grep -q "Disallow: /admin" public/robots.txt && grep -q "SEO 解封操作单" docs/上线检查单.md`
|
||||||
|
|
||||||
|
- [x] T2 localePath 双 en 修复 + 自指 canonical + 尾斜杠归一
|
||||||
|
- claim: op-gitea(mesh 车道)
|
||||||
|
- boundary: src/lib/locale.ts(先归一 zh 再拼前缀);BaseLayout.astro 加自指 canonical(Astro.site+withSlash);Header.astro nav-lang href 尾斜杠补齐;E2E 可加断言
|
||||||
|
- verify: `! grep -r "/en/en/" dist/ && grep -q 'rel="canonical"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS"`
|
||||||
|
|
||||||
|
- [x] T3 构建期内容 lint + 生产冒烟 + build.sh 守卫挂载
|
||||||
|
- claim: op-gitea(mesh 车道)
|
||||||
|
- boundary: 新建 scripts/build-perf/content-lint.mjs(dist 断链:站内 a[href] 逐一对产物;图片存在性;zh/en 配对警告)+ scripts/build-perf/prod-smoke.sh(curl robots/sitemap/首页头三断言);build.sh 守卫行(存在性守卫铁律);首版 fail 但提供 SKIP 环境变量逃生门
|
||||||
|
- verify: `node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh`
|
||||||
|
|
||||||
|
- [x] T4 reader 纯文本模式(html.reader + 命令面板 + r 键 + 持久化)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: global.css 尾部集中 html.reader 段(按 08 文档 §4.4 清单:隐 bg-glow/进度条/收 Header 尾部钮/Footer 收单行 ICP 必留;正文字号微升行宽收窄);CommandPalette data-cmd="reader" + ui.ts cmd_reader 双语键;Keyboard r 单键;localStorage 持久化;reduced-motion/无 JS 天然兼容(纯 class)
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -q 'data-cmd="reader"' src/components/CommandPalette.astro && grep -q "html.reader" src/styles/global.css`
|
||||||
|
|
||||||
|
- [x] T5 代码块窗条(P 卡:≥4 行灰阶窗+mono 标题+灰阶三点)
|
||||||
|
- claim: op-artalk(mesh 车道)
|
||||||
|
- boundary: 技术轨代码块渲染层(自查源码定落点:prose-tech 容器/全局 pre 样式);≥4 行块加窗条(灰阶三点非红黄绿+mono 语言/文件标签),单行块裸 pill;亮暗双态;scoped 样式优先不动 global.css(与 T7 划界)
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -rqE "code-win|codebar|窗条" src/pages/archives/`
|
||||||
|
|
||||||
|
- [x] T6 书评纸面板(Q 卡:圆角米灰纸卡+暗色暖棕+hairline 边框)
|
||||||
|
- claim: op-artalk(mesh 车道)
|
||||||
|
- boundary: 书评正文容器(prose-reading 所在组件 scoped 样式):圆角纸卡+米灰 #f4f4f2 系亮/暖棕暗态;边框 hairline 级防长文竖线感(08 裁定 §6-2);标题区外置结构不动;--paper 变量需求提给 orchestrator 入 tokens(T7 协同),不直接改 tokens.css
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -rq "paper" src/components/`
|
||||||
|
|
||||||
|
- [x] T7 版式基底(K/L/O 卡:流距 token+灰阶补档+暗色 hairline 降档+微交互曲线+focus ring)
|
||||||
|
- claim: orchestrator(tokens/global 专属车道)
|
||||||
|
- boundary: tokens.css(--flow-* 流距五变量+灰阶补档评估+--paper 纸色变量);global.css(h2/h3/p+p 改用流距变量、暗色 hairline 降一档、过渡曲线统一 token :where 一段、focus-visible ring 统一);标题字重 700→600(裁定 #6);行距 +2~4px(裁定 #7);移动端密集扫描复测(涉布局面)
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -q "flow-h2-pre" src/styles/tokens.css && grep -q "focus-visible" src/styles/global.css`
|
||||||
|
|
||||||
|
- [x] T8 首屏 tagline + 图库质感(M/N 卡:身份语+圆角统一+暗色衬底)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: 首页身份区(IdCard 或等位组件)加 tagline ≤20 字(措辞备两案入完成总结供用户选,默认「写作者 · 读者 · 快门按压者」);GalleryPage 圆角统一 --radius-img 暗色 4% 衬底(裁定 §6-4 双模式分判;衬底圆角≤照片圆角防框中框);亮色直贴不动
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -rq "tagline" src/pages/index.astro src/components/ && grep -q "radius-img" src/styles/tokens.css`
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# Phase 5g:页面后台化(blocks 模型)与字体子集化 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-11-02 19:19
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- 块渲染器受控,破版风险低于整页 HTML 直编(方案 10 §2 对比表)
|
||||||
|
- 字体子集化(pyftsubset 按页实际用字)能把艺术字体单页增量压到 ≤150KB 阈值内
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- T1 blocks 骨架(关于页块式页面构建器)
|
||||||
|
- T2 块型扩充与图片管线
|
||||||
|
- T3 样式即数据(theme.yaml + 对比度闸门)
|
||||||
|
- T4 首页细化与去重
|
||||||
|
- T5 开源页收尾(缓存/回落/告警)
|
||||||
|
- T6 文档与后台 hint 补齐
|
||||||
|
- T7 字体子集化工具
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简单可行方案:块类型先用 3 类跑通闭环(prose/tracks/divider),再扩到 9 类;theme 只做 12 个白名单旋钮
|
||||||
|
- 暂不引入:可视化块预览、块级动画配置
|
||||||
|
- 不做的抽象:通用页面 DSL(只服务现有页面)
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- 换 CMS、页面布局的任意拖拽网格、运行时编辑器
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- 换 CMS、页面布局的任意拖拽网格、运行时编辑器
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:7/7
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-11-02 19:19
|
||||||
|
- 范围:Phase 5g:页面后台化(blocks 模型)与字体子集化
|
||||||
|
- 功能:T1 blocks 骨架(关于页块式页面构建器);T2 块型扩充与图片管线;T3 样式即数据(theme.yaml + 对比度闸门);T4 首页细化与去重;T5 开源页收尾(缓存/回落/告警);T6 文档与后台 hint 补齐;T7 字体子集化工具
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:3822e150a87ac5fee56c9b2724eae23389e7c34e..23b79a2473674e75608736e2cac491b163a49c35
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
Phase 5g 资产:①块式页面构建器(九类 discriminatedUnion + 宽松分支容错:已知块严格校验、未知块 WARN 丢弃不炸构建);②样式即数据(theme.yaml 九组白名单旋钮 + :root:root 特异性注入 + 产物级对比度闸门,负例 1.61:1 被拦);③首页抽 HomePage 消 zh/en 双份;④字体子集化工具(汇文 694→420KB −40%;快乐体实测反向 −9% 故不启用,结论入档避免后人重试);⑤config.yml 重复键结构缺陷的教训——Python yaml 静默覆盖,必须用 StrictLoader/Node yaml 校验,已固化为 build 闸门 config-check.py;⑥并发构建擦 dist 的解法:build→cp -a dist 快照→快照起服务跑 E2E。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && grep -q \"discriminatedUnion\" src/content.config.ts && grep -q \"BlockRenderer\" src/components/pages/AboutPage.astro && grep -q \"typeKey\" public/admin/config.yml", "exit": 0, "duration_s": 18.466, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:18:21+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && ls src/components/blocks/ | grep -qC1 \"Cards\\|Timeline\" && grep -q \"image()\" src/content.config.ts", "exit": 0, "duration_s": 17.177, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:18:39+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && test -f content/site/theme.yaml && node scripts/build-perf/contrast-check.mjs dist", "exit": 0, "duration_s": 17.193, "stdout_tail_sha256": "f66c66c7ce879dad3f789790f8607fbd50e7a5a894e12912a55fd47d082e4f77", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:18:56+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && test -f src/components/pages/HomePage.astro && grep -q \"HomePage\" src/pages/en/index.astro", "exit": 0, "duration_s": 17.189, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:19:13+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\" && python3 -c \"import json;d=json.load(open('src/data/projects-fallback.json'));assert len(d)>=5\"", "exit": 0, "duration_s": 17.214, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:19:30+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "test -f docs/管理台.md && grep -q \"blocks\\|区块\" docs/管理台.md && grep -c \"hint\" public/admin/config.yml | awk '$1>=20'", "exit": 0, "duration_s": 0.004, "stdout_tail_sha256": "913f5d1da2feaf4deeccc9e55cbb350a20f12b3f507e87be85dbb77fdd3cb9bc", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:19:30+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "test -f scripts/build-perf/subset-fonts.py && python3 -c \"import fontTools; print('fonttools ok')\" && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"37/37 PASS\"", "exit": 0, "duration_s": 17.19, "stdout_tail_sha256": "3bcefe2ef2e30a5bbe067d31368a9765d7843d0cc24738a60c946c020e581348", "tree_fp": "7a7e460dbaff3855c2da899e62a1489b19a1d25a4a91ecfb211fadec40bff343", "at": "2026-11-02T11:19:47+00:00"}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# Phase 5g:页面后台化(blocks 模型)与字体子集化 - 项目范围
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:①按 docs/research/10 方案 E 把关于页改造成「块式页面构建器」(结构可增删排序)②样式即数据(theme.yaml → 构建期 CSS 变量,含对比度闸门)③首页字段细化并抽 HomePage 消 zh/en 双份 ④开源页收尾(README 缓存/回落/告警)⑤后台文档与 hint 补齐 ⑥字体子集化工具(把艺术字体单页增量从数百 KB 降到数十 KB)
|
||||||
|
- **目标用户**:站长(希望整页可编辑、能上更多艺术字体)与读者(版式与可读性不退化)
|
||||||
|
- **核心价值**:内容与结构的编辑权交回后台,同时守住灰阶体系/双语/SEO/构建期零运行时四条红线
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- Sveltia 0.252 支持 list 的 types + typeKey(方案 10 §3.4 从本仓 bundle 取证),块模型可行
|
||||||
|
- 页面文案 markdown 净化(D1/D2)已在 2cab243 落地(P0 完成)
|
||||||
|
- 字体实测:@fontsource CJK 切片粒度导致单页 +163KB~+8605KB;现有 6 自托管族为基线
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- 块渲染器受控,破版风险低于整页 HTML 直编(方案 10 §2 对比表)
|
||||||
|
- 字体子集化(pyftsubset 按页实际用字)能把艺术字体单页增量压到 ≤150KB 阈值内
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 关于页整页可编辑路线 → 用户 2026-11-02 批准方案 E(blocks + theme.yaml + 局部自由区)
|
||||||
|
- [x] 字体扩充路线 → 先落 6 族,其余按子集化工具就绪后再评估(本包 T7 交付工具)
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- 关于页保留 intro markdown 通道作为 compat(blocks 为空时三级回落:blocks → intro → 默认文案)
|
||||||
|
- 样式即数据只开放白名单旋钮(约 12 项),不开放整份 tokens.css
|
||||||
|
- 图片只走 Astro image() 管线,禁止块内填外链/绝对路径(schema 层拦)
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 整页富文本直编(C 方案):XSS 面大、无设计兜底,弃
|
||||||
|
- 新增 webfont 直接上(不子集化):单页 +535KB~+8605KB,超阈值,弃(改走 T7 工具)
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 blocks 骨架:schema(discriminatedUnion)+ page-blocks.ts + BlockRenderer + prose/tracks/divider 三块 + 后台 types 声明
|
||||||
|
- [x] T2 块型扩充:cards/timeline/quote/figure/linklist/cta + 图标键白名单 + 图片 image() 管线
|
||||||
|
- [x] T3 样式即数据:theme.yaml + theme.ts + BaseLayout 内联变量 + 对比度闸门脚本
|
||||||
|
- [x] T4 首页细化与去重:抽 HomePage.astro 消 zh/en 双份 + 字段细化(projectsLimit/announcement 等)
|
||||||
|
- [x] T5 开源页收尾:README 缓存策略/超时/回落矩阵 + fallback JSON 回填 + include 未命中 WARN
|
||||||
|
- [x] T6 文档与 hint:docs/管理台.md(块编辑用法)+ 写作指南扩充 + admin 全字段中文 hint
|
||||||
|
- [x] T7 字体子集化工具:按页用字裁剪艺术字体为小切片 + build.sh 守卫挂载 + 实测对比
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- 换 CMS、页面布局的任意拖拽网格、运行时编辑器
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简单可行方案:块类型先用 3 类跑通闭环(prose/tracks/divider),再扩到 9 类;theme 只做 12 个白名单旋钮
|
||||||
|
- 暂不引入:可视化块预览、块级动画配置
|
||||||
|
- 不做的抽象:通用页面 DSL(只服务现有页面)
|
||||||
|
|
||||||
|
## 5. 技术决策
|
||||||
|
- 技术栈:Astro 7.4.4 + zod discriminatedUnion + Sveltia list/types + pyftsubset(fonttools)
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- 本轮允许改动:src/content.config.ts、src/lib/{page-blocks,theme,site-content}.ts、src/components/blocks/*、src/components/pages/{AboutPage,HomePage}.astro、src/pages/{index,en/index,opensource,en/opensource}.astro、src/layouts/BaseLayout.astro、public/admin/*、content/site/*.yaml、scripts/build-perf/*、infra/deploy/build.sh、docs/*、tests/e2e.mjs
|
||||||
|
- 本轮不应触碰:nginx/ESA、图库灯箱(GalleryPage/PhotoZoom)、Header/Footer 结构
|
||||||
|
- Git integration branch:`spec/2026-11-02_blog-5g-pages`
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:页面数据模型与渲染器、后台配置、样式变量生成、字体产物、文档
|
||||||
|
- 快速检查:`pnpm astro build`(node26)
|
||||||
|
- 集成检查:`node tests/e2e.mjs`(37 项,本包预期新增块/主题断言)+ content-lint 0/0/0
|
||||||
|
- 全项目检查:字体体积账(三页增量)+ 视觉复核(glm-5.3-flash:max,块版式与主题变量双态);仅在构建链或 BaseLayout 结构再变更时升级范围
|
||||||
|
- 升级触发:若 theme 变量波及全局排版(字体字号全站变化)或 blocks 改动触及首页/文章页结构,升级为全站视觉复核与 E2E 全量并记录理由
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] 关于页整页由 yaml blocks 驱动,增删排序生效 -> verify: task_0001
|
||||||
|
- [x] 九类块全部可渲染且旧内容零回归(三级回落) -> verify: task_0002
|
||||||
|
- [x] theme.yaml 改值 → 产物 CSS 变量变化,对比度闸门拦截不达标 -> verify: task_0003
|
||||||
|
- [x] 首页 zh/en 由同一组件渲染且字段可配 -> verify: task_0004
|
||||||
|
- [x] 开源页 README 失败矩阵与 fallback 告警可用 -> verify: task_0005
|
||||||
|
- [x] 后台文档与 hint 完整 -> verify: task_0006
|
||||||
|
- [x] 字体子集化工具把指定字体单页增量压到 ≤150KB -> verify: task_0007
|
||||||
|
|
||||||
|
## 7. 风险与约束
|
||||||
|
- 灰阶体系与双语红线不变;彩色仅正文强调
|
||||||
|
- 块渲染器未知类型跳过 + WARN,绝不炸构建
|
||||||
|
- 字体子集化需保证字形覆盖(裁错字会出现豆腐块)→ 断言:子集后页面文本逐字命中
|
||||||
|
- 多车道并行:共享文件(content.config.ts/admin config/docs)按 boundary 划界,tokens/global 由 orchestrator 或指定单车道
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Phase 5g:页面后台化(blocks 模型)与字体子集化 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
|
||||||
|
- 构建一律 `export PATH="$HOME/.local/node/bin:$PATH"`(Node 26)
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] T1 blocks 骨架(关于页块式页面构建器)
|
||||||
|
- claim: op-gitea
|
||||||
|
- boundary: src/content.config.ts(siteAbout 加 blocks: z.array(discriminatedUnion))、新增 src/lib/page-blocks.ts(getAboutBlocks:过滤 enabled、未知 type 跳过+WARN、文本 pickBi 回落、块数≤24/单块≤4000 字)、新增 src/components/blocks/{BlockRenderer,Prose,Tracks,Divider}.astro、AboutPage.astro 改由 blocks 驱动(blocks 空→intro markdown→默认文案三级回落)、public/admin/config.yml 加 list+types(typeKey: type)
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && grep -q "discriminatedUnion" src/content.config.ts && grep -q "BlockRenderer" src/components/pages/AboutPage.astro && grep -q "typeKey" public/admin/config.yml`
|
||||||
|
|
||||||
|
- [x] T2 块型扩充与图片管线
|
||||||
|
- claim: op-gitea
|
||||||
|
- boundary: 新增 src/components/blocks/{Cards,Timeline,Quote,Figure,LinkList,Cta}.astro + 图标键白名单(复用 SocialLinks 注册表或新建 icons 注册表)、schema 块型补全(图片字段用 image(),禁止外链字符串)、content/site/assets/ 目录、near docs 图表说明
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && ls src/components/blocks/ | grep -qC1 "Cards\|Timeline" && grep -q "image()" src/content.config.ts`
|
||||||
|
|
||||||
|
- [x] T3 样式即数据(theme.yaml + 对比度闸门)
|
||||||
|
- claim: op-artalk
|
||||||
|
- boundary: 新增 content/site/theme.yaml(12 个白名单旋钮:prose-size/measure/radius/accent/leading 等)、新增 src/lib/theme.ts(getTheme + themeVarsCss 生成 :root:root{…})、BaseLayout.astro head 内联 theme 变量(构建期静态)、新增 scripts/build-perf/contrast-check.mjs(断言 theme 结果下正文对比度 ≥4.5)、public/admin/config.yml 加 theme 单例、build.sh 守卫挂载
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && test -f content/site/theme.yaml && node scripts/build-perf/contrast-check.mjs dist`
|
||||||
|
|
||||||
|
- [x] T4 首页细化与去重
|
||||||
|
- claim: op-artalk
|
||||||
|
- boundary: 新增 src/components/pages/HomePage.astro(承接现 index.astro 区块逻辑,locale prop)、src/pages/{index,en/index}.astro 改为薄壳、home.yaml 增 projectsLimit / announcement(markdown)/ postsTracks(轨开关细分),site-content 访问器同步
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && test -f src/components/pages/HomePage.astro && grep -q "HomePage" src/pages/en/index.astro`
|
||||||
|
|
||||||
|
- [x] T5 开源页收尾(缓存/回落/告警)
|
||||||
|
- claim: worker-one
|
||||||
|
- boundary: src/lib/site-content.ts(README 缓存策略与超时、回落矩阵)、content/.github-readme.md 与 README HTML 剥离策略(复用 rehype-content-sanitize 已挡危险标签;补: 外链徽章不阻断)、src/data/projects-fallback.json 回填 5-8 条真实仓、projects.ts include 未命中 console.warn
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && python3 -c "import json;d=json.load(open('src/data/projects-fallback.json'));assert len(d)>=5"`
|
||||||
|
|
||||||
|
- [x] T6 文档与后台 hint 补齐
|
||||||
|
- claim: worker-one
|
||||||
|
- boundary: docs/管理台.md(块编辑怎么用、增删排序、预览限制)、docs/写作指南.md(块字段表/受限类/主题项)、public/admin/config.yml 全字段中文 hint(含 T1-T4 新增字段)
|
||||||
|
- verify: `test -f docs/管理台.md && grep -q "blocks\|区块" docs/管理台.md && grep -c "hint" public/admin/config.yml | awk '$1>=20'`
|
||||||
|
|
||||||
|
- [x] T7 字体子集化工具
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: 新增 scripts/build-perf/subset-fonts.py(pyftsubset 按指定字体 + 页面实际用字生成子集 woff2 + 重写 fonts.css 指向子集文件)、infra/deploy/build.sh 守卫挂载、docs/写作指南加字流程补"子集化"一节
|
||||||
|
- verify: `test -f scripts/build-perf/subset-fonts.py && python3 -c "import fontTools; print('fonttools ok')" && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS"`
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Phase 5h:内容表达能力与社交反馈 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-11-02 21:16
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- 瞬间富文本走与全站文案同一条净化管线即可满足「字体/颜色可换」且不引入 XSS 面(危险标签整棵丢弃、协议白名单 http/https/mailto)
|
||||||
|
- 每条瞬间一个 Artalk page_key(`/moments/<slug>/`)不会与页面级 pv 冲突;Artalk 实例按需挂载(展开才 init)可避免 "one page one Artalk" 的全局实例冲突
|
||||||
|
- 开源页可用本地数据(已有 repo 元数据 + GitHub GraphQL 拉取)自绘 followers/stars/语言占比,无需任何第三方徽章服务
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- T1 文章置顶(契约 + 排序 + 标记 + 后台)
|
||||||
|
- T2 瞬间富文本(字体可换 / 颜色可换 / 多形式)
|
||||||
|
- T3 瞬间逐条评论(Artalk 独立 page_key + 折叠懒挂载)
|
||||||
|
- T4 计数徽标(眼睛图标 = 浏览数,气泡图标 = 评论数)
|
||||||
|
- T5 开源页重构(原生 GitHub 档案卡,去外链图依赖)
|
||||||
|
- T6 关于页承接 README 的个人叙述
|
||||||
|
- T7 写作模板示例文章 + 文档与 hint 收尾
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简单可行方案:置顶先用 boolean + 排序键;瞬间富文本直接把 `text` 交给既有 `renderRich`;评论用现有 Artalk 组件克隆一份带折叠的;计数徽标只做 pv + count 两个数
|
||||||
|
- 暂不引入:置顶权重/多级置顶、瞬间的图片轮播、评论楼中楼定制、开源页的实时 Star 曲线
|
||||||
|
- 不做的抽象:通用「内容社交层」框架(只服务现有页面)
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- 评论回复通知/邮件推送、点赞/投票、阅读进度、站点级总访问量统计(Artalk 无匿名聚合端点)
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- 评论回复通知/邮件推送、点赞/投票、阅读进度、站点级总访问量统计(Artalk 无匿名聚合端点)
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:7/7
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-11-02 21:16
|
||||||
|
- 范围:Phase 5h:内容表达能力与社交反馈
|
||||||
|
- 功能:T1 文章置顶(契约 + 排序 + 标记 + 后台);T2 瞬间富文本(字体可换 / 颜色可换 / 多形式);T3 瞬间逐条评论(Artalk 独立 page_key + 折叠懒挂载);T4 计数徽标(眼睛图标 = 浏览数,气泡图标 = 评论数);T5 开源页重构(原生 GitHub 档案卡,去外链图依赖);T6 关于页承接 README 的个人叙述;T7 写作模板示例文章 + 文档与 hint 收尾
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:ed473e996a3707f98015a40e4388aa4fa58b79ac..bdaf02f9cae774e2a094eb716a95be3f0eddab56
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
Phase 5h 资产:①置顶契约(sortPosts 统一四处排序,只改顺序不动 pubDate/URL/RSS)②瞬间富文本(复用 renderRich 净化链,危险标签整棵丢弃)③瞬间逐条评论(展开才挂 Artalk;评论键用虚拟路径 /moments/<slug>/——实测 # 在查询串会被截断成页面级键,导致所有瞬间共用一个评论流)④计数徽标 ViewMeta(眼睛/气泡、零第三方图片;修同载双计与同页数字差 1 两个自伤 bug,口径=「请求次数对」不等于「展示自洽」)⑤开源页去 README 外链依赖(7 类第三方图与 align 居中失效是「没居中/图片不显示」的根因)⑥灰阶纪律清剿:项目墙 15 个彩色语言点、并列 36% 单着色、全局 .tap 泄漏致卡片居中 ⑦视觉验收新增两条可复用断言法:Range.getClientRects 判折行、scrollWidth==innerWidth 判横向溢出。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -q \"pinned\" src/content.config.ts && grep -q \"pinned\" src/components/PostList.astro && grep -q \"pinned\" public/admin/config.yml", "exit": 0, "duration_s": 21.239, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:58:45+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -qE \"renderRich|renderMarkdown\" src/components/pages/MomentsPage.astro && python3 -c \"import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t\"", "exit": 0, "duration_s": 21.893, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:59:07+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f src/components/MomentComments.astro && grep -q \"MomentComments\" src/components/pages/MomentsPage.astro && grep -q \"moments/#\" src/components/MomentComments.astro && 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": 21.073, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:59:28+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "test -f src/components/geek/ViewMeta.astro && grep -q \"ViewMeta\" src/components/ArtalkComments.astro && grep -q \"ViewMeta\" src/pages/archives/[...slug].astro && 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": 23.015, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:59:51+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "grep -q \"GithubProfile\" src/pages/opensource.astro && python3 -c \"import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.03, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T12:59:52+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "python3 -c \"import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types\" && 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": 21.067, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T13:00:13+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q \"pinned: true\" content/posts/writing-template.md && grep -qE \"font-kai|c-blue\" content/posts/writing-template.md && grep -q \"写作模板\" docs/写作指南.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a4c5572a52c4f546900c03b86c7cfd32056ad32ec27194018ffc175ac700f3c5", "at": "2026-11-02T13:00:13+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -q \"pinned\" src/content.config.ts && grep -q \"pinned\" src/components/PostList.astro && grep -q \"pinned\" public/admin/config.yml", "exit": 0, "duration_s": 20.732, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:04:09+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -qE \"renderRich|renderMarkdown\" src/components/pages/MomentsPage.astro && python3 -c \"import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t\"", "exit": 0, "duration_s": 20.695, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:04:30+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f src/components/MomentComments.astro && grep -q \"MomentComments\" src/components/pages/MomentsPage.astro && grep -q 'moments/${slug}/' src/components/MomentComments.astro && 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": 20.501, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:04:50+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "test -f src/components/geek/ViewMeta.astro && grep -q \"ViewMeta\" src/components/ArtalkComments.astro && grep -q \"ViewMeta\" src/pages/archives/[...slug].astro && 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": 20.83, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:05:11+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "grep -q \"GithubProfile\" src/pages/opensource.astro && python3 -c \"import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.029, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:05:11+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "python3 -c \"import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types\" && 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": 21.719, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:05:33+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q \"pinned: true\" content/posts/writing-template.md && grep -qE \"font-kai|c-blue\" content/posts/writing-template.md && grep -q \"写作模板\" docs/写作指南.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "f35452f48bd7d96babf34565aeef8d17c25191be02e07d93fd27922795604348", "at": "2026-11-02T13:05:33+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -q \"pinned\" src/content.config.ts && grep -q \"pinned\" src/components/PostList.astro && grep -q \"pinned\" public/admin/config.yml", "exit": 0, "duration_s": 21.074, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:12:48+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -qE \"renderRich|renderMarkdown\" src/components/pages/MomentsPage.astro && python3 -c \"import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t\"", "exit": 0, "duration_s": 20.656, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:13:09+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f src/components/MomentComments.astro && grep -q \"MomentComments\" src/components/pages/MomentsPage.astro && grep -q 'moments/${slug}/' src/components/MomentComments.astro && 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": 20.386, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:13:29+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "test -f src/components/geek/ViewMeta.astro && grep -q \"ViewMeta\" src/components/ArtalkComments.astro && grep -q \"ViewMeta\" src/pages/archives/[...slug].astro && 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": 20.357, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:13:49+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "grep -q \"GithubProfile\" src/pages/opensource.astro && python3 -c \"import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.029, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:13:49+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "python3 -c \"import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types\" && 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": 21.589, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:14:11+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q \"pinned: true\" content/posts/writing-template.md && grep -qE \"font-kai|c-blue\" content/posts/writing-template.md && grep -q \"写作模板\" docs/写作指南.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "62086ce4cccd8c576913526a7ae517471278660c8e6cc6d7e24f9ea632038195", "at": "2026-11-02T13:14:11+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -q \"pinned\" src/content.config.ts && grep -q \"pinned\" src/components/PostList.astro && grep -q \"pinned\" public/admin/config.yml", "exit": 0, "duration_s": 20.396, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:14:57+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE \"汇总: [0-9]+/[0-9]+ PASS\" && grep -qE \"renderRich|renderMarkdown\" src/components/pages/MomentsPage.astro && python3 -c \"import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t\"", "exit": 0, "duration_s": 21.377, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:15:19+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f src/components/MomentComments.astro && grep -q \"MomentComments\" src/components/pages/MomentsPage.astro && grep -q 'moments/${slug}/' src/components/MomentComments.astro && 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": 20.908, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:15:40+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "test -f src/components/geek/ViewMeta.astro && grep -q \"ViewMeta\" src/components/ArtalkComments.astro && grep -q \"ViewMeta\" src/pages/archives/[...slug].astro && 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": 21.893, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:16:02+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "grep -q \"GithubProfile\" src/pages/opensource.astro && python3 -c \"import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.029, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:16:02+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "python3 -c \"import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types\" && 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": 20.399, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:16:22+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q \"pinned: true\" content/posts/writing-template.md && grep -qE \"font-kai|c-blue\" content/posts/writing-template.md && grep -q \"写作模板\" docs/写作指南.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4af92016715e63fb205156cc420c1d1a9f264ede8fd6bb358ffc8099741b2cef", "at": "2026-11-02T13:16:22+00:00"}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# Phase 5h:内容表达能力与社交反馈 - 项目范围
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:①文章支持置顶(作者指定优先)②瞬间的文字与文章同级表达力(字体可换、颜色可换、多段落/列表/强调)③瞬间每条可独立评论 ④浏览量与评论数用统一的「眼睛/气泡」计数徽标呈现(对齐开源页 GitHub 档案的眼图标语汇)⑤开源页开篇从 GitHub README 直接移植导致:`<div align="center">` 内联对齐失效、shields/vercel/snake/komarev 等外链图在慢网下空白破版、README 的「About Me」与开源页主题无关——重构为原生组件并从 README 解耦 ⑥关于页承接读完 README 后仍想保留的个人叙述 ⑦新增一篇「写作模板」示例文章,把可用排版能力一次演示完
|
||||||
|
- **目标用户**:站长(写得更自由,少打字面 HTML)、读者(读到更丰富且不破版的页)
|
||||||
|
- **核心价值**:内容表达力与后台编辑力对上,同时把外链依赖与版式事故面砍掉;社交反馈(浏览/评论)在灰阶体系内可量化可见
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- Artalk v2 匿名可读:`GET /artalk/api/v2/comments?site_name=…&page_key=…&limit=1` 返回 `{count, roots_count, page:{pv}}`(生产实测,2026-11-02);`POST /artalk/api/v2/pages/pv` 返回 `{pv}`(现有组件在用)
|
||||||
|
- 富文本管线已存在且已净化:`renderRich`(markdown + rehype-content-sanitize 标签/属性/协议三层白名单)与 `rehype-class-whitelist`(35 项排版类,单一事实源 `public/admin/typography-classes.js`)
|
||||||
|
- 现有 6 自托管字族(含 font-huiwen 子集化 420KB 单页)与 16 彩色语义类 + 4 灰阶语义类
|
||||||
|
- 开源页 README 现由 `intro_from: "off"` 关闭,缓存文件 `content/.github-readme.md`(3138B)含 6 类外链图(readme-typing-svg / shields.io / github-readme-stats / streak-stats / activity-graph / snake / komarev)
|
||||||
|
- 首页/归档/书架三处列表排序均为 `pubDate` 降序,无置顶概念(`grep pinned` 只命中开源页仓库白名单)
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- 瞬间富文本走与全站文案同一条净化管线即可满足「字体/颜色可换」且不引入 XSS 面(危险标签整棵丢弃、协议白名单 http/https/mailto)
|
||||||
|
- 每条瞬间一个 Artalk page_key(`/moments/<slug>/`)不会与页面级 pv 冲突;Artalk 实例按需挂载(展开才 init)可避免 "one page one Artalk" 的全局实例冲突
|
||||||
|
- 开源页可用本地数据(已有 repo 元数据 + GitHub GraphQL 拉取)自绘 followers/stars/语言占比,无需任何第三方徽章服务
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 瞬间文字是否允许 HTML 类名(字体/颜色)→ 用户原话「字体可换、颜色可换等」,按富文本管线放行 35 项白名单类
|
||||||
|
- [x] 开源页要不要与 README 完全一致 → 用户原话「可以自行进行美化,无需完全一致」,按原生组件重构
|
||||||
|
- [x] About Me 类内容放哪 → 用户原话「有些可以直接移植到关于页面中」,迁到 about.yaml blocks
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- 置顶只作用于列表排序与视觉标记,不改 URL / 不改 pubDate 语义(订阅源按时间仍正确)
|
||||||
|
- 计数徽标一次只拉当前页的 pv(POST 计一次)与评论数(GET 读);瞬间列表按可视区懒拉,避免 N 次请求把首屏拖慢
|
||||||
|
- 开源页保留 `intro_from` 开关作为 compat,但默认 off 且文档标注「不推荐」;README 渲染通道不删除(后台仍可切回)
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 直接渲染 README HTML 并试图用 CSS 修 `align="center"`:内联属性与 sanitize 的交互不可控,且外链图仍会空白破版 → 弃,改原生组件
|
||||||
|
- 每条瞬间都 init 一个 Artalk 实例常驻:内存与请求成本高、且 Artalk 对多实例的支持未验证 → 改为展开时挂载
|
||||||
|
- 用第三方徽章服务显示访客数(komarev 等):外链依赖 + 灰阶体系外颜色 → 弃,用 Artalk pv 自绘
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 文章置顶:posts/postsEn schema `pinned`、四处列表排序(首页双轨/归档/书架)、视觉标记(灰阶 pin)、后台开关
|
||||||
|
- [x] T2 瞬间富文本:moments `text` 支持 markdown + 白名单排版类(字体/颜色/强调/多段/列表)、净化管线复用、后台改 markdown 编辑器、旧纯文本零回归
|
||||||
|
- [x] T3 瞬间逐条评论:每条 moment 独立 page_key 的 Artalk 评论区(折叠、展开时挂载)、评论数联动、双语页共用同一 key
|
||||||
|
- [x] T4 计数徽标:新增 `ViewMeta` 组件(眼睛图标 = 浏览数、气泡图标 = 评论数),接入文章页/瞬间页/关于页/开源页
|
||||||
|
- [x] T5 开源页重构:原生 GitHub 档案卡(头像/名字/关注者/星标/语言占比,本地数据自绘)替换 README 外链图;`align` 失效与破图问题消失;README 通道降级为可选
|
||||||
|
- [x] T6 关于页承接:把 README 的 About Me / Find Me 迁为 about.yaml blocks(prose + linklist),开源页只留开源相关
|
||||||
|
- [x] T7 写作模板示例:中英双语示例文章,演示标题层级/彩色强调/字族/引用/代码/图/表/链接卡片/折叠,并作为置顶功能的活样本;docs 与后台 hint 同步
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- 评论回复通知/邮件推送、点赞/投票、阅读进度、站点级总访问量统计(Artalk 无匿名聚合端点)
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简单可行方案:置顶先用 boolean + 排序键;瞬间富文本直接把 `text` 交给既有 `renderRich`;评论用现有 Artalk 组件克隆一份带折叠的;计数徽标只做 pv + count 两个数
|
||||||
|
- 暂不引入:置顶权重/多级置顶、瞬间的图片轮播、评论楼中楼定制、开源页的实时 Star 曲线
|
||||||
|
- 不做的抽象:通用「内容社交层」框架(只服务现有页面)
|
||||||
|
|
||||||
|
## 5. 技术决策
|
||||||
|
- 技术栈:Astro 7.4.4 + zod schema + Artalk v2 HTTP API + GitHub GraphQL(已有 token)
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- 本轮允许改动:src/content.config.ts、src/components/{PostList,ArtalkComments}.astro、src/components/pages/{HomePage,MomentsPage,BooksPage}.astro、src/components/geek/{ViewMeta,GithubProfile}.astro(新)、src/pages/archives/[...slug].astro、src/pages/en/archives/[...slug].astro、src/pages/{about,opensource,en/about,en/opensource}.astro、src/lib/{projects,site-content,heatmap}.ts、content/site/{about,opensource}.yaml、content/posts/*(新增示例文章)、content/posts-en/*、public/admin/{config.yml,typography-classes.js}、docs/*、tests/e2e.mjs
|
||||||
|
- 本轮不应触碰:nginx/ESA、灯箱与图库(GalleryPage/PhotoZoom)、Header/Footer 结构、构建链顺序、tokens.css/global.css 基础尺度
|
||||||
|
- Git integration branch:`spec/2026-11-02_blog-5h-content-social`
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:内容模型(置顶/瞬间富文本)、渲染组件(计数/评论/档案卡)、后台配置、示例内容
|
||||||
|
- 快速检查:`pnpm astro build`(node26)+ `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查:`E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`(37 项,本包新增置顶顺序/徽标存在/瞬间富文本断言)+ content-lint 0/0/0
|
||||||
|
- 全项目检查:生产实测四条(开源页零第三方外链图、瞬间评论可发可见、徽标数值与 Artalk 端点对账一致、关于页承接内容渲染);**升级条件**——仅当改动触及 BaseLayout/全局样式(tokens/字体栈)或 Artalk 主题接管层时才执行,本包因往 tokens 补 emoji 回退链已触发一次并记录了理由
|
||||||
|
- 升级触发:若动到 BaseLayout/全局样式或 Artalk 主题接管层,升级为全站视觉复核(glm-5.3-flash:max,≤6 图/轮)
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] 置顶文章排在列表首位且带可见标记,取消置顶后恢复时间序 -> verify: task_0001
|
||||||
|
- [x] 瞬间文字可用字体类与彩色强调,危险标签被净化丢弃,旧纯文本渲染不变 -> verify: task_0002
|
||||||
|
- [x] 每条瞬间可展开评论区并成功发帖,双语页共用同一评论流 -> verify: task_0003
|
||||||
|
- [x] 文章/瞬间/关于/开源四页显示「浏览数 + 评论数」徽标且数值与 Artalk 一致 -> verify: task_0004
|
||||||
|
- [x] 开源页零第三方图片请求,档案卡数据来自本地/GraphQL,居中与破图问题不再出现 -> verify: task_0005
|
||||||
|
- [x] README 的个人叙述出现在关于页,开源页只保留开源相关内容 -> verify: task_0006
|
||||||
|
- [x] 示例文章渲染全部演示能力且构建通过,docs/hint 同步 -> verify: task_0007
|
||||||
|
|
||||||
|
## 7. 风险与约束
|
||||||
|
- 灰阶外壳红线不变:徽标图标与数字用 tokens(`--ink-*`/`--line`),不引入第三方彩色徽章
|
||||||
|
- 彩色仅正文强调(≤3 处/页)——示例文章本身要守纪律,成为正面样本而非反例
|
||||||
|
- 瞬间富文本必须复用 `renderRich` 净化链,禁止新增 set:html 直通路径
|
||||||
|
- Artalk 多实例风险:评论区折叠 + 展开挂载,若实测冲突则退回「逐条跳转到锚点页」
|
||||||
|
- 双语链路:所有新文案走 `t()`/`pickBi`,en 轨不得出现中文硬编码
|
||||||
|
- 多车道并行:content.config.ts / public/admin/config.yml 由 orchestrator 先落契约再放行,车道只改各自 boundary 内文件
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Phase 5h:内容表达能力与社交反馈 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
|
||||||
|
- 构建一律 `export PATH="$HOME/.local/node/bin:$PATH"`(Node 26)+ `pnpm astro build`
|
||||||
|
- **契约先行**:`src/content.config.ts` 与 `public/admin/config.yml` 由 orchestrator 先落契约(T1 时一并提交),车道不得再改这两个文件;需要新字段一律回报 orchestrator
|
||||||
|
- E2E 口径:`pnpm astro build → cp -a dist /tmp/dist-e2e → 用 18925 端口起快照服务 → E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] T1 文章置顶(契约 + 排序 + 标记 + 后台)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: src/content.config.ts(postsSchema 加 `pinned: z.boolean().optional()`,posts/postsEn 共用)、src/components/PostList.astro(置顶优先排序 + pin 标记)、src/pages/archives/index.astro(排序)、src/components/pages/{HomePage,BooksPage}.astro(排序)、public/admin/config.yml(posts 集合 pinned 开关 + 中文 hint)、src/i18n/ui.ts(置顶标签)
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS" && grep -q "pinned" src/content.config.ts && grep -q "pinned" src/components/PostList.astro && grep -q "pinned" public/admin/config.yml`
|
||||||
|
|
||||||
|
- [x] T2 瞬间富文本(字体可换 / 颜色可换 / 多形式)
|
||||||
|
- claim: worker-one
|
||||||
|
- boundary: src/components/pages/MomentsPage.astro(`text` 由纯文本 `<p>` 改为走 `renderRich` 净化+排版类管线,保留 inline 语义;空文本老数据零回归)、public/admin/config.yml 中 moments 集合 `text` 字段改 markdown widget + 提示可用排版类(**仅在 orchestrator 提交的字段块内改 widget 类型,不改结构**)、docs 提示留给 T7
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS" && grep -qE "renderRich|renderMarkdown" src/components/pages/MomentsPage.astro && python3 -c "import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t"`
|
||||||
|
|
||||||
|
- [x] T3 瞬间逐条评论(Artalk 独立 page_key + 折叠懒挂载)
|
||||||
|
- claim: worker-one
|
||||||
|
- boundary: 新增 src/components/MomentComments.astro(折叠 disclosure,展开时首次挂载 Artalk;page_key 约定 `/moments/#<slug>`,en 页同一 key 不分裂;评论数徽标复用 T4 的 ViewMeta 计数接口)、src/components/pages/MomentsPage.astro(每条卡片挂评论区)
|
||||||
|
- verify: `test -f src/components/MomentComments.astro && grep -q "MomentComments" src/components/pages/MomentsPage.astro && grep -q 'moments/${slug}/' src/components/MomentComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||||
|
|
||||||
|
- [x] T4 计数徽标(眼睛图标 = 浏览数,气泡图标 = 评论数)
|
||||||
|
- claim: op-artalk
|
||||||
|
- boundary: src/components/geek/ViewMeta.astro(**契约组件,由 orchestrator 在 T1 提交时一并落盘**,车道只消费不改语义)、src/components/ArtalkComments.astro(pv 小字行改为 ViewMeta,评论数取 `comments?limit=1` 的 count)、src/pages/archives/[...slug].astro、src/pages/en/archives/[...slug].astro(挂 ViewMeta)
|
||||||
|
- verify: `test -f src/components/geek/ViewMeta.astro && grep -q "ViewMeta" src/components/ArtalkComments.astro && grep -q "ViewMeta" src/pages/archives/[...slug].astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||||
|
|
||||||
|
- [x] T5 开源页重构(原生 GitHub 档案卡,去外链图依赖)
|
||||||
|
- claim: op-gitea
|
||||||
|
- boundary: 新增 src/components/geek/GithubProfile.astro(头像/名字/简介/关注者/总星标/语言占比,数据来自 projects.ts 与 GraphQL;零第三方图片与徽章)、src/lib/projects.ts(补统计聚合:followers/stars/language 汇总,失败降级不炸构建)、src/pages/opensource.astro、src/pages/en/opensource.astro(用 GithubProfile 替换 readme 渲染段;README 通道保留开关但默认 off 且标注不推荐)、content/site/opensource.yaml(注释说明 intro_from 现状)
|
||||||
|
- verify: `grep -q "GithubProfile" src/pages/opensource.astro && python3 -c "import re,pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev','github-readme-activity-graph'] if u in p];assert not bad,bad"`
|
||||||
|
|
||||||
|
- [x] T6 关于页承接 README 的个人叙述
|
||||||
|
- claim: op-gitea
|
||||||
|
- boundary: content/site/about.yaml(新增 blocks:关于/兴趣 prose、站点与项目 linklist、Find Me 社交块;双语 zh/en;测试值不得留在文件里)、src/pages/about.astro 与 en(仅当块渲染需要时改,优先零改动)
|
||||||
|
- verify: `python3 -c "import yaml;d=yaml.safe_load(open('content/site/about.yaml'));types=[b['type'] for b in d.get('blocks') or []];assert 'prose' in types and ('linklist' in types or 'cards' in types),types" && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||||
|
|
||||||
|
- [x] T7 写作模板示例文章 + 文档与 hint 收尾
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: content/posts/writing-template.md、content/posts-en/writing-template.md(en 手写 locked:true 直译,不交给 TMT 重译)、docs/写作指南.md(模板用法 + 排版类清单去重指引)、docs/管理台.md(瞬间富文本/逐条评论/置顶/徽标说明)、public/admin/config.yml(moments text hint、posts pinned 已含)
|
||||||
|
- verify: `test -f content/posts/writing-template.md && test -f content/posts-en/writing-template.md && grep -q "pinned: true" content/posts/writing-template.md && grep -qE "font-kai|c-blue" content/posts/writing-template.md && grep -q "写作模板" docs/写作指南.md`
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# Phase 5i:字体加载根治、交互缺陷修复与书架/开源页再设计 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-11-03 12:46
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- 字体按「内容实际用到的字族」按需加载即可兼顾体积与正确性(构建期扫正文/页面文案里的 `font-*` 类)
|
||||||
|
- README 动效图均为 SVG(含 SMIL/CSS 动画),可在构建期抓取并自托管,运行时零第三方请求,动效保留
|
||||||
|
- 灯箱信息栏与关闭钮的层叠冲突用 z-index + 命中区域划分即可解,无需改结构
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- T1 字族按需加载根治
|
||||||
|
- T2 Artalk 失败误报修复
|
||||||
|
- T3 瞬间评论区配色同步
|
||||||
|
- T4 灯箱关闭修复 + 右侧文字可排版
|
||||||
|
- T5 取消彩色强调次数限制
|
||||||
|
- T6 开源页项目墙恢复居中
|
||||||
|
- T7 书架页重做
|
||||||
|
- T8 README 动效图自托管与展示创新
|
||||||
|
- T9 字族扩展(款式待用户拍板)
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简可行:字体先修门禁(用现有 6 族验证通过),再谈扩展;灯箱只改层级与命中;README 动效先接 3 类(typing / stats / snake),验证体积与稳定性后再补
|
||||||
|
- 暂不引入:字体子集化的自动重跑编排、灯箱信息栏的富文本编辑器 UI
|
||||||
|
- 不做的抽象:通用「动效资源托管」服务
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- 换 CMS、评论系统替换、Artalk 服务端改造
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- 换 CMS、评论系统替换、Artalk 服务端改造
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:9/9
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-11-03 12:46
|
||||||
|
- 范围:Phase 5i:字体加载根治、交互缺陷修复与书架/开源页再设计
|
||||||
|
- 功能:T1 字族按需加载根治;T2 Artalk 失败误报修复;T3 瞬间评论区配色同步;T4 灯箱关闭修复 + 右侧文字可排版;T5 取消彩色强调次数限制;T6 开源页项目墙恢复居中;T7 书架页重做;T8 README 动效图自托管与展示创新;T9 字族扩展(款式待用户拍板)
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:36643f5cf087b9f8b46f598516b30c6b80fe9e6b..83e0b863fc117d2f4d05e144331bc748b1881988
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
Phase 5i 资产:①字体切换失效的根因=加载门禁(四份字体 CSS 被关在阅读轨分支,技术轨与全站页面从不加载)→ 改为「按本页内容实际用到的字族」注入;②六款自托管字族(OFL)全部过单页 ≤150KB 闸门,关键取舍=@fontsource 中文切片粒度太粗(一句话 308–442KB)→ 改原始 TTF 字频序细切(19 字/片,族总量 0.5–1.2MB);③评论失败提示误报的状态机修法(脚本标签在但库未就绪即判失败);④Artalk init 后加 .artalk 类导致 CDN 样式平级后来居上(瞬间评论区翻白);⑤灯箱关闭被右侧全高侧栏命中遮挡(elementFromPoint 实锤);⑥书架定稿方案 C,置顶标记改图标、多本置顶默认展开;⑦README 动效自托管 + 数据板(零第三方外链);⑧视觉验收法再沉淀:三段右缘对齐量化、竖排文本 scrollHeight 判溢、Range.getClientRects 判折行。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{"task": "task_0001", "cmd": "grep -q \"fontsForPage\" src/layouts/BaseLayout.astro && 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.681, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:42:37+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "grep -q \"onerror\" src/components/ArtalkComments.astro && 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.242, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:08+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "grep -qE \"data-artalk-panel|mom-atk\" src/components/MomentComments.astro && grep -q \"at-color-bg\" src/components/geek/ArtalkThemeStyles.astro && grep -q \"ArtalkThemeStyles\" src/components/ArtalkComments.astro", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:08+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "grep -q \"lb-close\" src/components/pages/GalleryPage.astro && 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.028, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:38+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "! grep -rq \"一页 ≤3 处\\|≤3 处/页\" docs/ content/posts/writing-template.md && grep -q \"彩色\" docs/写作指南.md", "exit": 0, "duration_s": 0.004, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:38+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "grep -q \"align-items: center\" src/components/geek/ProjectWall.astro", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:43:38+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "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.38, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:44:08+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "test -f scripts/build-perf/fetch-readme-assets.mjs && grep -q \"github-assets\" scripts/build-perf/fetch-readme-assets.mjs && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.033, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:44:08+00:00"}
|
||||||
|
{"task": "task_0009", "cmd": "grep -c \"font-\" public/admin/typography-classes.js | awk '$1>=40'", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:44:08+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "grep -q \"fontsForPage\" src/layouts/BaseLayout.astro && 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": 29.962, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:45:26+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "grep -q \"onerror\" src/components/ArtalkComments.astro && 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": 29.872, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:45:56+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "grep -qE \"data-artalk-panel|mom-atk\" src/components/MomentComments.astro && grep -q \"at-color-bg\" src/components/geek/ArtalkThemeStyles.astro && grep -q \"ArtalkThemeStyles\" src/components/ArtalkComments.astro", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:45:56+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "grep -q \"lb-close\" src/components/pages/GalleryPage.astro && 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": 29.988, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:26+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "! grep -rq \"一页 ≤3 处\\|≤3 处/页\" docs/ content/posts/writing-template.md && grep -q \"彩色\" docs/写作指南.md", "exit": 0, "duration_s": 0.004, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:26+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "grep -q \"align-items: center\" src/components/geek/ProjectWall.astro", "exit": 0, "duration_s": 0.002, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:26+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "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": 29.968, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:56+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "test -f scripts/build-perf/fetch-readme-assets.mjs && grep -q \"github-assets\" scripts/build-perf/fetch-readme-assets.mjs && python3 -c \"import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev'] if u in p];assert not bad,bad\"", "exit": 0, "duration_s": 0.032, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:56+00:00"}
|
||||||
|
{"task": "task_0009", "cmd": "grep -c \"font-\" public/admin/typography-classes.js | awk '$1>=40'", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "ec396f2bc636b87655d31daed6960522d1c311e699ca21959bdbe363543e385b", "at": "2026-11-03T04:46:56+00:00"}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Phase 5i:字体加载根治、交互缺陷修复与书架/开源页再设计 - 项目范围
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:①**字体切换全部失效的根因修复**(不是字体没做,是加载门禁错了)②按需扩展更多字族 ③Artalk「加载失败」误报与瞬间评论区配色脱同步 ④图库灯箱右上关闭被遮挡点不动,且右侧文字要能换字体/颜色 ⑤取消「彩色 ≤3 处/页」限制 ⑥开源页项目墙恢复居中 ⑦书架页重做 ⑧GitHub README 的动效图与更多展示形式回归(自托管,不再外链)
|
||||||
|
- **目标用户**:站长(写得更自由、字体真的能换)与读者(交互不再有假失败、书架更耐看)
|
||||||
|
- **核心价值**:把「看起来能用」变成「真的能用」——本轮多数是用户实测踩到的静默失效,根因都在加载门禁与层级覆盖
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实(本轮实测取证,2026-11-02)
|
||||||
|
- **字体失效根因**:`src/layouts/BaseLayout.astro:76` 把 serif/kai/kuaile/hwmc 四份字体 CSS 全部关在 `track === 'reading'` 里;技术轨与全站页面(默认 `track='tech'`)从不加载它们 → `.font-kai` 计算值虽为 `"LXGW WenKai Screen"` 但该 @font-face 不在文档字体集内(`document.fonts` 实测仅 Noto Sans/JetBrains),浏览器回退系统宋体/衬线,表现为「字体没换」
|
||||||
|
- **汇文明朝链路**:`/fonts/subset/hwmc/fonts.css` 由 public 手工切片提供(339 片 unicode-range),仅在阅读轨被 preload;技术轨整条链路不加载
|
||||||
|
- **Artalk 误报根因**:`ArtalkComments.astro:126` `if (document.getElementById('artalk-lib')) return initArtalk();` —— 软导航后脚本标签已在但 `window.Artalk` 尚未就绪,走 `initArtalk()` → `!lib` → `showRetry(true)` 永久显示失败提示;而评论随后正常渲染(用户所见「总显示失败但实际正常」)
|
||||||
|
- **瞬间评论区配色脱同步**:Artalk 主题变量块选择器是 `#artalk-container` 与 `body .atk-layer-wrap`;瞬间的容器 id 为 `mom-atk-<slug>`,不在作用域内 → 回落到 Artalk 默认色
|
||||||
|
- **灯箱关闭失灵根因**:`#lb-close` 位于 (1220,16,44×44),`document.elementFromPoint` 命中的是 `.lb-meta.lb-l2`(右侧信息栏压在按钮上),点击被信息栏吃掉 → `lb.close()` 永不触发(生产实测 `open` 保持 true)
|
||||||
|
- 书架页现状:色脊卡 + 年份分组网格;信息密度低、悬停无反馈、无简介/评分等可读线索
|
||||||
|
- README 动效资源清单(7 类):readme-typing-svg、github-readme-stats、streak-stats、activity-graph、贡献蛇(daily Action 产出的 SVG)、shields 徽章、komarev 访客
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- 字体按「内容实际用到的字族」按需加载即可兼顾体积与正确性(构建期扫正文/页面文案里的 `font-*` 类)
|
||||||
|
- README 动效图均为 SVG(含 SMIL/CSS 动画),可在构建期抓取并自托管,运行时零第三方请求,动效保留
|
||||||
|
- 灯箱信息栏与关闭钮的层叠冲突用 z-index + 命中区域划分即可解,无需改结构
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 字体扩展款式(用户选:宋系 / 楷系 / 手写系 / 现代黑系各补几款)
|
||||||
|
- [x] 书架重做方向(A 色脊墙精修 / B 封面网格 / C 书脊展开卡)
|
||||||
|
- [x] README 动效图确认「构建期抓取 + 自托管」路线
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- 字体:只按需加载,不做「全站全字族常开」(那会把单页体积拉到数 MB)
|
||||||
|
- README 动效:自托管后仍保留原服务生成逻辑(GitHub Action / 第三方接口),只是把产物入仓并在构建期更新
|
||||||
|
- 彩色限制取消后仍保留「灰阶外壳」红线(导航/页脚/按钮/背景不染色),只取消正文次数限制
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 「字族没做」判断错误:@font-face 与切片文件都在,问题在加载门禁(本轮实测推翻)
|
||||||
|
- 外链 README 图直接保留:既有 502/超时与外链依赖问题(上一阶段已判弃),改自托管
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 字族按需加载根治 + 字族扩展机制:BaseLayout 由「轨道门禁」改为「内容诉求驱动」,扫描正文与页面文案中的 `font-*` 类决定加载哪些字体 CSS;补字体扩展清单与接入流程
|
||||||
|
- [x] T2 Artalk 误报与配色同步:失败提示改为「真失败才提示」(脚本 onerror / 库未就绪超时且未挂载才提示),重置挂载状态机;主题变量覆盖全部 Artalk 实例容器(含瞬间逐条)
|
||||||
|
- [x] T3 灯箱:右上关闭恢复可点(层级与命中区域),右侧信息栏文字支持 markdown + 排版类(字体/颜色)
|
||||||
|
- [x] T4 取消彩色强调次数限制(文档 + 后台 hint + 示例文章同步)
|
||||||
|
- [x] T5 开源页项目墙恢复居中排版
|
||||||
|
- [x] T6 书架页重做(按用户选定方向)
|
||||||
|
- [x] T7 README 动效图自托管与展示创新(typing / stats / streak / activity-graph / snake 等构建期抓取入仓,配合原生数据卡与热力图组合呈现)
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 字体扩展的实际接入(款式待用户定)
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- 换 CMS、评论系统替换、Artalk 服务端改造
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简可行:字体先修门禁(用现有 6 族验证通过),再谈扩展;灯箱只改层级与命中;README 动效先接 3 类(typing / stats / snake),验证体积与稳定性后再补
|
||||||
|
- 暂不引入:字体子集化的自动重跑编排、灯箱信息栏的富文本编辑器 UI
|
||||||
|
- 不做的抽象:通用「动效资源托管」服务
|
||||||
|
|
||||||
|
## 5. 技术决策
|
||||||
|
- 技术栈:Astro 7.4.4 + @fontsource(自托管字体 CSS)+ Artalk v2 + 构建期 SVG 抓取缓存(复用 README 缓存模式)
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- 本轮允许改动:src/layouts/BaseLayout.astro、src/styles/{global,tokens}.css、src/components/{ArtalkComments,MomentComments}.astro、src/components/geek/{ViewMeta,ProjectWall,GithubProfile}.astro、src/components/pages/{GalleryPage,BooksPage}.astro、src/pages/{gallery,books}.astro、src/lib/{projects,site-content}.ts、content/site/*.yaml、content/photos/*.json(caption 富文本)、content/.github-assets/(新缓存目录)、public/admin/*、scripts/build-perf/*、infra/deploy/build.sh、docs/*、tests/e2e.mjs
|
||||||
|
- 本轮不应触碰:nginx/ESA、构建发布链主干顺序、双语路由结构、Artalk 服务端配置
|
||||||
|
- Git integration branch:`spec/2026-11-02_blog-5i-fonts-polish`
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:字体加载策略、Artalk 挂载状态机与主题作用域、灯箱层级与富文本、书架与开源页版式、自托管动效资源
|
||||||
|
- 快速检查:`pnpm astro build`(node26)+ `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查:E2E(当前 46 项,本包新增字族加载/灯箱关闭/Artalk 误报断言)+ content-lint 0/0/0
|
||||||
|
- 升级触发:**已触发两次并记录理由**——①往 tokens.css 补 emoji 回退链(触及全局字体栈)②BaseLayout 字体加载策略改写(触及全站首屏资源)。两次都执行了全站视觉复核(glm-5.3-flash:max,≤6 图/轮,两轮共 9 张)与生产实测。以下情况同样升级:Artalk 挂载状态机、灯箱层级、构建闸门顺序。
|
||||||
|
- 全项目检查:生产实测五条(字体真加载 `document.fonts` 命中、灯箱点关闭真关、评论失败提示不再误报、瞬间评论区配色与页面一致、开源页动效图零外链);**升级条件**——仅当改动触及 BaseLayout 字体策略或 Artalk 挂载状态机时升级为全站视觉复核,本包因两者都动,已排一轮视觉终审
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] 技术轨文章里的 `.font-kai/.font-song/.font-huiwen/.font-kuaile` 真加载对应字体(`document.fonts` 命中且渲染变化) -> verify: task_0001
|
||||||
|
- [x] 评论失败提示只在真失败时出现,正常挂载不提示 -> verify: task_0002
|
||||||
|
- [x] 瞬间评论区配色与页面主题一致(明暗双态) -> verify: task_0003
|
||||||
|
- [x] 灯箱右上关闭按钮点击必关(含缩放/信息栏展开态) -> verify: task_0004
|
||||||
|
- [x] 灯箱右侧文字支持字体与颜色类 -> verify: task_0005
|
||||||
|
- [x] 彩色强调不再有限次约束且文档同步 -> verify: task_0006
|
||||||
|
- [x] 开源页项目墙内容居中 -> verify: task_0007
|
||||||
|
- [x] 书架页信息密度与视觉达设计稿并过视觉终审 -> verify: task_0008
|
||||||
|
- [x] README 动效图自托管且生产零第三方请求 -> verify: task_0009
|
||||||
|
|
||||||
|
## 7. 风险与约束
|
||||||
|
- 灰阶外壳红线不变,仅取消正文彩色次数限制
|
||||||
|
- 自制切片/新增字体必须守单页增量 ≤150KB 阈值,超阈值只做子集化或降级为系统回退
|
||||||
|
- README 动效自托管:抓取失败必须回落(隐藏该块,绝不炸构建),且产物必须入仓保证构建确定性
|
||||||
|
- Artalk 状态机改动需覆盖三条触发路径(IO / 锚点 / 滚底),不得回归「评论区空着」
|
||||||
|
- 多车道并行:BaseLayout、ArtalkComments、GalleryPage、BooksPage 各归单一车道,admin config 与 docs 由 orchestrator 统一改
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Phase 5i:字体加载根治、交互缺陷修复与书架/开源页再设计 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
|
||||||
|
- 构建一律 `export PATH="$HOME/.local/node/bin:$PATH"`(Node 26)+ `pnpm astro build`
|
||||||
|
- E2E 口径:`pnpm astro build → cp -a dist /tmp/dist-e2e-5i → gz-server.py 18925 /tmp/dist-e2e-5i → E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] T1 字族按需加载根治
|
||||||
|
- claim: worker-one
|
||||||
|
- boundary: src/layouts/BaseLayout.astro(去掉 `track === 'reading'` 的字体门禁,改为「本页内容实际用到的字族」驱动:构建期扫正文/页面文案里的 `font-kai|font-song|font-fangsong|font-huiwen|font-kuaile|font-serif` 决定注入哪几份字体 CSS;零命中时只加载 sans/mono)、src/lib/ 新增字体诉求探测工具
|
||||||
|
- verify: `grep -q "fontsForPage" src/layouts/BaseLayout.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||||
|
|
||||||
|
- [x] T2 Artalk 失败误报修复
|
||||||
|
- claim: op-artalk
|
||||||
|
- boundary: src/components/ArtalkComments.astro(状态机重写:脚本 onerror 才是失败;`artalk-lib` 已存在但库未就绪时等 load 事件而非立即判失败;失败提示只在「确实未挂载且已超时」出现;`mounting` 在所有失败/成功路径复位)
|
||||||
|
- verify: `grep -q "onerror" src/components/ArtalkComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||||
|
|
||||||
|
- [x] T3 瞬间评论区配色同步
|
||||||
|
- claim: op-artalk
|
||||||
|
- boundary: src/components/MomentComments.astro(Artalk 主题变量作用域覆盖瞬间容器 `[data-artalk-panel]`/`mom-atk-*`,复用文章页那套 `--at-color-*` 映射:建议抽出共享样式片段避免两份漂移)
|
||||||
|
- verify: `grep -qE "data-artalk-panel|mom-atk" src/components/MomentComments.astro && grep -q "at-color-bg" src/components/geek/ArtalkThemeStyles.astro && grep -q "ArtalkThemeStyles" src/components/ArtalkComments.astro`
|
||||||
|
|
||||||
|
- [x] T4 灯箱关闭修复 + 右侧文字可排版
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: src/components/pages/GalleryPage.astro(关闭钮层级/命中区域;右侧信息栏不再拦截点击)、content/photos/*.json(caption 允许 markdown + 排版类)、必要时 src/lib/exif.ts 注释同步
|
||||||
|
- verify: `grep -q "lb-close" src/components/pages/GalleryPage.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||||
|
|
||||||
|
- [x] T5 取消彩色强调次数限制
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: docs/写作指南.md、docs/管理台.md、public/admin/config.yml(hint 里去掉「≤3 处」表述)、content/posts/writing-template.md + posts-en 同名(示例文章说明同步)
|
||||||
|
- verify: `! grep -rq "一页 ≤3 处\|≤3 处/页" docs/ content/posts/writing-template.md && grep -q "彩色" docs/写作指南.md`
|
||||||
|
|
||||||
|
- [x] T6 开源页项目墙恢复居中
|
||||||
|
- claim: op-gitea
|
||||||
|
- boundary: src/components/geek/ProjectWall.astro(`align-items: center` + `text-align: center`,与用户审美偏好一致)
|
||||||
|
- verify: `grep -q "align-items: center" src/components/geek/ProjectWall.astro`
|
||||||
|
|
||||||
|
- [x] T7 书架页重做
|
||||||
|
- claim: orchestrator(方向待用户拍板)
|
||||||
|
- boundary: src/components/pages/BooksPage.astro、可选 content/site/books.yaml 新配置、src/i18n/ui.ts
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`
|
||||||
|
|
||||||
|
- [x] T8 README 动效图自托管与展示创新
|
||||||
|
- claim: op-gitea
|
||||||
|
- boundary: 新增 scripts/build-perf/fetch-readme-assets.mjs(构建期抓 typing/stats/streak/activity-graph/snake 等 SVG 到 content/.github-assets/,失败回落隐藏该块)、src/components/geek/GithubProfile.astro 或新增动效展示组件、build.sh 守卫挂载、docs 说明
|
||||||
|
- verify: `test -f scripts/build-perf/fetch-readme-assets.mjs && grep -q "github-assets" scripts/build-perf/fetch-readme-assets.mjs && python3 -c "import pathlib;p=pathlib.Path('dist/opensource/index.html').read_text();bad=[u for u in ['img.shields.io','readme-typing-svg','github-readme-stats','streak-stats','komarev'] if u in p];assert not bad,bad"`
|
||||||
|
|
||||||
|
- [x] T9 字族扩展(款式待用户拍板)
|
||||||
|
- claim: worker-one
|
||||||
|
- boundary: src/styles/tokens.css、src/styles/global.css、public/admin/typography-classes.js、public/fonts/(新增字体切片)、docs/写作指南.md 字体清单
|
||||||
|
- verify: `grep -c "font-" public/admin/typography-classes.js | awk '$1>=40'`
|
||||||
@@ -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"}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Phase 5j:内容表达去重与页面文案可编辑 - 项目范围
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:①开源页叙述合一(`blurb` + `blurb_after` 两个字段夹一个链接的历史包袱,改为单段 markdown 富文本)②贡献数据去重与时效(贡献蛇是静态快照且与热力图重复→删;「连续贡献」非实时→改用热力图同一份 GraphQL 数据本地计算并标注更新时间)③瞬间页与书架页「标题下的话语」从硬编码改为后台可编辑,且字号/颜色/字体可调 ④关于页探索更自由的编辑方式(3–5 个方案供用户选)
|
||||||
|
- **目标用户**:站长(少记字段、能改每页的说明文字与样式)与读者(不看到重复、不看到过期数据)
|
||||||
|
- **核心价值**:把「为历史实现留下的人工约束」(字段拆两半、静态快照当实时数据)清掉,同时让页面文案回到后台手里
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- `content/site/opensource.yaml` 的 `blurb` / `blurb_after` 只为把 `repo_url` 链接夹在中间(`GithubProfile.astro` 里 `{blurb}<a>{repoLabel}</a>{' '}{blurbAfter}`),markdown 富文本可直接内联链接
|
||||||
|
- 贡献蛇 `public/github-assets/snake.svg` 是 GitHub Action 每日生成的**静态 SVG 快照**(抓取时已剥掉动画);`Heatmap.astro` 展示的是同一份 GitHub 贡献数据 → 信息重复
|
||||||
|
- `streak.svg`(当前/最长连续贡献)同为构建期抓取的快照,`public/github-assets/manifest.json` 记录抓取时间,构建不跑不更新
|
||||||
|
- `src/lib/heatmap.ts` 已能用 token 拉 `contributionsCollection.contributionCalendar`(53 周 × 7 日),**本地即可算出当前连续/最长连续/总贡献**,无需第三方 SVG
|
||||||
|
- 瞬间页标题下话语来自 `i18n/ui.ts` 的 `moments_note`;书架页来自 `BooksPage.astro` 里的字面量 `L.note` → 均不可后台编辑
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- 用热力图数据本地算连续贡献,与 GitHub 页面口径可能有 ±1 天差异(跨时区/当天未计),需在界面上标注「数据更新于 X(构建期)」
|
||||||
|
- 关于页改为单字段 markdown 富文本后,现有 blocks 能力仍保留为可选(blocks 非空优先渲染)
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 关于页形态:→ 已拆分为独立包 `2026-11-03_blog-5k-about-designs`(含纯 markdown 直编在内的 5 案预览,供用户选型)
|
||||||
|
- [x] 连续贡献:采用「构建期计算 + 更新时间标注」(与热力图同源,不引入定时任务;如将来需要每日刷新再立项)
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- 旧字段保留为兼容回落(`story` 为空时仍拼 `blurb` + 链接 + `blurb_after`),避免后台旧值突然消失
|
||||||
|
- 页面文案排版只开放三档(字号/颜色 token/字体类),不开放任意 CSS
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 继续用 `streak.svg`:无法实时、暗色需滤镜、与热力图数据源重复 → 弃
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 开源页叙述合一:`story` 单字段 markdown(内联链接),旧 `blurb/blurb_after` 作为兼容回落
|
||||||
|
- [x] T2 贡献数据去重与时效:删除贡献蛇块与抓取;连续贡献改为热力图同源本地计算(当前/最长/总计)+ 数据时间标注
|
||||||
|
- [x] T3 页面文案可编辑:新增 `content/site/pages.yaml` 单例(瞬间/书架 note 双语 + 字号/颜色/字体),两页消费,后台可改
|
||||||
|
- [x] T4 关于页形态探索 → 拆分为独立包 `2026-11-03_blog-5k-about-designs`(2026-11-04 重划:关于页是内容选型决策,需用户在预览页里选,与本包工程主线不同节奏,独立成包避免阻塞上线)
|
||||||
|
- [x] T5 文档与后台 hint 同步
|
||||||
|
|
||||||
|
### 3.2 扩展功能(已决策)
|
||||||
|
- 连续贡献每日刷新:**本包不做**(标注「数据更新于」已满足知情);将来如需要,另行立项 systemd timer
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- 换 GitHub 数据源、Artalk/灯箱改造、书架与开源页版式重做
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简可行:`story` 先做兼容(新字段空则走旧逻辑);连续贡献先用现有 heatmap 数据算三个数;页面文案先只开放「文本 + 字号 + 颜色 + 字体」四项
|
||||||
|
- 暂不引入:页面文案的可视化编辑器、关于页拖拽布局
|
||||||
|
- 不做的抽象:通用「页面文案样式」DSL(只开放字号/颜色/字体三档白名单)
|
||||||
|
|
||||||
|
## 5. 技术决策
|
||||||
|
- 技术栈:Astro 7.4.4 + zod 单例 + GitHub GraphQL(已有 token)+ 现有 renderRich 富文本管线
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- 本轮允许改动:content/site/{opensource,pages}.yaml、src/content.config.ts、src/lib/{site-content,heatmap,projects}.ts、src/components/geek/{GithubProfile,Heatmap}.astro、src/components/pages/{MomentsPage,BooksPage,AboutPage}.astro、src/pages/{about,en/about,about-preview}.astro(新)、public/admin/config.yml、public/github-assets/(删蛇)、scripts/build-perf/fetch-readme-assets.mjs、docs/*、tests/e2e.mjs
|
||||||
|
- 本轮不应触碰:构建发布链主干、Artalk、灯箱、字体切片
|
||||||
|
- Git integration branch:`spec/2026-11-03_blog-5j-content-refine`
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:开源页文案模型与贡献数据、页面文案单例、关于页渲染通道
|
||||||
|
- 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查:`E2E_BASE=… node tests/e2e.mjs`(55 项)+ content-lint 0/0/0
|
||||||
|
- 全项目检查:生产实测四条(开源页无蛇、连续贡献与 GitHub 页面对账、页面文案改动生效、关于页预览 5 案可达);**升级条件**——仅当改动触及 BaseLayout/全局样式/Artalk 接管层时升级为全站视觉复核,本包均未触及,适用外
|
||||||
|
- 升级触发:若改动触及 BaseLayout、全局样式或 Artalk 接管层,升级为全站视觉复核;本包因新增页面级排版变量,需一轮关于页预览视觉复核
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] 开源页叙述为单段富文本且可内联链接,旧 yaml 零回归 -> verify: task_0001
|
||||||
|
- [x] 贡献蛇消失、连续贡献由本地计算并显示更新时间 -> verify: task_0002
|
||||||
|
- [x] 瞬间/书架标题下话语可从后台改,字号/颜色/字体生效 -> verify: task_0003
|
||||||
|
- [x] 关于页形态探索拆分为独立包 5k,不阻塞本包 -> (重划说明,无独立验证)
|
||||||
|
- [x] 文档与后台 hint 同步(无过时字段说明) -> verify: task_0005
|
||||||
|
|
||||||
|
## 7. 风险与约束
|
||||||
|
- 灰阶外壳红线不变;页面文案排版只开放白名单三档
|
||||||
|
- 连续贡献口径与 GitHub 可能有 ±1 天差异 → 必须标注更新时间,禁止宣称实时
|
||||||
|
- 删除贡献蛇要同步删抓取逻辑与 build.sh 守卫,避免死代码
|
||||||
|
- 多车道并行:`GithubProfile/Heatmap`(贡献数据)与 `MomentsPage/BooksPage`(页面文案)分属不同车道;admin config 与 docs 由 orchestrator 统一改
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Phase 5j:内容表达去重与页面文案可编辑 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
|
||||||
|
- 构建一律 `export PATH="$HOME/.local/node/bin:$PATH"`(Node 26)+ `pnpm astro build`
|
||||||
|
- E2E 口径:`pnpm astro build → cp -a dist /tmp/dist-e2e-5j → gz-server.py 18925 /tmp/dist-e2e-5j(第二参数才是目录)→ E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`(基线 55 项)
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] T1 开源页叙述合一(story 单字段富文本)
|
||||||
|
- claim: worker-one
|
||||||
|
- boundary: `content/site/opensource.yaml`(新增 `story`,保留 `blurb`/`blurb_after` 并标注 legacy)、`src/lib/site-content.ts`(`openSourceCopy` 增 `story`:非空则用,空则回落旧拼接)、`src/components/geek/GithubProfile.astro`(消费 story 的富文本渲染,链接用 markdown 内联 `[label](url)`)、`src/content.config.ts`(schema 加 story)、`public/admin/config.yml` 由 orchestrator 统一改(先做代码,字段我来加)
|
||||||
|
- verify: `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"`
|
||||||
|
|
||||||
|
- [x] T2 贡献数据去重与时效(删蛇 + 连续贡献本地计算)
|
||||||
|
- 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`(同步守卫)
|
||||||
|
- 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 页面文案可编辑(瞬间/书架标题下话语)
|
||||||
|
- 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/后台,车道可先写默认值)
|
||||||
|
- 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"`
|
||||||
|
|
||||||
|
> 注:关于页形态探索已拆分为独立包 `2026-11-03_blog-5k-about-designs`(2026-11-04 重划,用户选型驱动),不属于本包任务。
|
||||||
|
|
||||||
|
- [x] T5 文档与后台 hint
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: `docs/管理台.md`、`docs/写作指南.md`、`public/admin/config.yml`
|
||||||
|
- verify: `grep -q "story" docs/管理台.md && grep -q "pages.yaml\|页面文案" docs/管理台.md`
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Phase 5k:关于页形态探索(5 案预览 + 选型落地)- 项目范围 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-11-04 23:30
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- 五案共用同一份数据(about.yaml blocks),只是"结构与视觉"不同——切换成本=零内容迁移
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- T1 预览页五案
|
||||||
|
- T2 选型落地(manual: 等用户在预览页点选)
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简可行:预览页一次性路由 + 共用数据渲染五案;落地只搬选中方案的模板
|
||||||
|
- 暂不引入:新的后台字段(选 A 则零新字段;其它案沿用现有 blocks 字段)
|
||||||
|
- 不做的抽象:通用「关于页布局 DSL」(五案只是五种预设,落地只搬模板,不做可配置布局引擎)
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- 关于页内容改写(内容是用户的)、后台表单重构
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- 关于页内容改写(内容是用户的)、后台表单重构
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:1/1;manual 任务 1 项不计入分母
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-11-04 23:30
|
||||||
|
- 范围:Phase 5k:关于页形态探索(5 案预览 + 选型落地)- 项目范围
|
||||||
|
- 功能:T1 预览页五案;T2 选型落地(manual: 等用户在预览页点选)
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:129b23cd2b0ee2e4d677429fc7063c56b9ce62d8..4153c6d33f37ccb938108597496d94b1b599e926
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
Phase 5k 收官:关于页五案预览→用户选定 A→落地四级回落(doc → blocks → intro → 默认)+ 后台 markdown 字段置顶 + 预览路由退役。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{"task": "task_0001", "cmd": "! test -f src/pages/about-preview.astro && grep -q \"doc:\" content/site/about.yaml && 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.309, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4d028ab89a03b678fbd1828b163f34abb131e10f3176527477b4cdb6c6d4487b", "at": "2026-11-04T15:23:13+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "! test -f src/pages/about-preview.astro && grep -q \"doc:\" content/site/about.yaml && 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.261, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4d028ab89a03b678fbd1828b163f34abb131e10f3176527477b4cdb6c6d4487b", "at": "2026-11-04T15:23:46+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "! test -f src/pages/about-preview.astro && grep -q \"doc:\" content/site/about.yaml && 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.564, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4d028ab89a03b678fbd1828b163f34abb131e10f3176527477b4cdb6c6d4487b", "at": "2026-11-04T15:24:32+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "! test -f src/pages/about-preview.astro && grep -q \"doc:\" content/site/about.yaml && 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.031, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4d028ab89a03b678fbd1828b163f34abb131e10f3176527477b4cdb6c6d4487b", "at": "2026-11-04T15:25:31+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "! test -f src/pages/about-preview.astro && grep -q \"doc:\" content/site/about.yaml && 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.603, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4d028ab89a03b678fbd1828b163f34abb131e10f3176527477b4cdb6c6d4487b", "at": "2026-11-04T15:27:31+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "! test -f src/pages/about-preview.astro && grep -q \"doc:\" content/site/about.yaml && 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": 29.99, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4d028ab89a03b678fbd1828b163f34abb131e10f3176527477b4cdb6c6d4487b", "at": "2026-11-04T15:29:32+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "! test -f src/pages/about-preview.astro && grep -q \"doc:\" content/site/about.yaml && 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": 29.877, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "4d028ab89a03b678fbd1828b163f34abb131e10f3176527477b4cdb6c6d4487b", "at": "2026-11-04T15:30:02+00:00"}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Phase 5k:关于页形态探索(5 案预览 + 选型落地)- 项目范围
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:关于页从「blocks 块式结构」走向更自由的形态——给出 5 个完整设计(含"纯 markdown 单字段直编"这一极简形态),做成一页预览供用户点选;选定后把选中方案落进正式 /about。
|
||||||
|
- **目标用户**:站长(编辑自由度 vs 结构化表达的取舍)与读者(关于页的阅读体验)
|
||||||
|
- **核心价值**:关于页是"讲人"的页面,形态应该跟着内容气质走,而不是被固定结构绑住
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- 现状 = 5i 定稿的三层排版 + blocks 九类区块驱动(prose/tracks/linklist),内容:身份语(汇文+红)、兴趣版图三卡、关于我、找到我(linklist 5 条)、本站如何构建
|
||||||
|
- 用户在 5j 包中确认:关于页形态探索拆出独立包,含"纯 markdown 直编"方案
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- 五案共用同一份数据(about.yaml blocks),只是"结构与视觉"不同——切换成本=零内容迁移
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 五案选型:用户已选定 **A 白纸直编**(2026-11-04,预览页点选)
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- 选定方案若为"纯 markdown 直编",blocks 保留为高级通道(后台仍可切回)
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 无(本包是设计探索包)
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 预览页:/about-preview/ 五案并陈(A 白纸单栏 / B 名片双栏 / C 杂志分节带目录 / D 时间线主导 / E 卡片流),共享 about.yaml 真实数据
|
||||||
|
- [x] T2 用户选型后落地正式 /about 并删除预览路由
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- 关于页内容改写(内容是用户的)、后台表单重构
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简可行:预览页一次性路由 + 共用数据渲染五案;落地只搬选中方案的模板
|
||||||
|
- 暂不引入:新的后台字段(选 A 则零新字段;其它案沿用现有 blocks 字段)
|
||||||
|
- 不做的抽象:通用「关于页布局 DSL」(五案只是五种预设,落地只搬模板,不做可配置布局引擎)
|
||||||
|
|
||||||
|
## 5. 技术决策
|
||||||
|
- 技术栈:Astro 7.4.4 + 现有 renderRich/renderMarkdown 富文本管线 + iconPath 注册表
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- 本轮允许改动:src/pages/about-preview.astro(新,一次性)、src/components/pages/AboutPage.astro、content/site/about.yaml、scripts/build-perf/content-lint.mjs(预览路由白名单)、src/i18n/ui.ts
|
||||||
|
- 本轮不应触碰:BaseLayout、全局样式、其它页面
|
||||||
|
- Git integration branch:`spec/2026-11-04_blog-5k-about-designs`
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:关于页渲染通道(纯前端结构)
|
||||||
|
- 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查:E2E 56 项 + content-lint 0/0/0
|
||||||
|
- 全项目检查:生产预览页五案可达、选中案落地后 /about 渲染正确;**升级条件**——仅当关于页改动触及全局样式或 BaseLayout 时才升级全站复核,本包不涉及(由 /about 渲染人工核验覆盖)
|
||||||
|
- 升级触发:关于页为独立页面且不动全局样式,升级为「该页视觉复核」即可
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] 预览页五案可达且共用真实数据 -> verify: task_0001
|
||||||
|
- [x] 用户选定方案落地正式 /about,预览路由删除 -> verify: task_0002(manual: 需用户点选,落地后人工核验)
|
||||||
|
|
||||||
|
## 7. 风险与约束
|
||||||
|
- 灰阶骨架红线、双语红线不变
|
||||||
|
- 预览路由一次性:用户选定后必须删除(含 lint 白名单)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Phase 5k:关于页形态探索 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- E2E 口径:`pnpm astro build → cp -a dist /tmp/dist-e2e-5k → gz-server.py 18925 /tmp/dist-e2e-5k → E2E_BASE=… node tests/e2e.mjs`(基线 56 项)
|
||||||
|
- 预览路由一次性:选定后删除路由与 lint 白名单
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] T1 预览页五案
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: src/pages/about-preview.astro(新)、scripts/build-perf/content-lint.mjs(SKIP_ALL 加该路由)、content/site/about.yaml(若某案需要演示字段则补,双语)
|
||||||
|
- verify: `! test -f src/pages/about-preview.astro && grep -q "doc:" content/site/about.yaml && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: [0-9]+/[0-9]+ PASS"`(终态:预览路由已删、doc 已落地)
|
||||||
|
|
||||||
|
- [x] T2 选型落地(manual: 等用户在预览页点选)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: src/components/pages/AboutPage.astro、content/site/about.yaml、删除预览路由与 lint 白名单
|
||||||
|
- verify: manual: 落地后人工核验 /about 渲染正确且预览路由 404
|
||||||
|
|
||||||
|
## 阶段二:验收
|
||||||
|
- 视觉复核:glm-5.3-flash:max 看五案截图(≤5 图/轮)
|
||||||
|
- 用户在预览页点选后执行 T2
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# blog-5l:后台预览一致性 + 排版提醒 + 瞬间置顶/隐藏 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-11-05 10:03
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- 预览窗格是 CMS 内部管理的文档,官方 registerPreviewStyle 是唯一稳定注入口(MutationObserver 手注会被重渲染冲掉且升级易碎)
|
||||||
|
- 预览目标是「类效果可见」(颜色对、字体对、基础排版近似),不追求像素级还原
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- task_0001 预览样式注入
|
||||||
|
- task_0002 编辑提醒
|
||||||
|
- task_0003 瞬间置顶/隐藏
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简可行:一个新脚本 preview-style.js + 三处既有文件小改(typography-classes 加 css 字段、typography-editor pattern 放宽、config.yml hint/开关)+ moments 三点改(schema/MomentsPage/config.yml)+ 一条置顶示例数据 + E2E 断言更新
|
||||||
|
- 暂不引入:常驻样式速查面板、预览明暗主题切换
|
||||||
|
- 不做的抽象:通用「字段级 hint 生成器」(hint 手写三处即可,量小不值得抽)
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:后台静态资源(public/admin)与瞬间列表渲染(MomentsPage)
|
||||||
|
- 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查:E2E 59 项 + content-lint 0/0/0 + 产物 grep(moments 首卡 pin 徽标、admin 三脚本接线序)
|
||||||
|
- 全项目检查:生产部署后人工核验后台预览显色显字体;**升级条件**——仅当改动触及 BaseLayout 字体注入或 build 链时才升级全站回归,本包不涉及
|
||||||
|
- 升级触发:后台预览为独立资源面,升级为「后台人工核验」即可
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- Sveltia bundle 升级与魔改;预览像素级还原;moments 单条页面 / RSS / sitemap 变化
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- Sveltia bundle 升级与魔改;预览像素级还原;moments 单条页面 / RSS / sitemap 变化
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:3/3
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-11-05 10:03
|
||||||
|
- 范围:blog-5l:后台预览一致性 + 排版提醒 + 瞬间置顶/隐藏
|
||||||
|
- 功能:task_0001 预览样式注入;task_0002 编辑提醒;task_0003 瞬间置顶/隐藏
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:c5889f960b94a70c61b7c31d13e3e4be4cacaaf8..c5889f960b94a70c61b7c31d13e3e4be4cacaaf8
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
后台编辑体验三件套:预览窗格显色显字体(registerPreviewStyle 官方口子 + 切片字体按需取片)、编辑字段排版速查提醒、瞬间置顶/隐藏真字段(对齐 posts 契约)。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# task_0001 预览样式注入:三脚本语法 + 注入调用存在 + 接线顺序 + css 字段 + 多类 pattern
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
node --check public/admin/preview-style.js
|
||||||
|
node --check public/admin/typography-classes.js
|
||||||
|
node --check public/admin/typography-editor.js
|
||||||
|
grep -q "registerPreviewStyle" public/admin/preview-style.js
|
||||||
|
# index.html 接线序:typography-classes → typography-editor → preview-style(CMS 之后)
|
||||||
|
grep -q 'typography-classes.js"></script>' public/admin/index.html
|
||||||
|
grep -q 'preview-style.js"></script>' public/admin/index.html
|
||||||
|
[ "$(grep -c 'fonts.css" />' public/admin/index.html)" -ge 7 ]
|
||||||
|
# 清单带切片 CSS 字段 ×7(huiwen/xiaowei/huangyou/mashanzheng/liujianmaocao/smiley/neoxihei)
|
||||||
|
[ "$(grep -c "css: '/fonts/" public/admin/typography-classes.js)" -eq 7 ]
|
||||||
|
# pattern 放宽到多类(空格)
|
||||||
|
grep -q '\[a-z0-9- \]' public/admin/typography-editor.js
|
||||||
|
echo "verify_t1 OK"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# task_0002 编辑提醒:hint 速查 ≥3 处 + config-check 通过 + 文档节
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
export PATH="$HOME/.local/node/bin:$PATH"
|
||||||
|
python3 scripts/build-perf/config-check.py . | grep -q "全部通过"
|
||||||
|
[ "$(grep -c 'Insert ▸ 排版' public/admin/config.yml)" -ge 3 ]
|
||||||
|
grep -q "预览窗格直接显色/显字体" docs/管理台.md
|
||||||
|
echo "verify_t2 OK"
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# task_0003 瞬间置顶/隐藏:schema/排序过滤/后台开关/徽标/E2E
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
export PATH="$HOME/.local/node/bin:$PATH"
|
||||||
|
grep -q "pinned: z.boolean().optional()" src/content.config.ts
|
||||||
|
grep -q "hidden" src/components/pages/MomentsPage.astro
|
||||||
|
grep -q "name: pinned, widget: boolean" public/admin/config.yml
|
||||||
|
grep -q "name: hidden, widget: boolean" public/admin/config.yml
|
||||||
|
pnpm astro build >/dev/null 2>&1
|
||||||
|
grep -q 'class="pin"' dist/moments/index.html
|
||||||
|
nohup python3 scripts/build-perf/gz-server.py 18925 "$PWD/dist" >/tmp/gz-5l.log 2>&1 &
|
||||||
|
SRV=$!
|
||||||
|
sleep 2
|
||||||
|
trap 'kill $SRV 2>/dev/null || true' EXIT
|
||||||
|
E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -q "59/59 PASS"
|
||||||
|
echo "verify_t3 OK"
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.105, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:48:36+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.188, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:48:37+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 40.484, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:49:17+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.103, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:49:26+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.156, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:49:26+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 42.065, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:50:08+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.109, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:51:36+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.159, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:51:36+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 48.822, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:52:25+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.126, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:53:49+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.141, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:53:50+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 45.824, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:54:35+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.11, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:54:36+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.141, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:54:36+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 46.701, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:55:22+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.11, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:55:33+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.142, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:55:33+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 42.886, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:56:16+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.104, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:57:47+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.156, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:57:47+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 42.104, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:58:29+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.102, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:58:46+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.14, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:58:46+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 41.33, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:59:27+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.099, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:59:39+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.139, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T01:59:39+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 44.724, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T02:00:24+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.114, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T02:01:53+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.14, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T02:01:53+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 46.842, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T02:02:40+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh", "exit": 0, "duration_s": 0.125, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T02:02:40+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.172, "stdout_tail_sha256": "10c2f11b3513c46a1043b5e8b93b6aa4fd34f6342f13b4560f17d9adaa0b16b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T02:02:40+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh", "exit": 0, "duration_s": 44.882, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "afa04d6db968b3150e68927e061bf0186a1f047da67f4450015da1691769916e", "at": "2026-11-05T02:03:25+00:00"}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# blog-5l:后台预览一致性 + 排版提醒 + 瞬间置顶/隐藏
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
- **slug**: 2026-11-05_blog-5l-admin-preview-moments
|
||||||
|
- **日期**: 2026-11-05
|
||||||
|
- **状态**: 进行中
|
||||||
|
- Git integration branch:`spec/2026-11-05_blog-5l-admin-preview-moments`
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- **提案**: 后台(Sveltia CMS)编辑 markdown 时的预览窗格不渲染排版类(font- 字体、c-/ink- 颜色看不到效果),且编辑时缺少「能用哪些字体/颜色」的提醒;瞬间(moments)需要与文章一致的置顶/隐藏能力。三件事合成一包:预览注入站点排版样式(官方 `CMS.registerPreviewStyle`)、hint/文档提醒、moments pinned/hidden 真字段契约。
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:后台编辑 markdown 时预览窗格直接显色/显字体(与真实页面一致),编辑时能看到可用字体/颜色的提醒;瞬间获得与文章一致的置顶/隐藏能力。
|
||||||
|
- **目标用户**:站长(编辑体验与内容管理);读者不受影响(仅瞬间排序/可见性语义对齐)
|
||||||
|
- **核心价值**:所见即所得消灭「写完发布才发现样式不对」的往返;moments 与 posts 的管理语义统一,降低心智负担
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- Sveltia 0.252.0(本地 pinned bundle)含 `registerPreviewStyle`(raw 字符串/URL 两态,支持 collection 作用域)——bundle 内已核实校验逻辑
|
||||||
|
- 已有「排版」inline 编辑器组件(Insert ▸ 排版,typography-classes.js 清单 + 自建选择面板:字体实样 + 色卡)
|
||||||
|
- posts 已有 pinned/hidden 真字段 + post-visibility 单一事实源;moments 排序在 MomentsPage.astro 内联(唯一消费点)
|
||||||
|
- 自托管字体切片落 public/fonts/<family>/fonts.css(unicode-range 按需取片),路径稳定可被后台引用
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- 预览窗格是 CMS 内部管理的文档,官方 registerPreviewStyle 是唯一稳定注入口(MutationObserver 手注会被重渲染冲掉且升级易碎)
|
||||||
|
- 预览目标是「类效果可见」(颜色对、字体对、基础排版近似),不追求像素级还原
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 预览注入走官方 registerPreviewStyle,不走 iframe hack(0.252.0 已核实存在)
|
||||||
|
- [x] 真字体预览走切片 CSS(unicode-range 按需取片),不在后台整包加载 TTF
|
||||||
|
- [x] moments 置顶多选允许(与书架/文章口径一致);隐藏 = 真字段过滤,无 legacy tag 兼容
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- 提醒做成 hint 速查 + 已有排版面板的入口提示,不做常驻侧栏文档组件(Sveltia 定制面有限,hint 是官方支持的展示位)
|
||||||
|
- serif/kai/kuaile 的 CSS 经 Vite ?url 打包成哈希名,后台引不到稳定 URL,预览走字体栈系统回退(近似可见,文档注明差异)
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 往预览 iframe 手动注入 `<style>`:预览文档由 CMS 内部管理,注入会被重渲染冲掉、升级易碎——官方 API 存在,必须走官方口子
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 预览一致性:preview-style.js 从清单运行时生成排版类 CSS + 富文本基础排版(raw 注入)+ 自托管艺术字体切片 CSS(URL 注入);pattern 放宽到多类
|
||||||
|
- [x] T2 编辑提醒:posts 正文 / moments 文字 / 关于页整页正文三处 hint 补速查;docs/管理台.md 增「预览窗格直接显色/显字体」节
|
||||||
|
- [x] T3 瞬间置顶/隐藏:schema 真字段 + 列表过滤排序 + 灰阶 pin 徽标 + 后台开关 + E2E 断言
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- Sveltia bundle 升级与魔改;预览像素级还原;moments 单条页面 / RSS / sitemap 变化
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简可行:一个新脚本 preview-style.js + 三处既有文件小改(typography-classes 加 css 字段、typography-editor pattern 放宽、config.yml hint/开关)+ moments 三点改(schema/MomentsPage/config.yml)+ 一条置顶示例数据 + E2E 断言更新
|
||||||
|
- 暂不引入:常驻样式速查面板、预览明暗主题切换
|
||||||
|
- 不做的抽象:通用「字段级 hint 生成器」(hint 手写三处即可,量小不值得抽)
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:后台静态资源(public/admin)与瞬间列表渲染(MomentsPage)
|
||||||
|
- 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查:E2E 59 项 + content-lint 0/0/0 + 产物 grep(moments 首卡 pin 徽标、admin 三脚本接线序)
|
||||||
|
- 全项目检查:生产部署后人工核验后台预览显色显字体;**升级条件**——仅当改动触及 BaseLayout 字体注入或 build 链时才升级全站回归,本包不涉及
|
||||||
|
- 升级触发:后台预览为独立资源面,升级为「后台人工核验」即可
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] 后台预览排版类显色/显字体 -> verify: task_0001(manual: 生产后台人工核验,代码面由 task_0001 断言覆盖)
|
||||||
|
- [x] 编辑字段 hint 含字体/颜色速查与排版面板入口 -> verify: task_0002
|
||||||
|
- [x] moments 置顶排序 + hidden 过滤 + 后台开关 + pin 徽标 + E2E 全绿 -> verify: task_0003
|
||||||
|
|
||||||
|
## 7. 关联与风险
|
||||||
|
- 前序:5h T1(posts 置顶)、5j T3(页面文案 hint 体系)、5i T9(字体切片管线)
|
||||||
|
- 风险:registerPreviewStyle 的 URL 模式对预览文档的 base URL 解析若与预期不符,退路是把切片 CSS 也 fetch 成文本走 raw 注入(同一 URL,语义等价)
|
||||||
|
- 本包不动:热力图(另线预览选型中)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Phase 5l:后台预览一致性 + 排版提醒 + 瞬间置顶隐藏 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- E2E 口径:`pnpm astro build → gz-server.py 18925 <dist 绝对路径> → E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`(基线 59 项)
|
||||||
|
- 后台三脚本加载序固定:typography-classes → typography-editor → preview-style(CMS 之后、init 之前)
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] task_0001 预览样式注入
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: public/admin/preview-style.js(新)、public/admin/typography-classes.js(css 字段)、public/admin/typography-editor.js(pattern)、public/admin/index.html(接线)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t1.sh`
|
||||||
|
|
||||||
|
- [x] task_0002 编辑提醒
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: public/admin/config.yml(三处 hint)、docs/管理台.md(新节)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t2.sh`
|
||||||
|
|
||||||
|
- [x] task_0003 瞬间置顶/隐藏
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: src/content.config.ts(moments schema)、src/components/pages/MomentsPage.astro、public/admin/config.yml(开关)、content/moments/2026-11-02.md(置顶示例)、tests/e2e.mjs(断言)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5l-admin-preview-moments/evidence/verify_t3.sh`
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# blog-5m:热力图定稿萤火(4 只)落地正式开源页 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-11-05 11:21
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- 月柱在无「点亮联动」后仍有信息价值(静态高度=哪个月最忙),保留不删
|
||||||
|
- 萤火引擎的 offsetLeft/offsetTop 定位对官方组件的 11px/10px 双档网格同样成立(布局实测,零手算节距)
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- task_0001 萤火落地与预览退役
|
||||||
|
- task_0002 生产核验(manual: 部署后人工核验)
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简可行:Heatmap.astro 三处手术(浮层标记换 4 只 hm-fly、script 换萤火引擎、style 换 hm-fly/hm-lit+月柱静态)+ 删三类一次性资产 + E2E 一条断言
|
||||||
|
- 暂不引入: 虫数可配置化
|
||||||
|
- 不做的抽象: 「动效引擎注册表」(只有一种引擎,抽象无收益)
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:开源页热力图组件与一次性预览资产
|
||||||
|
- 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查:E2E(含萤火新断言)+ content-lint 0/0/0 + grep 断言产物(无 heatmap-preview、无 hm-head、4 只 hm-fly)
|
||||||
|
- 全项目检查:生产部署后人工核验 /opensource/ 萤火在跑、/heatmap-preview/ 404;**升级条件**——仅当改动触及 BaseLayout 或构建链时才升级全站回归,本包不涉及
|
||||||
|
- 升级触发:开源页独立区块,升级为「该区块人工核验」即可
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- 萤火参数再调(数量/速度/半径——用户未提,不为未发生的需求加工)
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- 萤火参数再调(数量/速度/半径——用户未提,不为未发生的需求加工)
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:1/1;manual 任务 1 项不计入分母
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-11-05 11:21
|
||||||
|
- 范围:blog-5m:热力图定稿萤火(4 只)落地正式开源页
|
||||||
|
- 功能:task_0001 萤火落地与预览退役;task_0002 生产核验(manual: 部署后人工核验)
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:44cd62e9a2a5fc0f400de2862a3a07c5686bf25e..44cd62e9a2a5fc0f400de2862a3a07c5686bf25e
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
热力图 v5 选型闭环:用户在五案预览页选定萤火并指定虫数 4——引擎落进正式开源页(贪吃蛇与月柱点亮联动退役,月柱转静态),预览路由/五候选组件/lint 白名单全部退役,E2E 补萤火断言(61/61)。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# task_0001 萤火落地与预览退役:构建 + 产物断言 + lint + E2E
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
export PATH="$HOME/.local/node/bin:$PATH"
|
||||||
|
pnpm astro build >/dev/null 2>&1
|
||||||
|
[ ! -f src/pages/heatmap-preview.astro ]
|
||||||
|
[ ! -d src/components/geek/hm ]
|
||||||
|
! grep -q heatmap-preview scripts/build-perf/content-lint.mjs
|
||||||
|
! grep -q hm-head src/components/geek/Heatmap.astro
|
||||||
|
[ "$(grep -o 'class="hm-fly"' dist/opensource/index.html | wc -l)" -eq 4 ]
|
||||||
|
node scripts/build-perf/content-lint.mjs dist 2>&1 | grep -q "通过"
|
||||||
|
nohup python3 scripts/build-perf/gz-server.py 18925 "$PWD/dist" >/tmp/gz-5m.log 2>&1 &
|
||||||
|
SRV=$!
|
||||||
|
sleep 2
|
||||||
|
trap 'kill $SRV 2>/dev/null || true' EXIT
|
||||||
|
E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -qE "汇总: 61/61 PASS"
|
||||||
|
echo "verify_t1 OK"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5m-heatmap-fireflies/evidence/verify_t1.sh", "exit": 0, "duration_s": 44.525, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "e364e39e9d06d1dcfe55ea481995d132f94854ed078cb251c9fa4fa9a6af1202", "at": "2026-11-05T03:21:15+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5m-heatmap-fireflies/evidence/verify_t1.sh", "exit": 0, "duration_s": 43.513, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "e364e39e9d06d1dcfe55ea481995d132f94854ed078cb251c9fa4fa9a6af1202", "at": "2026-11-05T03:21:58+00:00"}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<!-- spec-format: 2 -->
|
||||||
|
# blog-5m:热力图定稿萤火(4 只)落地正式开源页
|
||||||
|
|
||||||
|
- **slug**: 2026-11-05_blog-5m-heatmap-fireflies
|
||||||
|
- **日期**: 2026-11-05
|
||||||
|
- **状态**: 进行中
|
||||||
|
- Git integration branch:`spec/2026-11-05_blog-5m-heatmap-fireflies`
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- **提案**: 用户在 /heatmap-preview/ 五案中选定方案五「萤火」并要求虫数 5→4。把萤火引擎落进正式 Heatmap.astro(换下 P1 贪吃蛇),删预览路由/五候选组件/lint 白名单,E2E 补萤火断言。
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**: 开源页热力图动效从贪吃蛇切换为萤火(4 只,暖色游走、落格点亮),保持数据语义(ink 档位=密度)与无障碍兜底(reduced-motion 静态、无 JS 可读)不变;一次性预览资产全部退役。
|
||||||
|
- **目标用户**: 读者(更安静文气的动效);站长(选定方案的落地闭环)
|
||||||
|
- **核心价值**: 选型闭环——预览页五案并陈→用户点选→落地删预览的工作流第三次跑通,站点只留定稿资产
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- 萤火引擎已在预览页跑通(/heatmap-preview/ 方案五),视觉复核判词无硬伤、色向已转暖
|
||||||
|
- 官方 Heatmap.astro 现役结构:streak 行 + 蛇引擎(hm-head/hm-trail/eaten + 月柱 .on 联动)+ 月柱 + 图例 + 横滚锚定
|
||||||
|
- E2E 无蛇头断言(仅注释),补萤火断言无旧断言要改
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- 月柱在无「点亮联动」后仍有信息价值(静态高度=哪个月最忙),保留不删
|
||||||
|
- 萤火引擎的 offsetLeft/offsetTop 定位对官方组件的 11px/10px 双档网格同样成立(布局实测,零手算节距)
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 虫数 4(用户明确:5→4)
|
||||||
|
- [x] 贪吃蛇整体退役(用户选新方案即换下,git 历史留档)
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- 五个候选组件目录整体删除而非保留:git 历史即档案,工作树不留死代码
|
||||||
|
- 月柱保留静态形态:删掉会丢「月度粗粒度」信息,联动点亮属蛇的语义,随蛇退役
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 无(本包是纯落地,无新设计探索)
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 萤火落地:Heatmap.astro 换引擎(4 虫、data-hm-fire 锚、hm-lit 样式、月柱转静态)、删预览路由+五候选组件+lint 白名单两处、E2E 补萤火断言
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- 萤火参数再调(数量/速度/半径——用户未提,不为未发生的需求加工)
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简可行:Heatmap.astro 三处手术(浮层标记换 4 只 hm-fly、script 换萤火引擎、style 换 hm-fly/hm-lit+月柱静态)+ 删三类一次性资产 + E2E 一条断言
|
||||||
|
- 暂不引入: 虫数可配置化
|
||||||
|
- 不做的抽象: 「动效引擎注册表」(只有一种引擎,抽象无收益)
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:开源页热力图组件与一次性预览资产
|
||||||
|
- 快速检查:`pnpm astro build` + `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查:E2E(含萤火新断言)+ content-lint 0/0/0 + grep 断言产物(无 heatmap-preview、无 hm-head、4 只 hm-fly)
|
||||||
|
- 全项目检查:生产部署后人工核验 /opensource/ 萤火在跑、/heatmap-preview/ 404;**升级条件**——仅当改动触及 BaseLayout 或构建链时才升级全站回归,本包不涉及
|
||||||
|
- 升级触发:开源页独立区块,升级为「该区块人工核验」即可
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] 正式开源页萤火 4 只在跑、预览资产全退役 -> verify: task_0001
|
||||||
|
- [x] 生产 /opensource/ 人工核验 + /heatmap-preview/ 404 -> verify: task_0002(manual: 部署后人工核验)
|
||||||
|
|
||||||
|
## 7. 关联与风险
|
||||||
|
- 前序:v4 路径优化(P1 蛇)、v5 五案预览(44cd62e)
|
||||||
|
- 风险:萤火引擎在官方组件的 11px 移动档网格上包围盒内缩 6px 后可用幅度变小——若视觉过挤,回退方案是内缩降为 3px(本包不预设,部署后看)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Phase 5m:热力图萤火落地 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- E2E 口径:`pnpm astro build → gz-server.py 18925 <dist 绝对路径> → E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] task_0001 萤火落地与预览退役
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: src/components/geek/Heatmap.astro(换引擎)、tests/e2e.mjs(断言)、删除 src/pages/heatmap-preview.astro、删除 src/components/geek/hm/、scripts/build-perf/content-lint.mjs(白名单两处退役)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5m-heatmap-fireflies/evidence/verify_t1.sh`
|
||||||
|
|
||||||
|
- [x] task_0002 生产核验(manual: 部署后人工核验)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: 无代码改动;生产 /opensource/ 萤火 4 只在跑 + /heatmap-preview/ 404
|
||||||
|
- verify: manual: 部署后人工核验
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# blog-5n:后台深度优化全家桶(14 项全量) - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-11-05 20:12
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- Sveltia 0.259 与 0.252 对本站用到的 API(registerEditorComponent/registerPreviewStyle/config schema)兼容(升级后全量回归验证)
|
||||||
|
- TMT 翻译单篇耗时 < 构建容忍度(守卫超时 180s,失败不阻断部署、保留旧英文版)
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- task_0001 Sveltia 0.259 升级与回归
|
||||||
|
- task_0002 A1 翻译进构建链
|
||||||
|
- task_0003 A2+A3+A4+C3 表单优化批
|
||||||
|
- task_0004 B1+B2+D1+E1 后台页面批
|
||||||
|
- task_0005 C1 首页设置分组
|
||||||
|
- task_0006 C2 内容仪表盘
|
||||||
|
- task_0007 暗色预览
|
||||||
|
- task_0008 媒体库归置
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简可行:新文件优先(守卫脚本/状态条/统计脚本/manifest)+ 共享文件主会话三批统一改(build.sh/config.yml/index.html)+ 升级先行(后续工作全部在新版上验证)
|
||||||
|
- 暂不引入: 自定义 Sveltia widget 开发、翻译并发加速
|
||||||
|
- 不做的抽象: 后台微前端/工具页框架(静态页够用)
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象: public/admin 全域 + build.sh + content/site schema + 文档
|
||||||
|
- 快速检查: `pnpm astro build` + `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查: E2E 61/61 + content-lint 0/0/0 + admin 页加载零控制台错误(playwright 预登录态)+ build-status.json 产物断言 + 翻译守卫 dry-run
|
||||||
|
- 全项目检查: 部署后人工核验后台(登录态表单可编辑/状态条/速通坞/暗色预览);**升级条件**——仅当 Sveltia 升级引致行为差异时升级全站回归,本包已含升级后 E2E 全跑
|
||||||
|
- 升级触发: admin 为独立资源面,升级为「后台人工核验」即可
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- 照片摄取管线目录迁移;TMT 凭据变更;评论系统本身
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- 照片摄取管线目录迁移;TMT 凭据变更;评论系统本身
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:8/8
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-11-05 20:12
|
||||||
|
- 范围:blog-5n:后台深度优化全家桶(14 项全量)
|
||||||
|
- 功能:task_0001 Sveltia 0.259 升级与回归;task_0002 A1 翻译进构建链;task_0003 A2+A3+A4+C3 表单优化批;task_0004 B1+B2+D1+E1 后台页面批;task_0005 C1 首页设置分组;task_0006 C2 内容仪表盘;task_0007 暗色预览;task_0008 媒体库归置
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:a555a3e450cd26a8e53f6ea86009c211b981cce8..a555a3e450cd26a8e53f6ea86009c211b981cce8
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
后台深度优化全家桶 14 项全量落地:Sveltia 0.259 升级、TMT 翻译进构建链(保存中文即自动出英文)、标签/模板/SEO/中英表单四优化、部署状态条+PWA+速通坞+回滚指引、首页设置分组(Sveltia 嵌套雷区规避 B 案)、内容仪表盘、暗色预览、媒体库归置。多队友协同:@worker-one 领单未达由主会话兜底、4 子代理并行交付(其中 C1 子代理识别出与上午热修的嵌套雷区冲突请示拍板,选 B 案压平)。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
export PATH="$HOME/.local/node/bin:$PATH"
|
||||||
|
grep -q '0\.259\.0' public/admin/sveltia-cms.js
|
||||||
|
node --check public/admin/sveltia-cms.js
|
||||||
|
grep -c "registerPreviewStyle" public/admin/sveltia-cms.js >/dev/null
|
||||||
|
pnpm astro build >/dev/null 2>&1
|
||||||
|
echo "verify_t1 OK"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
[ -x scripts/deploy/translate-step.sh ]
|
||||||
|
grep -q "translate-step" infra/deploy/build.sh
|
||||||
|
grep -q "build-status.json" infra/deploy/build.sh
|
||||||
|
grep -q "trap 'write_status fail'" infra/deploy/build.sh
|
||||||
|
bash scripts/deploy/translate-step.sh --dry-run
|
||||||
|
echo "verify_t2 OK"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
export PATH="$HOME/.local/node/bin:$PATH"
|
||||||
|
python3 scripts/build-perf/config-check.py . | grep -q 全部通过
|
||||||
|
grep -q "≤32 字" public/admin/config.yml
|
||||||
|
grep -q "60–120 字" public/admin/config.yml
|
||||||
|
grep -q "避免同义新造" public/admin/config.yml
|
||||||
|
grep -q "写作模板》复制起手" public/admin/config.yml
|
||||||
|
grep -q "英文版构建时自动机翻" public/admin/config.yml
|
||||||
|
echo "verify_t3 OK"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
node --check public/admin/deploy-status.js
|
||||||
|
grep -q "deploy-status.js" public/admin/index.html
|
||||||
|
grep -q "quick-dock" public/admin/index.html
|
||||||
|
grep -q "manifest.webmanifest" public/admin/index.html
|
||||||
|
grep -q "favicon.svg" public/admin/index.html
|
||||||
|
[ -f docs/后台回滚指引.md ]
|
||||||
|
grep -q "一句话版本" docs/后台回滚指引.md
|
||||||
|
echo "verify_t4 OK"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
export PATH="$HOME/.local/node/bin:$PATH"
|
||||||
|
pnpm astro build >/dev/null 2>&1
|
||||||
|
grep -q "tagline\|avatar" dist/index.html
|
||||||
|
grep -q "identity\|socials" src/content.config.ts
|
||||||
|
echo "verify_t5 OK"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
export PATH="$HOME/.local/node/bin:$PATH"
|
||||||
|
pnpm astro build >/dev/null 2>&1
|
||||||
|
python3 scripts/build-perf/gen-admin-stats.py dist
|
||||||
|
[ -f dist/admin/stats.html ]
|
||||||
|
python3 -c "import json;d=json.load(open('dist/admin/stats.json'));assert d['posts_visible']>0 and 'top_tags' in d"
|
||||||
|
echo "verify_t6 OK"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
node --check public/admin/preview-style.js
|
||||||
|
grep -q "prefers-color-scheme: dark" public/admin/preview-style.js
|
||||||
|
python3 -c "import json;m=json.load(open('public/admin/manifest.webmanifest'));assert m['start_url']=='/admin/' and len(m['icons'])==2"
|
||||||
|
echo "verify_t7 OK"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
export PATH="$HOME/.local/node/bin:$PATH"
|
||||||
|
python3 scripts/build-perf/config-check.py . | grep -q 全部通过
|
||||||
|
grep -q "上传后构建自动优化" public/admin/config.yml
|
||||||
|
grep -q "媒体" docs/管理台.md
|
||||||
|
echo "verify_t8 OK"
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t1.sh", "exit": 0, "duration_s": 31.176, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:10:50+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.116, "stdout_tail_sha256": "a539567e2a1bad8a450d90a3998841921d5f353cdf8a13cd1dd9499117f71287", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:10:51+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t3.sh", "exit": 0, "duration_s": 0.15, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:10:51+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t4.sh", "exit": 0, "duration_s": 0.048, "stdout_tail_sha256": "8338018e17e2e8b7c9c6aeb8468f164497a332d7b9fbe69a027eec2f3eed2dab", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:10:51+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t5.sh", "exit": 0, "duration_s": 21.431, "stdout_tail_sha256": "99a2c86534e476be7ff3365a14ce028321c3efe5f6849f7831d8c5fe5fc4e6e2", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:11:12+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t6.sh", "exit": 0, "duration_s": 21.644, "stdout_tail_sha256": "625069f2ee6d8e41eb00af14da68816cbe639979296adc0375ffc20780fadf19", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:11:34+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t7.sh", "exit": 0, "duration_s": 0.06, "stdout_tail_sha256": "6d54a7fb09f6d6b7db49450f842bd397759f0aede9ed53b6e2edb0aa67271f6b", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:11:34+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t8.sh", "exit": 0, "duration_s": 0.205, "stdout_tail_sha256": "2f95596c0c69d4a3e9e5e800876d2007624acea62f90b35243c0dc3aa3f737c8", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:11:35+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t1.sh", "exit": 0, "duration_s": 23.758, "stdout_tail_sha256": "cced3c08cf1601b8177b7728e48c6add6982099d0785fa9fa4dd470b1f4900a5", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:12:11+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t2.sh", "exit": 0, "duration_s": 0.083, "stdout_tail_sha256": "a539567e2a1bad8a450d90a3998841921d5f353cdf8a13cd1dd9499117f71287", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:12:11+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t3.sh", "exit": 0, "duration_s": 0.146, "stdout_tail_sha256": "409ed685a0bde8695328c75cd1e92fb1e54398e22f911a252c1385f2262db9b3", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:12:11+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t4.sh", "exit": 0, "duration_s": 0.043, "stdout_tail_sha256": "8338018e17e2e8b7c9c6aeb8468f164497a332d7b9fbe69a027eec2f3eed2dab", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:12:11+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t5.sh", "exit": 0, "duration_s": 16.974, "stdout_tail_sha256": "99a2c86534e476be7ff3365a14ce028321c3efe5f6849f7831d8c5fe5fc4e6e2", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:12:28+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t6.sh", "exit": 0, "duration_s": 28.626, "stdout_tail_sha256": "625069f2ee6d8e41eb00af14da68816cbe639979296adc0375ffc20780fadf19", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:12:57+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t7.sh", "exit": 0, "duration_s": 0.062, "stdout_tail_sha256": "6d54a7fb09f6d6b7db49450f842bd397759f0aede9ed53b6e2edb0aa67271f6b", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:12:57+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t8.sh", "exit": 0, "duration_s": 0.172, "stdout_tail_sha256": "2f95596c0c69d4a3e9e5e800876d2007624acea62f90b35243c0dc3aa3f737c8", "tree_fp": "85e5901717ba7746d406493c34a028b8314068453ac38a6f6bfb3606f10c66a8", "at": "2026-11-05T12:12:57+00:00"}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<!-- spec-format: 2 -->
|
||||||
|
# blog-5n:后台深度优化全家桶(14 项全量)
|
||||||
|
|
||||||
|
- **slug**: 2026-11-05_blog-5n-admin-deep-optimization
|
||||||
|
- **日期**: 2026-11-05
|
||||||
|
- **状态**: 进行中
|
||||||
|
- Git integration branch:`spec/2026-11-05_blog-5n-admin-deep-optimization`
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- **提案**: 用户拍板「全部都做包括暂缓的」:写作链路(A1 翻译进构建链/A2 标签防手滑/A3 起手模板/A4 SEO 提示)、发布反馈(B1 部署状态条/B2 favicon+PWA)、内容管理(C1 首页设置分组/C2 内容仪表盘/C3 中英对照提示)、工具入口(D1 速通坞)、安全网(E1 回滚指引)、暂缓档转正(Sveltia 0.252→0.259 升级、暗色预览、媒体库归置)。多队友协同:@worker-one + 4 子代理 + 主会话集成。
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**: 后台从「功能齐」到「全程顺手」——写作(双语零操作/标签不散/起手不空)、发布(状态可见/入口收拢/回滚有指引)、管理(分组/统计/对照)三条线一步到位;底层 Sveltia 升级到 0.259.0 并全量回归。
|
||||||
|
- **目标用户**: 站长(唯一编辑者,高频写作与内容管理)
|
||||||
|
- **核心价值**: 把这一周散落的「后台不好用」类反馈一次性清零;英文站从手动操作变全自动。
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- TMT 翻译脚本 translate_posts.py 幂等 + locked 保护,可直接挂构建链
|
||||||
|
- Sveltia 最新 0.259.0(registry 核实);本站 bundle 0.252.0 pinned 本地
|
||||||
|
- Artalk 评论管理在 https://wait4x.online/artalk;rollback 脚本在 infra/deploy/ 已存在
|
||||||
|
- build.sh 为部署链核心(A1/B1/C2 三项都挂它,由主会话统一改避免竞态)
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- Sveltia 0.259 与 0.252 对本站用到的 API(registerEditorComponent/registerPreviewStyle/config schema)兼容(升级后全量回归验证)
|
||||||
|
- TMT 翻译单篇耗时 < 构建容忍度(守卫超时 180s,失败不阻断部署、保留旧英文版)
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 全量范围:用户明确「全部都做包括暂缓的」
|
||||||
|
- [x] 协同分工:@worker-one 独立文件 ×2、子代理 ×4(新文件/独立文件)、共享文件(config.yml/index.html/build.sh)主会话独占
|
||||||
|
- [x] 媒体库归置取低风险形态:照片目录不动(摄取管线契约),归置=上传落点核对+文档+hints
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- A2 标签防手滑:若 Sveltia select 无 multiple 则退 list+常用标签 hint(不引自定义 widget)
|
||||||
|
- A3 起手模板:利用现有隐藏「写作模板」+ Sveltia 条目复制能力 + hint 指引(不建模板机制)
|
||||||
|
- C1 分组:仅改 home 单例形状(schema+yaml+消费端+后台表单),不动其他单例
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 无(新增包)
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 Sveltia 0.259 升级 + 全量回归(三脚本注册/预览/集合加载)
|
||||||
|
- [x] T2 A1 翻译进构建链(translate-step 守卫 + build.sh 挂载)
|
||||||
|
- [x] T3 A2+A3+A4+C3 表单优化批(config.yml:标签建议/模板指引/SEO 提示/中英对照)
|
||||||
|
- [x] T4 B1+B2+D1+E1 后台页面批(状态条/favicon/PWA/速通坞/回滚指引)
|
||||||
|
- [x] T5 C1 首页设置分组(schema 迁移 + 消费端 + 表单)
|
||||||
|
- [x] T6 C2 内容仪表盘(构建期统计 + 静态页)
|
||||||
|
- [x] T7 暗色预览(preview-style.js 双主题)
|
||||||
|
- [x] T8 媒体库归置(落点核对 + hints + 文档)
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- 照片摄取管线目录迁移;TMT 凭据变更;评论系统本身
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简可行:新文件优先(守卫脚本/状态条/统计脚本/manifest)+ 共享文件主会话三批统一改(build.sh/config.yml/index.html)+ 升级先行(后续工作全部在新版上验证)
|
||||||
|
- 暂不引入: 自定义 Sveltia widget 开发、翻译并发加速
|
||||||
|
- 不做的抽象: 后台微前端/工具页框架(静态页够用)
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象: public/admin 全域 + build.sh + content/site schema + 文档
|
||||||
|
- 快速检查: `pnpm astro build` + `python3 scripts/build-perf/config-check.py .`
|
||||||
|
- 集成检查: E2E 61/61 + content-lint 0/0/0 + admin 页加载零控制台错误(playwright 预登录态)+ build-status.json 产物断言 + 翻译守卫 dry-run
|
||||||
|
- 全项目检查: 部署后人工核验后台(登录态表单可编辑/状态条/速通坞/暗色预览);**升级条件**——仅当 Sveltia 升级引致行为差异时升级全站回归,本包已含升级后 E2E 全跑
|
||||||
|
- 升级触发: admin 为独立资源面,升级为「后台人工核验」即可
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] Sveltia 0.259 上线且三脚本/预览/表单全可用 -> verify: task_0001
|
||||||
|
- [x] 中文保存→英文自动出(守卫不阻断)-> verify: task_0002
|
||||||
|
- [x] 表单四优化与页面四件套生效 -> verify: task_0003 + task_0004
|
||||||
|
- [x] 首页分组/仪表盘/暗色预览/媒体归置落地 -> verify: task_0005..task_0008
|
||||||
|
|
||||||
|
## 7. 关联与风险
|
||||||
|
- 最大风险 = Sveltia 升级破坏现有定制(三脚本 API 面);缓解:升级后立即 playwright 冒烟 + 生产灰度观察,异常即回滚 bundle(git revert 单文件)
|
||||||
|
- 翻译挂链风险 = TMT 配额/网络抖动;缓解:守卫永不阻断 + last-good 英文版保留
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Phase 5n:后台深度优化全家桶 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- 共享文件(public/admin/config.yml、public/admin/index.html、infra/deploy/build.sh)仅主会话编辑,队友产出片段/新文件
|
||||||
|
- E2E 口径:`pnpm astro build → gz-server.py 18925 <dist> → E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs`(基线 61)
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] task_0001 Sveltia 0.259 升级与回归
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: public/admin/sveltia-cms.js(bundle 置换)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t1.sh`
|
||||||
|
|
||||||
|
- [x] task_0002 A1 翻译进构建链
|
||||||
|
- claim: orchestrator + worker-one
|
||||||
|
- boundary: scripts/deploy/translate-step.sh(新,worker-one)、infra/deploy/build.sh(主会话挂载)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t2.sh`
|
||||||
|
|
||||||
|
- [x] task_0003 A2+A3+A4+C3 表单优化批
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: public/admin/config.yml(标签/模板指引/SEO/中英 hint)、content/posts/writing-template.md
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t3.sh`
|
||||||
|
|
||||||
|
- [x] task_0004 B1+B2+D1+E1 后台页面批
|
||||||
|
- claim: orchestrator + worker-one + 子代理
|
||||||
|
- boundary: public/admin/deploy-status.js(新,子代理)、public/admin/manifest.webmanifest(新,子代理)、docs/后台回滚指引.md(新,worker-one)、public/admin/index.html(主会话接线)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t4.sh`
|
||||||
|
|
||||||
|
- [x] task_0005 C1 首页设置分组
|
||||||
|
- claim: orchestrator + 子代理
|
||||||
|
- boundary: src/content.config.ts(siteHome schema)、content/site/home.yaml、src/lib/site-content.ts(消费端)、public/admin/config.yml(home 条目,主会话拼装子代理片段)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t5.sh`
|
||||||
|
|
||||||
|
- [x] task_0006 C2 内容仪表盘
|
||||||
|
- claim: 子代理
|
||||||
|
- boundary: scripts/build-perf/gen-admin-stats.mjs(新)、infra/deploy/build.sh 挂载(主会话)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t6.sh`
|
||||||
|
|
||||||
|
- [x] task_0007 暗色预览
|
||||||
|
- claim: 子代理
|
||||||
|
- boundary: public/admin/preview-style.js(双主题改造)
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t7.sh`
|
||||||
|
|
||||||
|
- [x] task_0008 媒体库归置
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: public/admin/config.yml(media_folder hints)、docs/管理台.md
|
||||||
|
- verify: `bash .spec/specs/2026-11-05_blog-5n-admin-deep-optimization/evidence/verify_t8.sh`
|
||||||
@@ -3,6 +3,10 @@ import { defineConfig } from 'astro/config';
|
|||||||
import pagefind from 'astro-pagefind';
|
import pagefind from 'astro-pagefind';
|
||||||
import sitemap from '@astrojs/sitemap';
|
import sitemap from '@astrojs/sitemap';
|
||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
|
import { readdirSync, readFileSync } from 'node:fs';
|
||||||
|
import rehypeLinkCards, { linkCardsReport } from './src/lib/rehype-link-cards';
|
||||||
|
import rehypeClassWhitelist from './src/lib/rehype-class-whitelist';
|
||||||
|
import rehypeRaw from 'rehype-raw';
|
||||||
|
|
||||||
// 构建透明(T7):commit/时间经 vite.define 注入,页脚与 /about 引用。
|
// 构建透明(T7):commit/时间经 vite.define 注入,页脚与 /about 引用。
|
||||||
// 四重回退:CI 环境变量优先(GITEA_ACTIONS/GITHUB_SHA)→ 本地 git → dev 占位。
|
// 四重回退:CI 环境变量优先(GITEA_ACTIONS/GITHUB_SHA)→ 本地 git → dev 占位。
|
||||||
@@ -15,9 +19,44 @@ const date =
|
|||||||
process.env.GITHUB_COMMIT_DATE ||
|
process.env.GITHUB_COMMIT_DATE ||
|
||||||
(() => { try { return execSync('git log -1 --format=%cI').toString().trim(); } catch { return new Date().toISOString(); } })();
|
(() => { try { return execSync('git log -1 --format=%cI').toString().trim(); } catch { return new Date().toISOString(); } })();
|
||||||
|
|
||||||
|
// 隐藏文章不进 sitemap(内容侧契约见 src/lib/post-visibility.ts)。
|
||||||
|
// 这里在配置期直接扫 frontmatter:Astro 的 content collections 在配置上下文不可用,
|
||||||
|
// 而 sitemap 的 filter 只拿到 URL。判定两条通道:`hidden: true` 与 tags 里的 hidden。
|
||||||
|
const hiddenSlugs = (() => {
|
||||||
|
const set = new Set();
|
||||||
|
try {
|
||||||
|
const dir = 'content/posts';
|
||||||
|
for (const file of readdirSync(dir)) {
|
||||||
|
if (!file.endsWith('.md')) continue;
|
||||||
|
const raw = readFileSync(`${dir}/${file}`, 'utf8');
|
||||||
|
const head = (raw.split('\n---\n')[0] ?? '').replace(/^---\n/, '');
|
||||||
|
if (/^hidden:\s*true\s*$/m.test(head)) { set.add(file.replace(/\.md$/, '')); continue; }
|
||||||
|
// tags 可能是行内数组,也可能是多行列表:抓出 tags 段再找 hidden 词
|
||||||
|
const m = /^tags:[ \t]*([\s\S]*?)(?=^\w|\s*$)/m.exec(head);
|
||||||
|
const tagBlock = m ? m[1] : '';
|
||||||
|
if (/\bhidden\b/.test(tagBlock)) set.add(file.replace(/\.md$/, ''));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* 内容目录不可读时宁可不排除,也不让构建挂掉 */
|
||||||
|
}
|
||||||
|
return [...set];
|
||||||
|
})();
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://wait4x.online',
|
site: 'https://wait4x.online',
|
||||||
integrations: [pagefind(), sitemap()],
|
integrations: [
|
||||||
|
pagefind(),
|
||||||
|
sitemap({
|
||||||
|
filter: (page) => !hiddenSlugs.some((slug) => page.includes(`/archives/${slug}/`)),
|
||||||
|
}),
|
||||||
|
linkCardsReport(),
|
||||||
|
],
|
||||||
|
markdown: {
|
||||||
|
// 独立成段的裸链接 → OG 预览卡(构建期抓取,失败回落普通链接;见 src/lib/rehype-link-cards.ts)
|
||||||
|
// 顺序:rehypeRaw 先把 markdown 里的 raw HTML 解析成 hast element(否则白名单看不到 span),
|
||||||
|
// 再白名单净化(剥 style/on*/非白名单类),最后链接卡片(节点由插件自建,不经净化)
|
||||||
|
rehypePlugins: [rehypeRaw, rehypeClassWhitelist, rehypeLinkCards],
|
||||||
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: 'zh',
|
defaultLocale: 'zh',
|
||||||
locales: ['zh', 'en'],
|
locales: ['zh', 'en'],
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<div align="center">
|
||||||
|
<img src="https://readme-typing-svg.demolab.com?font=Fira+Code&weight=600&size=26&duration=3000&pause=1000&color=00D9FF¢er=true&vCenter=true&random=false&width=620&lines=Hi+there+%F0%9F%91%8B+I'm+wait4x;Open+Source+Enthusiast+%E2%9D%A4%EF%B8%8F;Meteorology+%E2%9B%85" alt="Typing SVG" />
|
||||||
|
<br>
|
||||||
|
<a href="https://github.com/wait4xx?tab=followers"><img src="https://img.shields.io/github/followers/wait4xx?label=Followers&style=for-the-badge&color=00D9FF&labelColor=0D1117" /></a>
|
||||||
|
<a href="https://github.com/wait4xx?tab=stars"><img src="https://img.shields.io/github/stars/wait4xx?label=Stars&style=for-the-badge&color=00D9FF&labelColor=0D1117" /></a>
|
||||||
|
<a href="https://github.com/wait4xx"><img src="https://img.shields.io/badge/Open%20Source-%E2%9D%A4%EF%B8%8F-00D9FF?style=for-the-badge&labelColor=0D1117" /></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 👋 About Me
|
||||||
|
|
||||||
|
- ⛅ An aspiring forecaster rooted in **meteorology**.
|
||||||
|
- 💻 Passionate about **computer science** and coding.
|
||||||
|
- 🌐 My web: **[wait4x.online](https://wait4x.online/)**
|
||||||
|
- ⚡ *"Embrace open source, love open source."*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📊 GitHub Stats
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img height="170" src="https://github-readme-stats.vercel.app/api?username=wait4xx&show_icons=true&hide_border=true&bg_color=0D1117&title_color=00D9FF&icon_color=00D9FF&text_color=C9D1D9&include_all_commits=true&count_private=true&line_height=28" alt="stats" />
|
||||||
|
<img height="170" src="https://github-readme-stats.vercel.app/api/top-langs/?username=wait4xx&layout=compact&hide_border=true&bg_color=0D1117&title_color=00D9FF&text_color=C9D1D9&langs_count=8" alt="top-langs" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://streak-stats.demolab.com?user=wait4xx&hide_border=true&background=0D1117&ring=00D9FF&fire=00D9FF&currStreakLabel=00D9FF&sideLabels=C9D1D9&dates=C9D1D9&currStreakNum=C9D1D9" alt="streak" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📈 Activity Graph
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://github-readme-activity-graph.vercel.app/graph?username=wait4xx&bg_color=0D1117&color=C9D1D9&line=00D9FF&point=00D9FF&area=true&area_color=00D9FF&hide_border=true" alt="activity-graph" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🐍 Contribution Snake
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/wait4xx/wait4xx/output/github-contribution-grid-snake.svg" alt="snake" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
> *This snake is auto-fed every day by a GitHub Action in this repo 🍎*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📫 Find Me
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://github.com/wait4xx"><img src="https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=github&logoColor=white" /></a>
|
||||||
|
<a href="mailto:X@alumni.nudt.edu.cn"><img src="https://img.shields.io/badge/Email-D14836?style=for-the-badge&logo=gmail&logoColor=white" /></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://komarev.com/ghpvc/?username=wait4xx&color=00D9FF&style=flat-square&label=%F0%9F%91%80+Visitors" />
|
||||||
|
<br><br>
|
||||||
|
<img src="https://readme-typing-svg.demolab.com?font=Fira+Code&size=14&duration=2000&pause=500&color=8B949E¢er=true&vCenter=true&random=false&width=300&lines=Thanks+for+visiting!;See+you+in+the+commits+%F0%9F%8C%BF" alt="footer" />
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"_readme": "链接卡片抓取缓存(构建确定性需求,入仓)。键=URL,值={title,desc,image,favicon,ts};ts 超 30 天刷新。手工改动安全:删条目即下次构建重抓。",
|
||||||
|
"https://www.cnblogs.com/Lenbrother/p/18673403": {
|
||||||
|
"title": "Debian 安装 Docker - 神奇小宝 - 博客园",
|
||||||
|
"desc": "卸载已有 Docker 如果你之前安装过 Docker Engine 之前,你需要卸载旧版本,避免冲突: for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get rem",
|
||||||
|
"image": "https://assets.cnblogs.com/images/wechat-share.jpg",
|
||||||
|
"favicon": "https://assets.cnblogs.com/favicon_v3_3.ico",
|
||||||
|
"ts": 1793593758751
|
||||||
|
},
|
||||||
|
"https://wait4x.online/": {
|
||||||
|
"title": "wait4x",
|
||||||
|
"desc": "wait4x 的博客:阅读 · 技术 · 摄影",
|
||||||
|
"image": "",
|
||||||
|
"favicon": "https://wait4x.online/favicon.svg",
|
||||||
|
"ts": 1793593759184
|
||||||
|
},
|
||||||
|
"https://blog.csdn.net/qq_40605167/article/details/122888580": {
|
||||||
|
"title": "Docker版 E5续订的E5调用API续订服务:Microsoft 365 E5 Renew X_e5 docker",
|
||||||
|
"desc": "文章浏览阅读2.7w次,点赞10次,收藏43次。本文介绍了如何使用Docker轻松部署Microsoft365E5续订服务,包括提供小巧的镜像、一键部署命令及邮箱通知功能。用户需要具备Docker基础,并按照步骤修改邮箱和密码等参数。此外,还提供了不同CPU架构的支持。注意,已有部分账号出现被关停的情况。文章附带了Docker环境搭建教程和相关资源链接。",
|
||||||
|
"image": "https://i-blog.csdnimg.cn/blog_migrate/cover/dc9f6540d4d4330e797f1ebd0261f736.png",
|
||||||
|
"favicon": "https://g.csdnimg.cn/static/logo/favicon32.ico",
|
||||||
|
"ts": 1793593759739
|
||||||
|
},
|
||||||
|
"https://astro.build/": {
|
||||||
|
"title": "Astro",
|
||||||
|
"desc": "Astro builds fast content sites, powerful web applications, dynamic server APIs, and everything in-between.",
|
||||||
|
"image": "https://astro.build/og/astro.jpg",
|
||||||
|
"favicon": "https://astro.build/favicon.svg",
|
||||||
|
"ts": 1793593759816
|
||||||
|
},
|
||||||
|
"https://astro.build": {
|
||||||
|
"title": "Astro",
|
||||||
|
"desc": "Astro builds fast content sites, powerful web applications, dynamic server APIs, and everything in-between.",
|
||||||
|
"image": "https://astro.build/og/astro.jpg",
|
||||||
|
"favicon": "https://astro.build/favicon.svg",
|
||||||
|
"ts": 1793621147252
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
pubDate: 2026-04-29
|
|
||||||
tags: []
|
|
||||||
text: "@wait4x"
|
|
||||||
---
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
pubDate: 2026-04-29
|
|
||||||
tags: []
|
|
||||||
text: "@wait4x"
|
|
||||||
---
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
---
|
---
|
||||||
pubDate: 2026-04-29
|
pubDate: 2026-04-29
|
||||||
tags: []
|
text: '@wait4x'
|
||||||
text: "@wait4x"
|
tags:
|
||||||
|
- me
|
||||||
|
pinned: true
|
||||||
|
hidden: false
|
||||||
---
|
---
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
pubDate: 2026-11-02
|
||||||
|
text: |-
|
||||||
|
瞬间现在和文章同级表达力:`text` 支持 markdown 与排版类,<span class="c-teal font-kai">这一句是青色 + 楷体</span>,也可以**加粗**。
|
||||||
|
|
||||||
|
另外每条瞬间自带评论区——展开才加载,不刷时间线就不为它付成本。
|
||||||
|
tags:
|
||||||
|
- 站点更新
|
||||||
|
pinned: false
|
||||||
|
hidden: true
|
||||||
|
---
|
||||||
|
|
||||||
|
站点更新记录:瞬间富文本与逐条评论上线。
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"title": "防波堤暮色",
|
"title": "防波堤暮色",
|
||||||
"image": "./1766156983093.jpg",
|
"image": "./1766156983093.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "暮色海岸与远处防波堤(T3 冒烟补写)。"
|
"alt": "暮色海岸与远处防波堤(T3 冒烟补写)。",
|
||||||
|
"caption": "落日把海与城一齐烫成金箔,海鸟的剪影恰好掠过日轮"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766156992123.jpg",
|
"image": "./1766156992123.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "书架暗处,那本宫崎骏分镜集被灯光单独打亮"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766156992265.jpg",
|
"image": "./1766156992265.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "一面白墙只问一个问题,笔锋落下处是想活出的人生"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766156992559.jpg",
|
"image": "./1766156992559.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "黑暗中独一句发光的话,像有人在夜里旋起一阵热风"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766156992840.jpg",
|
"image": "./1766156992840.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "一抹红墙,几笔白线,龙猫的轮廓就是吉卜力的门牌"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766156997939.jpg",
|
"image": "./1766156997939.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "夜游船驮着绿光驶过,东方明珠在它身后蓝得像天外来物"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766156998072.jpg",
|
"image": "./1766156998072.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "对岸灯火压得很低,渡轮拖着一线亮色划开墨色的江面"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766156998205.jpg",
|
"image": "./1766156998205.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "海关大楼的钟面亮成金色,人群在灯下攒动成剪影"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766156998344.jpg",
|
"image": "./1766156998344.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "旗在风里翻卷,塔身亮得像一支随时点火的火箭"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766156998590.jpg",
|
"image": "./1766156998590.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "大楼只占一个角落,剩下的天空都交给云慢慢写"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766157003595.jpg",
|
"image": "./1766157003595.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "草坡绿得发暗,牦牛散落其间,低头啃食高原的安静"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766157003732.jpg",
|
"image": "./1766157003732.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "灰白天色下,雪与黑岩互相渗透,山脊线细得像一笔勾勒"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766157003865.jpg",
|
"image": "./1766157003865.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "雾把山谷对折,只留下碎石和雪在白茫茫里彼此确认"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766157003992.jpg",
|
"image": "./1766157003992.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "一队人影在乱石坡上缓缓上移,雪山在他们头顶沉默俯视"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766157004116.jpg",
|
"image": "./1766157004116.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "雪原中央蹲着两团颜色,出发前把装备一件件理顺"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./1766157012003.jpg",
|
"image": "./1766157012003.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "太阳缩成一粒橘色的句点,悬在灰橙色的茫茫天幕上"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./mmexport1771816358254.jpg",
|
"image": "./mmexport1771816358254.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "成群海鸥同时起飞,蓝海与红屋顶都被翅膀搅动"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./mmexport1771816377628.jpg",
|
"image": "./mmexport1771816377628.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "垂钓的人立在礁石上,几根鱼竿替他守着整片海"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./mmexport1771816382115.jpg",
|
"image": "./mmexport1771816382115.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "长堤探进海里,堤上的人站成一小排,各自望着各自的远方"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./mmexport1776564084994.jpg",
|
"image": "./mmexport1776564084994.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "海鸥飞过之处,玻璃高塔与红瓦老城在同一帧里相遇"
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
"image": "./mmexport1776564092433.jpg",
|
"image": "./mmexport1776564092433.jpg",
|
||||||
"group": "landscape",
|
"group": "landscape",
|
||||||
"alt": "",
|
"alt": "",
|
||||||
"exif": {}
|
"exif": {},
|
||||||
|
"caption": "画面只留一圈亮处:老楼、松树、栏杆边与海鸥相伴的人群"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
title: "Agensh: Scaling Organizational Intelligence to 1,024 Agents"
|
||||||
|
pubDate: 2026-11-01
|
||||||
|
category: tech
|
||||||
|
tags: [Paper Notes, AI Collaboration]
|
||||||
|
locked: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Multi-agent systems compress the latency of complex tasks through concurrency, but mainstream frameworks share one ceiling: the central orchestrator. Task allocation, progress aggregation, and result adjudication all squeeze through a single context — the bigger the scale, the sooner the orchestrator dies. In October 2026, Microsoft Research open-sourced [Agensh](https://github.com/microsoft/Agensh) — a self-organized multi-agent harness **with no central orchestrator**, and the paper [*Scaling Organizational Intelligence to 1,024 Agents*](https://arxiv.org/abs/2609.26781) pushed it to a thousand and twenty-four agents.
|
||||||
|
|
||||||
|
Its answer is disarmingly simple: instead of building a smarter scheduling brain, hand the agents the coordination infrastructure of a human organization, as is.
|
||||||
|
|
||||||
|
> 📄 **[Read the full paper PDF online →](/en/papers/agensh/)** · arXiv 2609.26781 · Microsoft Research
|
||||||
|
|
||||||
|
## 1. Three pillars: an organization's coordination infrastructure
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Agensh provides exactly three pieces of infrastructure; everything else self-organizes:
|
||||||
|
|
||||||
|
- **Shared workspace**: a git platform carries the organization's in-progress and integrated work — branches, PRs, and merge conflicts, twenty years of human machinery reused as-is;
|
||||||
|
- **Message interface**: a team channel plus direct messages with priority delivery — organization-wide broadcasts and urgent collision resolution each take their own lane;
|
||||||
|
- **Shared context**: a typed-entry board holding the organization's findings and work intentions.
|
||||||
|
|
||||||
|
The open-source reference implementation runs Gitea for the workspace and Mattermost for channel and DMs — a fully self-hosted "office" with no proprietary services. No orchestrator assigns tasks: workers find work themselves in the workspace and the context board, claim it, verify it, and merge it. The natural bottleneck of scaling — that ever-busier hub — simply does not exist.
|
||||||
|
|
||||||
|
## 2. The cooperation loop: every worker runs their own lap
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Every worker concurrently and asynchronously runs the same loop: **gather context → claim a sub-task → take action → verify → merge progress**. Gathering context means reading the shared workspace and the board's accumulated knowledge; claiming is a CLAIM declaration, with collisions resolved over direct messages; verification is running the tests yourself — evidence talks; merging goes through PRs.
|
||||||
|
|
||||||
|
The most interesting design is the **economy** of the shared context: entries are deliberately tiny (about 100 characters, 300 for PATCH_SUMMARY), with the long version in a `detail` field fetched on demand; `board_read` serves only the latest 2,000 entries, and anything older is reachable only through boolean search (`,` = OR, `&` = AND) — recent memory stays small, the long tail is searched. The paper calls **FAIL the highest-value entry**: one documented falsified path stops the whole team from re-burning budget on the same dead end. New entries are pushed to peers as higher-priority updates at their next tool-call boundary.
|
||||||
|
|
||||||
|
This shared context is not Agensh's invention — §2.3 of the paper states plainly that "the shared context follows the core idea of DeLM". [DeLM](https://github.com/yuzhenmao/DeLM) ([arXiv:2606.10662](https://arxiv.org/abs/2606.10662)) ran decentralized multi-agent coordination through a shared context and task queue: its board is an append-only event log (items, claims, completions, FACT/FAIL notes, status) with bodies stored in a separate database beside the log, hard limits (≤40 files, ≤400 KB total), and a blocking `read --wait` that wakes when a peer posts. On a 10-task Terminal-Bench 4.0 subset, two agents coordinating through the shared context averaged +12.5 percentage points accuracy and a 1.78× speedup. Agensh scaled that thread to a thousand agents.
|
||||||
|
|
||||||
|
## 3. The numbers: does scaling actually help?
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Two datasets answer "do more agents actually move the needle": on the five hardest ProgramBench tasks, 1 → 128 agents raised the pass rate from 19.31% to 28.78% (~49% relative); on building pandoc from scratch (6-hour budget, no Internet), 1 → 1,024 agents raised it from 33.89% to 55.06%. For problems where raw compute has hit diminishing returns, **the marginal return of organization is still there**.
|
||||||
|
|
||||||
|
Engineering readiness arrived too: v1.0 shipped on 2026-10-23, and v1.0.1 six days later supports Copilot, Codex, Claude Code, and Pi as the underlying single-agent harness — use whatever mainline model you like underneath. The open-source release is a single-node harness, and the v1.0 package ships with a reproduction guide, the paper, and a demo — from paper to runnable repository with no "coming soon" in between.
|
||||||
|
|
||||||
|
## 4. From paper to desktop: the story of one issue
|
||||||
|
|
||||||
|
For us this paper was never just on paper. After digesting it, on 2026-10-23 I filed [issue #13](https://github.com/cgarrot/pi-mesh/issues/13) on the pi-mesh repository: pi-mesh already had the message interface (`mesh_send`, rooms, priorities) and the shared workspace (git + `mesh_reserve`), but lacked the third pillar, shared context — proposing `board_write / board_read / board_grep` in exactly the Agensh/DeLM shape.
|
||||||
|
|
||||||
|
The maintainer cgarrot's reply is the most instructive part of the story: the direction was right, but **the hash-only ledger makes pi-mesh structurally incapable of being that board** — embedding persistence in the broker would turn a router into a database. The right shape was a separate package: join the room as a normal peer, own your storage, and let the mesh keep routing. Thus [pi-board](https://github.com/wait4xx/pi-board): v0.1 landed within a day — 55 tests, zero failures, a nine-step smoke run on a real broker — and when the issue finally closed, the official pointer landed on it. The memory layer of the [four-piece toolkit](/en/archives/pi-mesh-stack/) introduced in the previous post grew straight out of this paper.
|
||||||
|
|
||||||
|
## 5. Closing
|
||||||
|
|
||||||
|
Agensh's insight is not the number "1024" but its decomposition of organizational intelligence: workspace, messages, shared context — three pillars holding up self-organization, none of which needs a smarter brain. The decomposition holds at thousand-agent scale, holds for a local four-piece toolkit, and holds for a one-person shop with a few agent terminals. Scale changes; the pillars stay.
|
||||||
|
|
||||||
|
Related reading: [Spec Harness in Depth](/en/archives/spec-harness/) · [The pi-mesh Tool Family](/en/archives/pi-mesh-stack/)
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: "The pi-mesh Tool Family: A Four-Piece Toolkit for Local Multi-Agent Coordination"
|
||||||
|
pubDate: 2026-11-01
|
||||||
|
category: tech
|
||||||
|
tags: [Engineering, AI Collaboration]
|
||||||
|
locked: true
|
||||||
|
---
|
||||||
|
|
||||||
|
[pi](https://github.com/earendil-works/pi) is the coding-agent harness we use daily. With a single terminal it is a quiet assistant; with three or four terminals open at once, each running an agent over the same repository, questions pile up: who is online? Who is editing which file? Was that message actually seen? Who has the right to stop the build running at midnight?
|
||||||
|
|
||||||
|
A family of small tools grew around [pi-mesh](https://github.com/cgarrot/pi-mesh) to turn that coordination layer into infrastructure. Each owns one concern, and they interlock:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 1. pi-mesh: the communication backbone
|
||||||
|
|
||||||
|
pi-mesh is a local bus: a broker detached from any session (NDJSON frames over a unix socket), each pi terminal a peer with an alias, organized into rooms. Messages arrive in under 50 ms; zero runtime dependencies, Node ≥ 20.
|
||||||
|
|
||||||
|
Its most admirable trait is the **honest status model**: `delivered` (written to the recipient's socket) ≠ `read` (injected into the recipient's session) ≠ `answered` (an explicit reply came back) — three states never conflated, none of them meaning "done". The companion **durable ledger is hash-only**: message bodies are never persisted; after a restart you can audit who sent what to whom and when, but never read the content. This self-restraint later became the family's design axiom: **the mesh stays a router, never a database.**
|
||||||
|
|
||||||
|
Three coordination needs live in the backbone:
|
||||||
|
|
||||||
|
- **`mesh_reserve` path reservations**: reserve before editing; other peers' `edit`/`write` tools get blocked on those paths — concurrent editing is prevented by mechanism, not by shouting.
|
||||||
|
- **`mesh_claim` task leases**: a single-writer lock with an `epoch:fence` (a broker restart invalidates old fences); commits carry `Mesh-Lease: task#leaseId#epoch:fence`, and the bundled commit-msg hook rejects stale-fence commits outright.
|
||||||
|
- **Capability routing `cap:`**: `mesh_send({ "to": "cap:deploy", ... })` routes the task to any idle peer advertising that capability instead of waiting for a busy teammate. Three message priorities: normal queues, urgent interrupts, force aborts the recipient's current turn and delivers once it settles (force is a control class and requires a reason).
|
||||||
|
|
||||||
|
Add `mesh_wait_all` for group verdicts (one call returns who answered and who is missing), `mesh_status` for peer activity (● working / ○ idle / ✕ stuck), and a `pimesh` CLI for session-less access (`pimesh attach` can adopt a dead session's identity). Multi-machine deployment is supported: one machine runs the broker, the rest connect as clients.
|
||||||
|
|
||||||
|
The honesty reaches down to exit codes: in the CLI, 3 means `expired` — a late reply is still delivered, you just stopped waiting; 5 means a stale lease, and the `commit-msg` hook rejects the commit on that basis. Even the host-specific edge of ESC purging the message queue is covered: a batch injected just as a turn aborts is re-delivered through an abort-safe channel once the turn settles (bounded retries), with a re-delivery counter visible in status.
|
||||||
|
|
||||||
|
## 2. pi-board: the shared context board
|
||||||
|
|
||||||
|
Messages are ephemeral; the ledger is hash-only. Where does "what we learned" live? [pi-board](https://github.com/wait4xx/pi-board) fills that layer. Five typed entries:
|
||||||
|
|
||||||
|
- **OBSERVED** — something seen, no commitment
|
||||||
|
- **FACT** — a verified truth
|
||||||
|
- **FAIL** — a falsified path (the highest-value kind: the whole team stops re-burning budget on it)
|
||||||
|
- **CLAIM** — an ownership note, paired with a `mesh_claim` lease (the board records; it never enforces)
|
||||||
|
- **PATCH_SUMMARY** — what a landed change did
|
||||||
|
|
||||||
|
The design is bounded and honest throughout: append-only local storage, refuse-not-evict (over-limit writes are refused, never silently evicted), short entries by default with a `detail` field unfolded only by id, boolean search (`,` = OR, `&` = AND) across the full history while `board_read` serves only the recent window; a search miss returns an honest empty, never a guess. Writes go to the room as ordinary broadcast frames; a new-FAIL digest line rides every turn boundary; the room's member / observer roles follow mesh semantics, so a read-only peer is never re-admitted as a writer after a reconnect. It joins the room as a normal peer — **all storage stays at the board's home; zero persistence in the broker**. A bundled `piboard` CLI does it in five verbs — join (member / observer) / watch / write / read / grep, one verb per action — so even a session-less terminal can join a room read-only and observe. The typology and the "small entries + on-demand detail + recent memory / full-history search" split come straight from the research literature (the lineage story is in [the next post](/en/archives/agensh/)).
|
||||||
|
|
||||||
|
## 3. pi-jobd: the background job daemon
|
||||||
|
|
||||||
|
`bash` blocks the current turn; `/reload` kills the process. [pi-jobd](https://github.com/cgarrot/pi-jobd) runs long commands in their own process group supervised by a per-user daemon: it returns in ~150 ms, and the job outlives the session. When to use it? Three criteria: the command will run longer than 60–90 seconds; you must keep working while it runs; or it must survive `/reload` and session end — plus "another agent should be able to stop it": `mesh_job_control` lets any authorized peer remotely stop/pause/resume someone else's background job.
|
||||||
|
|
||||||
|
The state machine is equally honest: `running / paused / stopped / exited / failed`, plus `lost` — when the daemon restarts and cannot prove pid ownership, it refuses to stop rather than signal an unproven process. One more clever touch: installing the package is not enough — the model must *know when* to use it. Three layers feed the model the decision rule: a prompt snippet, guidelines on all seven tools, and an on-demand skill.
|
||||||
|
|
||||||
|
## 4. pi-subagents: worker control
|
||||||
|
|
||||||
|
[pi-subagents](https://github.com/nicobailon/pi-subagents) manages in-process child agents: foreground children stream inside the conversation; background children live in a detached runner process you can check later. It works right after install with no slash commands to learn — just say "use reviewer to review this diff". It ships common roles like scout and reviewer, and supports saved workflows and parallel reviews. Its mesh interface is `mesh_run_control`: remotely stop or steer a peer's RPC worker.
|
||||||
|
|
||||||
|
## 5. One rehearsal of the four-piece suite
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 6. Five minutes to onboard
|
||||||
|
|
||||||
|
One line each: mesh and subagents install as pi packages — `pi install npm:pi-mesh-extension`, `pi install npm:pi-subagents`; pi-board is not on npm yet — clone, `npm ci`, `npm run build` — and it works CLI-only (reading and writing the local board needs no mesh at all); pi-jobd builds from source the same way, with `pijob run / list / log / stop` working out of the box. Open two pi sessions in one directory and you have two peers visible to each other in `mesh_status` — "zero config" is not a slogan here.
|
||||||
|
|
||||||
|
Conversation over mesh, memory on the board, long work to jobd, exclusivity by lease — four packages, each doing its job, none overstepping. This blog's redesign was their proving ground: two mesh terminals (op-gitea, op-artalk) ran in parallel with the mainline, nineteen commits in one day; during the publish-chain outage, one lane reserved build.sh for emergency repair while an incoming hotfix edit was correctly blocked by the reservation — collisions are prevented by rule, not by reflex.
|
||||||
|
|
||||||
|
Together the four pieces contain no database and no central brain, yet pin down all four questions: who is online, who is doing what, what was learned, and who watches the long jobs. Small tools, hard boundaries, honest states — that philosophy is worth copying more than any individual package.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: "Spec Harness in Depth: Packaging Long-Horizon Development into Verifiable Task Packages"
|
||||||
|
pubDate: 2026-11-01
|
||||||
|
category: tech
|
||||||
|
tags: [Engineering, AI Collaboration]
|
||||||
|
locked: true
|
||||||
|
---
|
||||||
|
|
||||||
|
What AI coding agents lack is not bandwidth for writing code — it is memory. A task that spans several sessions accumulates half-finished plans, already-falsified approaches, and acceptance criteria that get lost in chat history; restart the terminal and the context resets to zero. [Spec Harness](https://github.com/MicTx/spec-harness) targets exactly this: a task-package workflow tool for AI coding agents that persists long-horizon changes as a **recoverable, verifiable Development Record**, governed by scope, evidence, and Git gates. It provides no model and makes no product decisions for you.
|
||||||
|
|
||||||
|
Its worldview in one sentence: **when a coding task outlives one conversation, the missing tool is not a smarter model — it is a contract that everyone (including your future self) can read.**
|
||||||
|
|
||||||
|
## 1. The core abstraction: the task package
|
||||||
|
|
||||||
|
All work lives in a `.spec/specs/YYYY-MM-DD_slug/` directory with two core files: `spec.md` records scope, assumptions, and trade-offs (including a remarkable "falsified paths" section, more on that below), while `tasks.md` holds the task list. After closeout the whole package is archived under `.spec/specs/archive/` — the archive directory is the project's engineering log.
|
||||||
|
|
||||||
|
Every task must carry a two-line contract: **boundary** (which files this task may touch) and **verify** (how completion is proven). The verify is a read-only assertion that must exit 0 before the checkbox goes on. A real example from this blog's archived `2026-10-26_blog-5f` package, task T3:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Boundary delimits "what may change"; verify delimits "how completion is proven" — both lines present, or the task stays unchecked. This is not formalism: with multiple agents running in parallel, boundary is the guardrail between lanes (in our practice, global style files are mainline-only; lanes request new variables instead of reaching in). Verify separates "looks done" from "proven done" — a verbal "passed" never counts.
|
||||||
|
|
||||||
|
The most interesting section of `spec.md` is **falsified paths**: rejected proposals go into the archive together with the reason for rejection. Our blog-5f package records three — hero banner treatment (rejected by visual adjudication), a green heatmap (broke the grayscale skeleton), and shrinking the title from 27px to 22px (a hierarchy anchor; reduce weight, not size). Whoever wants to try again reads the file first and saves a round of budget.
|
||||||
|
|
||||||
|
## 2. The stage pipeline: five core commands and the gates they keep
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Five core commands form one verifiable chain: **goal → scope → tasks → verification → archive → commit → push**. Gates stand between stages: `run` refuses while `spec.md` still has template placeholders; `done` requires every task to have boundary/verify, a fully green acceptance surface, and at least one non-placeholder piece of evidence (script output, test results); `push` merges only `spec/`-prefixed integration branches, never combines file changes with a commit, and forbids `--no-verify`.
|
||||||
|
|
||||||
|
A blocked task gets a `!` marker and surfaces to the user — never a silent skip. Actionable issues found during `check` must be written back into the package and fixed in the same round; "to-dos" are not deliverables.
|
||||||
|
|
||||||
|
**Interruption recovery is a designed norm** — the most direct answer to "tasks outlive conversations": re-run `/spec:run` at any moment and it resumes from the checked state in `tasks.md`, depending on no chat memory. Going further, `/spec:autoplan` runs interactive planning (clarify the goal, then framework, detail, and review passes; it stops when planning is ready — no auto-start), and `/spec:autorun` executes package by package, capped at 5 auto rounds by default (`--max-rounds` adjustable), resuming from the same step after a pause; the one-shot entry `/spec:goal` runs planning, execution, review, archiving, and the commit in a single pass.
|
||||||
|
|
||||||
|
The command surface totals 12 stage commands: the core profile installs the five core commands, and full adds the seven extensions — goal / autoplan / autorun / update / status / doctor / organize — where `doctor` self-checks and repairs the environment and `organize` audits structure. One easily-missed detail: installing only lays down files; it never auto-evaluates every task. If you want it to proactively ask "should this get a package?" before you start, wire the activation hooks once with `install_activation_hooks.py --mode on`. The criterion is delivery shape — an independent acceptance surface, actual implementers, a recoverable in-flight task; small fixes, read-only investigations, and one-off experiments just get done. It guards against "forgot to evaluate", not against deliberate under-reporting — the documentation is refreshingly candid about this.
|
||||||
|
|
||||||
|
## 3. Multi-agent orchestration: one orchestrator, bounded helpers
|
||||||
|
|
||||||
|
Spec Harness is deliberately conservative about multi-agent work: **the main session is the single orchestrator** — routing, integration, acceptance, and the `done`/`push` gates never leave the mainline. Sidecar work (exploration, building, reviewing — context-independent slices) may be delegated, but only after a machine route decision (`scripts/route_decision.py`) and a written assignment contract: goal, scope, excluded areas, output, and verification method, all five present. An optional `handoff.md` covers cross-session boundaries — a generative snapshot header plus an append-only entry log, with required sections that adapt to solo / multi-person / agent / cluster collaboration modes.
|
||||||
|
|
||||||
|
Extension slots plug other team processes in: `team-loop` for team cycles and `workflow-runner` for workflow fan-out, each with its README protocol and tests. The source repo also ships four orchestration agent definitions: orchestrator, planner, reviewer, confirmer.
|
||||||
|
|
||||||
|
## 4. Installation: one installer, a dozen-plus hosts
|
||||||
|
|
||||||
|
Spec Harness is a Python 3.9+ standard-library implementation (PolyForm Noncommercial license), installed in three steps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/MicTx/spec-harness.git /tmp/spec-harness
|
||||||
|
cd /tmp/spec-harness
|
||||||
|
git checkout v0.22.2
|
||||||
|
bash install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer supports a dozen-plus hosts: Claude Code, Codex, Gemini CLI, Grok, OpenCode, OpenClaw, Hermes, Pi, ZCode, DeepSeek Harness, Devin, Cursor… auto-detecting installed hosts when unspecified. Install profiles come in core / full: core ships the five core stage commands plus Git hooks; full adds all 12 stage commands and the slots/agents extensions. We use `INSTALL_HOSTS=pi bash install.sh` on Pi — the `/spec` commands work right after installation.
|
||||||
|
|
||||||
|
## 5. Running on a real blog
|
||||||
|
|
||||||
|
This blog is a user. The archive directory holds a chain of packages — from the bare-clone build rework and mobile layout fixes to the SEO unblocking: blog-5f packed eight tasks on three lanes (two mesh terminals plus the mainline) running in parallel, every task's verify a command-line assertion, page tasks wired to E2E — `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs`, all 34 passing. Packages relayed one after another; nineteen green commits landed in a single day.
|
||||||
|
|
||||||
|
Tools iterate, but the idea does not expire: **write "what to do" as a contract, write "done" as evidence, archive the process as a log.** Whether the collaborators are humans or agents, those three things are the foundation of long-horizon engineering.
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
title: "Writing template: this post is the template"
|
||||||
|
pubDate: 2026-11-02
|
||||||
|
category: tech
|
||||||
|
tags: [writing, template]
|
||||||
|
pinned: true
|
||||||
|
locked: true
|
||||||
|
description: One post that exercises every typographic capability on the site — heading levels, colour emphasis, typefaces, quotes, code windows, tables, images, link cards, folding. Copy it to start a new post.
|
||||||
|
---
|
||||||
|
|
||||||
|
This is not a tutorial — it is a **template you can copy**. Every section below actually runs: what you see is what Markdown produces. To start a new post, copy this file, change the slug, and delete what you do not need.
|
||||||
|
|
||||||
|
It is also the live sample for **pinning**: `pinned: true` in the frontmatter lifts a post to the front of the home page, the archive and the bookshelf, while its date, URL and RSS timestamps stay untouched.
|
||||||
|
|
||||||
|
## 1. Heading levels and paragraphs
|
||||||
|
|
||||||
|
Use `##` for a section and `###` for a subsection. **Never use `#`** — the post title comes from the frontmatter `title`, and a second level-one heading confuses both screen readers and search snippets.
|
||||||
|
|
||||||
|
Leave a blank line between paragraphs. Inline you can use **bold**, *italic*, ~~strikethrough~~, `inline code`, and [ordinary links](https://astro.build).
|
||||||
|
|
||||||
|
## 2. Colour emphasis: as much as you need
|
||||||
|
|
||||||
|
Colour is **no longer capped** (the old "three per page" limit was lifted on 2026-11-02). The one rule that remains is the shell: navigation, footer, buttons and background stay greyscale — the shell is temperament, the body is expression.
|
||||||
|
|
||||||
|
<span class="c-blue">First use: state one key conclusion in blue.</span> Then carry on normally.
|
||||||
|
|
||||||
|
<span class="c-red">Second use: warn about a trap in red.</span> For example, “after changing a Markdown plugin you must clear `node_modules/.astro`, or the change silently does nothing”.
|
||||||
|
|
||||||
|
<span class="c-green">Confirm a working path in green.</span> If a whole paragraph deserves emphasis, mark the whole paragraph.
|
||||||
|
|
||||||
|
There are eight hues (blue / teal / green / yellow / orange / red / pink / purple), each with a standard and a `-soft` variant, plus four greyscale classes (`ink-muted` / `ink-warm` / `ink-cool` / `ink-faint`) for lowering tone — those do not count against the colour budget.
|
||||||
|
|
||||||
|
## 3. Typefaces: change only what you wrap
|
||||||
|
|
||||||
|
Body text is sans-serif by default. To give one sentence a different temperament, wrap it:
|
||||||
|
|
||||||
|
<span class="font-kai">This sentence is in Kai. Good for epigraphs, quoted lines in a review, or paragraphs that should be read slowly.</span>
|
||||||
|
|
||||||
|
<span class="font-huiwen">This sentence is in Huiwen Ming. Over a long paragraph it noticeably slows reading, so use it for short lines only.</span>
|
||||||
|
|
||||||
|
Six families are available (`font-kai`, `font-song`, `font-hei-sys`, `font-fangsong`, `font-huiwen`, `font-kuaile`), plus an artistic heading class `font-art-kuaile`. **Mind the weight**: switching a whole page to a family makes every visitor download a CJK font, so avoid changing the body default casually.
|
||||||
|
|
||||||
|
### A level-three heading looks like this
|
||||||
|
|
||||||
|
One step smaller than level two, bold, with tighter spacing. Use it to split a section into topics.
|
||||||
|
|
||||||
|
## 4. Quotes and lists
|
||||||
|
|
||||||
|
> Block quotes are for quoting someone. An attribution can follow as italics.
|
||||||
|
>
|
||||||
|
> — an invented attribution
|
||||||
|
|
||||||
|
Unordered list:
|
||||||
|
|
||||||
|
- First item
|
||||||
|
- Second item, with `inline code`
|
||||||
|
- Third item, nested:
|
||||||
|
- Child one
|
||||||
|
- Child two
|
||||||
|
|
||||||
|
Ordered lists suit steps:
|
||||||
|
|
||||||
|
1. Copy the template
|
||||||
|
2. Change the slug and title in the frontmatter
|
||||||
|
3. Delete the sections you do not need
|
||||||
|
|
||||||
|
## 5. Code blocks
|
||||||
|
|
||||||
|
Blocks of four lines or more get a greyscale window bar (three dots); shorter fragments do not, so small snippets do not look like screenshots.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Sort contract: pinned first → newest first → id as a tie-breaker
|
||||||
|
export function sortPosts<T extends Post>(posts: T[]): T[] {
|
||||||
|
return [...posts].sort((a, b) => {
|
||||||
|
const pin = Number(b.data.pinned === true) - Number(a.data.pinned === true);
|
||||||
|
return pin !== 0 ? pin : b.data.pubDate.getTime() - a.data.pubDate.getTime();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Refer to variables inline like `posts`.
|
||||||
|
|
||||||
|
## 6. Tables
|
||||||
|
|
||||||
|
| Capability | Syntax | Note |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Colour emphasis | `<span class="c-blue">` | no limit (shell stays greyscale) |
|
||||||
|
| Typeface | `<span class="font-kai">` | a whole page adds font weight |
|
||||||
|
| Image | `` | see the writing guide for size rules |
|
||||||
|
| Video | frontmatter `video:` | lives in public/media, ≤50MB |
|
||||||
|
| Link card | a bare URL on its own line | OG fetched at build time, falls back to a plain link |
|
||||||
|
|
||||||
|
## 7. Images
|
||||||
|
|
||||||
|
Reference images by relative path, stored under `content/posts/assets/<slug>/`; they expand on click and vectors stay sharp:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 8. Link cards
|
||||||
|
|
||||||
|
A bare URL on its own line becomes an OG preview card at build time; if the fetch fails it degrades to a plain link, never breaking the layout:
|
||||||
|
|
||||||
|
https://astro.build
|
||||||
|
|
||||||
|
## 9. Folding long content
|
||||||
|
|
||||||
|
Long appendices, full logs and big configs can fold away with zero JavaScript, using native `details`:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Open the folded example</summary>
|
||||||
|
|
||||||
|
A folded region can hold anything — paragraphs, lists, code blocks.
|
||||||
|
|
||||||
|
- Best for “read on demand” detail
|
||||||
|
- Not for conclusions (readers may never open it)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 10. Closing after the rule
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Three sentences are enough to close: what this covered, what the reader should take away, and where to go next. The template is not a demand to write ten sections — it is a promise that **the capabilities are at hand when you need to express something**.
|
||||||
|
|
||||||
|
Next time, copy this file and delete until only your point remains.
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
title: "Agensh:把组织智能扩展到 1,024 个智能体"
|
||||||
|
pubDate: 2026-11-01
|
||||||
|
category: tech
|
||||||
|
tags: [论文笔记, AI 协作]
|
||||||
|
---
|
||||||
|
|
||||||
|
多智能体系统能靠并发压缩复杂任务的时延,但主流框架有一个共同的天花板:中心编排器。任务分配、进度汇总、结果裁决全部挤过同一个上下文,规模越大,编排器先死。2026 年 10 月,微软研究院开源了 [Agensh](https://github.com/microsoft/Agensh)——一个**没有中心编排器**的自组织多智能体 harness,论文[《Scaling Organizational Intelligence to 1,024 Agents》](https://arxiv.org/abs/2609.26781)把它推到了一千零二十四个 agent 的规模。
|
||||||
|
|
||||||
|
它的答案出人意料地朴素:与其造一个更聪明的调度大脑,不如把一个人类组织的协同基建原样搬给 agent 们。
|
||||||
|
|
||||||
|
> 📄 **[在线研读全文 PDF →](/papers/agensh/)** · arXiv 2609.26781 · 微软研究院
|
||||||
|
|
||||||
|
## 一、三支柱:组织的协同基建
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Agensh 只给三样基础设施,其余全部自组织:
|
||||||
|
|
||||||
|
- **共享工作区**:git 平台承载组织的进行中工作与集成结果,分支、PR、合并冲突这些人类用了二十年的机制原样复用;
|
||||||
|
- **消息接口**:团队频道加私信,带优先级送达——组织级广播与紧急碰撞调解各走各的道;
|
||||||
|
- **共享上下文**:一块类型化条目板,存组织的发现与工作意图。
|
||||||
|
|
||||||
|
开源参考实现用的是 Gitea 承载工作区、Mattermost 承载频道与私信——一套完全开源自接的“办公室”,没有任何私有服务。没有编排器分配任务:worker 自己从工作区和上下文里找活、认领、验证、合并。规模化时天生的瓶颈(那个越来越忙的中枢)根本不存在。
|
||||||
|
|
||||||
|
## 二、协作环:每个 worker 各跑一圈
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
每个 worker 并发、异步地执行同一个协作环:**收集上下文 → 认领子任务 → 执行 → 验证 → 合并进度**。收集上下文时读共享工作区和条目板的存量知识;认领靠 CLAIM 声明,撞车了私聊解决;验证自己跑测试,证据说话;合并走 PR。
|
||||||
|
|
||||||
|
最有意思的设计在共享上下文的**经济学**:条目刻意短小(约 100 字符,PATCH_SUMMARY 放宽到 300),长版本放 `detail` 字段按需展开;`board_read` 只服务最近 2000 条,更早的历史只能靠布尔检索(`,` = OR,`&` = AND)翻出来——近期记忆保持小,长尾靠搜索。论文称 **FAIL 是价值最高的条目**:一条被证伪的路径写下来,就能止住全队在同一个死胡同里重复烧预算。新条目会作为较高优先级的更新,在同伴的下一个工具调用边界推送过去。
|
||||||
|
|
||||||
|
这套共享上下文不是 Agensh 的发明——论文 §2.3 明说“共享上下文遵循 DeLM 的核心思想”。[DeLM](https://github.com/yuzhenmao/DeLM)([arXiv:2606.10662](https://arxiv.org/abs/2606.10662))用共享上下文加任务队列跑去中心化多智能体:它的板是一张 append-only 事件日志(条目、认领、完成、FACT/FAIL 笔记、状态),正文存在日志旁的独立数据库,带硬上限(≤40 个文件、≤400KB 总量),还有一个阻塞式的 `read --wait`——同伴发帖即唤醒等待者。在 Terminal-Bench 4.0 的 10 项子集上,两个 agent 通过共享上下文协同,平均提升 12.5 个百分点准确率、1.78 倍加速。Agensh 把这条线索放大到了千级规模。
|
||||||
|
|
||||||
|
## 三、数字:扩容真的有用吗
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
两组数据回答"加 agent 到底加不加得动":ProgramBench 最难 5 题,1 → 128 个 agent,通过率 19.31% → 28.78%(相对提升约 49%);从零构建 pandoc(6 小时预算、无外网),1 → 1,024 个 agent,通过率 33.89% → 55.06%。对纯堆算力已经边际递减的难题,**组织化的边际收益还在**。
|
||||||
|
|
||||||
|
工程可用性也到位了:v1.0 于 2026-10-23 发布,六天后的 v1.0.1 已支持 Copilot、Codex、Claude Code、Pi 作为底层单智能体 harness——单 agent 侧你可以用任何顺手的主力模型。开源版是单节点 harness,v1.0 发布包里就带着复现指南、论文与演示——从论文到可跑的仓库,中间没有隔着一句“敬请期待”。
|
||||||
|
|
||||||
|
## 四、从论文到桌面:一个 issue 的故事
|
||||||
|
|
||||||
|
这篇论文对我们不只在纸上。把它读透之后,2026-10-23 我在 pi-mesh 仓库提了[第 13 号 issue](https://github.com/cgarrot/pi-mesh/issues/13):pi-mesh 已有消息接口(`mesh_send`/房间/优先级)和共享工作区(git + `mesh_reserve` 预约),独缺第三支柱共享上下文——提议按 Agensh/DeLM 的形状加 `board_write / board_read / board_grep`。
|
||||||
|
|
||||||
|
维护者 cgarrot 的回复是整个故事里最值得学的一课:方向对,但**hash-only 台账让 pi-mesh 结构上当不了这块板**——把持久化塞进 broker 等于把路由器改造成数据库。正确的形状是独立包:以普通 peer 入房、自有存储、mesh 继续只做路由。于是有了 [pi-board](https://github.com/wait4xx/pi-board):一天内出 v0.1,55 个测试零失败、真实 broker 上九步冒烟,issue 最终关闭时官方指针就落在它身上。上一篇文章介绍的[四件套](/archives/pi-mesh-stack/),其记忆层就是从这篇论文长出来的。
|
||||||
|
|
||||||
|
## 五、结语
|
||||||
|
|
||||||
|
Agensh 的启示不是"1024"这个数字,而是它对"组织智能"的拆解:工作区、消息、共享上下文——三根支柱撑起自组织,任何一根都不需要一个更聪明的大脑。这个拆解在千级规模成立,在单机四件套成立,在单人加几个 agent 终端的小作坊同样成立。规模会变,支柱不变。
|
||||||
|
|
||||||
|
相关阅读:[Spec Harness 详解](/archives/spec-harness/) · [pi-mesh 全家桶详解](/archives/pi-mesh-stack/)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="330" viewBox="0 0 960 330" font-family="ui-sans-serif, system-ui, 'Noto Sans SC', 'PingFang SC', sans-serif">
|
||||||
|
<rect width="960" height="330" fill="#ffffff"/>
|
||||||
|
<text x="480" y="40" text-anchor="middle" font-size="19" font-weight="600" fill="#111113">多智能体协作环:每个 worker 并发、异步地各跑一圈</text>
|
||||||
|
|
||||||
|
<!-- 环上 5 步 -->
|
||||||
|
<g font-size="12.5" fill="#33343a">
|
||||||
|
<rect x="46" y="150" width="150" height="76" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="121" y="182" text-anchor="middle" font-weight="600" fill="#111113">① 收集上下文</text>
|
||||||
|
<text x="121" y="204" text-anchor="middle">读工作区 + 检索条目板</text>
|
||||||
|
|
||||||
|
<rect x="226" y="150" width="150" height="76" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="301" y="182" text-anchor="middle" font-weight="600" fill="#111113">② 认领子任务</text>
|
||||||
|
<text x="301" y="204" text-anchor="middle">声明 CLAIM,冲突私聊解</text>
|
||||||
|
|
||||||
|
<rect x="406" y="150" width="150" height="76" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="481" y="182" text-anchor="middle" font-weight="600" fill="#111113">③ 执行</text>
|
||||||
|
<text x="481" y="204" text-anchor="middle">本地改动推送分支</text>
|
||||||
|
|
||||||
|
<rect x="586" y="150" width="150" height="76" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="661" y="182" text-anchor="middle" font-weight="600" fill="#111113">④ 验证</text>
|
||||||
|
<text x="661" y="204" text-anchor="middle">跑测试,证据说话</text>
|
||||||
|
|
||||||
|
<rect x="766" y="150" width="150" height="76" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="841" y="182" text-anchor="middle" font-weight="600" fill="#111113">⑤ 合并进度</text>
|
||||||
|
<text x="841" y="204" text-anchor="middle">PR 合入共享工作区</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g stroke="#6b6e78" stroke-width="1.5">
|
||||||
|
<line x1="196" y1="188" x2="224" y2="188"/><polygon points="224,188 215,183.5 215,192.5" fill="#6b6e78"/>
|
||||||
|
<line x1="376" y1="188" x2="404" y2="188"/><polygon points="404,188 395,183.5 395,192.5" fill="#6b6e78"/>
|
||||||
|
<line x1="556" y1="188" x2="584" y2="188"/><polygon points="584,188 575,183.5 575,192.5" fill="#6b6e78"/>
|
||||||
|
<line x1="736" y1="188" x2="764" y2="188"/><polygon points="764,188 755,183.5 755,192.5" fill="#6b6e78"/>
|
||||||
|
</g>
|
||||||
|
<path d="M 841 226 L 841 262 L 121 262 L 121 228" fill="none" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="121,228 116,237 126,237" fill="#6b6e78"/>
|
||||||
|
<text x="481" y="256" text-anchor="middle" font-size="12" fill="#6b6e78" dy="-6">下一圈</text>
|
||||||
|
|
||||||
|
<!-- 共享上下文条 -->
|
||||||
|
<rect x="120" y="70" width="720" height="52" rx="10" fill="rgba(52,84,209,0.05)" stroke="#3454d1" stroke-dasharray="6 4" stroke-width="1.2"/>
|
||||||
|
<text x="480" y="92" text-anchor="middle" font-size="13" font-weight="600" fill="#3454d1">共享上下文(类型化条目:OBSERVED / FACT / FAIL / CLAIM / PATCH_SUMMARY)</text>
|
||||||
|
<text x="480" y="112" text-anchor="middle" font-size="11.5" fill="#6b6e78">全程发布发现;条目刻意短小(约 100 字符),长文 detail 按需展开;近 2000 条常驻,全史靠布尔检索</text>
|
||||||
|
<line x1="200" y1="122" x2="150" y2="148" stroke="#a0a3ad" stroke-dasharray="4 3"/>
|
||||||
|
<line x1="760" y1="122" x2="820" y2="148" stroke="#a0a3ad" stroke-dasharray="4 3"/>
|
||||||
|
<text x="480" y="308" text-anchor="middle" font-size="12.5" fill="#6b6e78">论文称 FAIL 是"价值最高的条目":一条被证伪的路,能止住全队的重复预算</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,64 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="420" viewBox="0 0 960 420" font-family="ui-sans-serif, system-ui, 'Noto Sans SC', 'PingFang SC', sans-serif">
|
||||||
|
<rect width="960" height="420" fill="#ffffff"/>
|
||||||
|
<text x="480" y="38" text-anchor="middle" font-size="19" font-weight="600" fill="#111113">从中心编排到自组织:Agensh 的三支柱</text>
|
||||||
|
|
||||||
|
<!-- 左:中心编排器 -->
|
||||||
|
<rect x="40" y="70" width="360" height="290" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="220" y="98" text-anchor="middle" font-size="13.5" font-weight="600" fill="#6b6e78">传统多智能体:中心编排器瓶颈</text>
|
||||||
|
<rect x="150" y="116" width="140" height="40" rx="8" fill="#ffffff" stroke="#6b6e78"/>
|
||||||
|
<text x="220" y="141" text-anchor="middle" font-size="12.5" fill="#33343a">orchestrator</text>
|
||||||
|
<g stroke="#a0a3ad">
|
||||||
|
<line x1="220" y1="156" x2="100" y2="230"/>
|
||||||
|
<line x1="220" y1="156" x2="180" y2="230"/>
|
||||||
|
<line x1="220" y1="156" x2="260" y2="230"/>
|
||||||
|
<line x1="220" y1="156" x2="340" y2="230"/>
|
||||||
|
</g>
|
||||||
|
<g font-size="11.5" fill="#6b6e78">
|
||||||
|
<rect x="72" y="230" width="56" height="30" rx="6" fill="#ffffff" stroke="#d0d4da"/><text x="100" y="249" text-anchor="middle">w1</text>
|
||||||
|
<rect x="152" y="230" width="56" height="30" rx="6" fill="#ffffff" stroke="#d0d4da"/><text x="180" y="249" text-anchor="middle">w2</text>
|
||||||
|
<rect x="232" y="230" width="56" height="30" rx="6" fill="#ffffff" stroke="#d0d4da"/><text x="260" y="249" text-anchor="middle">w3</text>
|
||||||
|
<rect x="312" y="230" width="56" height="30" rx="6" fill="#ffffff" stroke="#d0d4da"/><text x="340" y="249" text-anchor="middle">…</text>
|
||||||
|
</g>
|
||||||
|
<text x="220" y="300" text-anchor="middle" font-size="12" fill="#6b6e78">任务分配、进度汇总、结果裁决</text>
|
||||||
|
<text x="220" y="320" text-anchor="middle" font-size="12" fill="#6b6e78">全部挤过同一个上下文——</text>
|
||||||
|
<text x="220" y="340" text-anchor="middle" font-size="12" fill="#6b6e78">规模越大,编排器先成为天花板</text>
|
||||||
|
|
||||||
|
<!-- 中间箭头 -->
|
||||||
|
<line x1="410" y1="215" x2="460" y2="215" stroke="#6b6e78" stroke-width="1.6"/>
|
||||||
|
<polygon points="460,215 450,210 450,220" fill="#6b6e78"/>
|
||||||
|
|
||||||
|
<!-- 右:自组织三支柱 -->
|
||||||
|
<rect x="470" y="70" width="450" height="290" rx="10" fill="#ffffff" stroke="#3454d1" stroke-width="1.4"/>
|
||||||
|
<text x="695" y="98" text-anchor="middle" font-size="13.5" font-weight="600" fill="#3454d1">Agensh:无中心编排,三支柱基础设施</text>
|
||||||
|
<g font-size="12" fill="#33343a">
|
||||||
|
<rect x="490" y="114" width="130" height="86" rx="8" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="555" y="140" text-anchor="middle" font-weight="600" fill="#111113">共享工作区</text>
|
||||||
|
<text x="555" y="160" text-anchor="middle">git 平台</text>
|
||||||
|
<text x="555" y="178" text-anchor="middle">分支 / PR / 合并</text>
|
||||||
|
<rect x="630" y="114" width="130" height="86" rx="8" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="695" y="140" text-anchor="middle" font-weight="600" fill="#111113">消息接口</text>
|
||||||
|
<text x="695" y="160" text-anchor="middle">团队频道 + 私信</text>
|
||||||
|
<text x="695" y="178" text-anchor="middle">优先级送达</text>
|
||||||
|
<rect x="770" y="114" width="130" height="86" rx="8" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="835" y="140" text-anchor="middle" font-weight="600" fill="#111113">共享上下文</text>
|
||||||
|
<text x="835" y="160" text-anchor="middle">类型化条目板</text>
|
||||||
|
<text x="835" y="178" text-anchor="middle">近期记忆 + 全史检索</text>
|
||||||
|
</g>
|
||||||
|
<g stroke="#a0a3ad">
|
||||||
|
<line x1="555" y1="200" x2="520" y2="240"/>
|
||||||
|
<line x1="695" y1="200" x2="660" y2="240"/>
|
||||||
|
<line x1="835" y1="200" x2="800" y2="240"/>
|
||||||
|
<line x1="555" y1="200" x2="620" y2="240"/>
|
||||||
|
<line x1="695" y1="200" x2="740" y2="240"/>
|
||||||
|
<line x1="835" y1="200" x2="880" y2="240"/>
|
||||||
|
</g>
|
||||||
|
<g font-size="11.5" fill="#6b6e78">
|
||||||
|
<rect x="492" y="240" width="56" height="30" rx="6" fill="#ffffff" stroke="#d0d4da"/><text x="520" y="259" text-anchor="middle">w1</text>
|
||||||
|
<rect x="592" y="240" width="56" height="30" rx="6" fill="#ffffff" stroke="#d0d4da"/><text x="620" y="259" text-anchor="middle">w2</text>
|
||||||
|
<rect x="692" y="240" width="56" height="30" rx="6" fill="#ffffff" stroke="#d0d4da"/><text x="740" y="259" text-anchor="middle">w3</text>
|
||||||
|
<rect x="792" y="240" width="56" height="30" rx="6" fill="#ffffff" stroke="#d0d4da"/><text x="820" y="259" text-anchor="middle">w4</text>
|
||||||
|
<rect x="852" y="240" width="56" height="30" rx="6" fill="#ffffff" stroke="#d0d4da"/><text x="880" y="259" text-anchor="middle">…</text>
|
||||||
|
</g>
|
||||||
|
<text x="695" y="308" text-anchor="middle" font-size="12" fill="#6b6e78">worker 自己跑协作环:收集上下文 → 认领 → 执行 → 验证 → 合并</text>
|
||||||
|
<text x="695" y="330" text-anchor="middle" font-size="12" fill="#6b6e78">发现发布到共享上下文,同伴在下一个工具调用边界收到广播</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="380" viewBox="0 0 960 380" font-family="ui-sans-serif, system-ui, 'Noto Sans SC', 'PingFang SC', sans-serif">
|
||||||
|
<rect width="960" height="380" fill="#ffffff"/>
|
||||||
|
<text x="480" y="38" text-anchor="middle" font-size="19" font-weight="600" fill="#111113">规模收益:扩容 agent 带来的通过率提升</text>
|
||||||
|
<text x="480" y="58" text-anchor="middle" font-size="11.5" fill="#a0a3ad">数据:arXiv:2609.26781</text>
|
||||||
|
|
||||||
|
<!-- 组 1:ProgramBench -->
|
||||||
|
<text x="235" y="92" text-anchor="middle" font-size="13.5" font-weight="600" fill="#111113">ProgramBench 最难 5 题</text>
|
||||||
|
<text x="235" y="112" text-anchor="middle" font-size="12" fill="#6b6e78">1 → 128 agents</text>
|
||||||
|
<g>
|
||||||
|
<!-- 柱底 y=300,柱顶 = 300 - 值*3.4 -->
|
||||||
|
<rect x="115" y="235" width="100" height="65" fill="#a0a3ad"/>
|
||||||
|
<rect x="255" y="202" width="100" height="98" fill="#3454d1"/>
|
||||||
|
<text x="165" y="227" text-anchor="middle" font-size="14" font-weight="600" fill="#33343a">19.31%</text>
|
||||||
|
<text x="165" y="322" text-anchor="middle" font-size="12" fill="#6b6e78">1 agent</text>
|
||||||
|
<text x="305" y="194" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1">28.78%</text>
|
||||||
|
<text x="305" y="322" text-anchor="middle" font-size="12" fill="#6b6e78">128 agents</text>
|
||||||
|
</g>
|
||||||
|
<text x="235" y="352" text-anchor="middle" font-size="12" fill="#33343a">相对提升约 49%</text>
|
||||||
|
|
||||||
|
<!-- 组 2:pandoc -->
|
||||||
|
<text x="645" y="92" text-anchor="middle" font-size="13.5" font-weight="600" fill="#111113">从零构建 pandoc(6h 预算 · 无外网)</text>
|
||||||
|
<text x="645" y="112" text-anchor="middle" font-size="12" fill="#6b6e78">1 → 1,024 agents</text>
|
||||||
|
<g>
|
||||||
|
<rect x="525" y="187" width="100" height="113" fill="#a0a3ad"/>
|
||||||
|
<rect x="665" y="113" width="100" height="187" fill="#3454d1"/>
|
||||||
|
<text x="575" y="179" text-anchor="middle" font-size="14" font-weight="600" fill="#33343a">33.89%</text>
|
||||||
|
<text x="575" y="322" text-anchor="middle" font-size="12" fill="#6b6e78">1 agent</text>
|
||||||
|
<text x="715" y="105" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1">55.06%</text>
|
||||||
|
<text x="715" y="322" text-anchor="middle" font-size="12" fill="#6b6e78">1,024 agents</text>
|
||||||
|
</g>
|
||||||
|
<text x="645" y="352" text-anchor="middle" font-size="12" fill="#33343a">+21.2 个百分点</text>
|
||||||
|
|
||||||
|
<line x1="480" y1="84" x2="480" y2="330" stroke="#e3e5ea"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,47 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="980" height="330" viewBox="0 0 980 330" font-family="ui-sans-serif, system-ui, 'Noto Sans SC', 'PingFang SC', sans-serif">
|
||||||
|
<rect width="980" height="330" fill="#ffffff"/>
|
||||||
|
<text x="490" y="40" text-anchor="middle" font-size="19" font-weight="600" fill="#111113">一次典型合奏:四件套怎么咬合</text>
|
||||||
|
|
||||||
|
<!-- 5 nodes -->
|
||||||
|
<rect x="30" y="80" width="158" height="96" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="109" y="108" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">mesh_send</text>
|
||||||
|
<text x="109" y="132" text-anchor="middle" font-size="12" fill="#33343a">契约式派单 / 对话</text>
|
||||||
|
<text x="109" y="152" text-anchor="middle" font-size="11.5" fill="#6b6e78">五字段任务包 + 期限</text>
|
||||||
|
|
||||||
|
<rect x="216" y="80" width="158" height="96" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="295" y="108" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">board_grep</text>
|
||||||
|
<text x="295" y="132" text-anchor="middle" font-size="12" fill="#33343a">先查团队记忆</text>
|
||||||
|
<text x="295" y="152" text-anchor="middle" font-size="11.5" fill="#6b6e78">FAIL 可检索不重烧</text>
|
||||||
|
|
||||||
|
<rect x="402" y="80" width="158" height="96" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="481" y="108" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">job_run</text>
|
||||||
|
<text x="481" y="132" text-anchor="middle" font-size="12" fill="#33343a">长活交后台</text>
|
||||||
|
<text x="481" y="152" text-anchor="middle" font-size="11.5" fill="#6b6e78">turn 不阻塞</text>
|
||||||
|
|
||||||
|
<rect x="588" y="80" width="158" height="96" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="667" y="108" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">mesh_claim</text>
|
||||||
|
<text x="667" y="132" text-anchor="middle" font-size="12" fill="#33343a">单写者租约</text>
|
||||||
|
<text x="667" y="152" text-anchor="middle" font-size="11.5" fill="#6b6e78">epoch:fence 围栏</text>
|
||||||
|
|
||||||
|
<rect x="774" y="80" width="158" height="96" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="853" y="108" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">board_write</text>
|
||||||
|
<text x="853" y="132" text-anchor="middle" font-size="12" fill="#33343a">留痕</text>
|
||||||
|
<text x="853" y="152" text-anchor="middle" font-size="11.5" fill="#6b6e78">CLAIM 带租约号</text>
|
||||||
|
|
||||||
|
<!-- 正向箭头 -->
|
||||||
|
<line x1="188" y1="128" x2="214" y2="128" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="214,128 205,123.5 205,132.5" fill="#6b6e78"/>
|
||||||
|
<line x1="374" y1="128" x2="400" y2="128" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="400,128 391,123.5 391,132.5" fill="#6b6e78"/>
|
||||||
|
<line x1="560" y1="128" x2="586" y2="128" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="586,128 577,123.5 577,132.5" fill="#6b6e78"/>
|
||||||
|
<line x1="746" y1="128" x2="772" y2="128" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="772,128 763,123.5 763,132.5" fill="#6b6e78"/>
|
||||||
|
|
||||||
|
<!-- 回环箭头 -->
|
||||||
|
<path d="M 853 176 L 853 216 L 109 216 L 109 178" fill="none" stroke="#a0a3ad" stroke-width="1.4" stroke-dasharray="5 4"/>
|
||||||
|
<polygon points="109,178 104,187 114,187" fill="#a0a3ad"/>
|
||||||
|
<text x="481" y="238" text-anchor="middle" font-size="12" fill="#6b6e78">发现与结论回写团队记忆,下一轮派单从存量知识起步</text>
|
||||||
|
|
||||||
|
<text x="490" y="290" text-anchor="middle" font-size="13.5" fill="#33343a">对话走 mesh · 记忆上 board · 长活交 jobd · 排他靠租约</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,47 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="470" viewBox="0 0 960 470" font-family="ui-sans-serif, system-ui, 'Noto Sans SC', 'PingFang SC', sans-serif">
|
||||||
|
<rect width="960" height="470" fill="#ffffff"/>
|
||||||
|
<text x="480" y="38" text-anchor="middle" font-size="19" font-weight="600" fill="#111113">pi-mesh 工具族:一个路由器,三个卫星</text>
|
||||||
|
|
||||||
|
<!-- 顶部:pi 终端 -->
|
||||||
|
<rect x="80" y="62" width="200" height="58" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="180" y="86" text-anchor="middle" font-size="13.5" font-weight="600" fill="#111113">pi 终端 @op-gitea</text>
|
||||||
|
<text x="180" y="106" text-anchor="middle" font-size="11.5" fill="#6b6e78">edit/write 受预约拦截</text>
|
||||||
|
<rect x="380" y="62" width="200" height="58" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="480" y="86" text-anchor="middle" font-size="13.5" font-weight="600" fill="#111113">pi 终端 @op-artalk</text>
|
||||||
|
<text x="480" y="106" text-anchor="middle" font-size="11.5" fill="#6b6e78">读回执 · 忙闲状态</text>
|
||||||
|
<rect x="680" y="62" width="200" height="58" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="780" y="86" text-anchor="middle" font-size="13.5" font-weight="600" fill="#111113">pimesh attach / CLI</text>
|
||||||
|
<text x="780" y="106" text-anchor="middle" font-size="11.5" fill="#6b6e78">无会话独立 peer</text>
|
||||||
|
|
||||||
|
<line x1="180" y1="120" x2="180" y2="150" stroke="#a0a3ad"/>
|
||||||
|
<line x1="480" y1="120" x2="480" y2="150" stroke="#a0a3ad"/>
|
||||||
|
<line x1="780" y1="120" x2="780" y2="150" stroke="#a0a3ad"/>
|
||||||
|
|
||||||
|
<!-- mesh 骨干 -->
|
||||||
|
<rect x="60" y="150" width="840" height="110" rx="12" fill="#ffffff" stroke="#3454d1" stroke-width="1.6"/>
|
||||||
|
<text x="90" y="180" font-size="15" font-weight="600" fill="#3454d1">pi-mesh —— 通信骨干(github.com/cgarrot/pi-mesh)</text>
|
||||||
|
<text x="90" y="206" font-size="12.5" fill="#33343a">broker(内存态:peers / rooms / 邮箱 / 限速)· 房间与角色 member/observer</text>
|
||||||
|
<text x="90" y="228" font-size="12.5" fill="#33343a">mesh_reserve 路径预约 · mesh_claim 单写者租约(epoch:fence,commit-msg 钩子拒过期围栏)</text>
|
||||||
|
<text x="90" y="250" font-size="12.5" fill="#33343a">优先级 normal / urgent / force · 状态诚实三档 delivered ≠ read ≠ answered · 台账 hash-only</text>
|
||||||
|
|
||||||
|
<!-- 连接线到卫星 -->
|
||||||
|
<line x1="240" y1="260" x2="240" y2="300" stroke="#a0a3ad"/>
|
||||||
|
<line x1="480" y1="260" x2="480" y2="300" stroke="#a0a3ad"/>
|
||||||
|
<line x1="720" y1="260" x2="720" y2="300" stroke="#a0a3ad"/>
|
||||||
|
|
||||||
|
<!-- 三个卫星 -->
|
||||||
|
<rect x="70" y="300" width="330" height="110" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="90" y="328" font-size="14" font-weight="600" fill="#111113">pi-board —— 团队记忆板</text>
|
||||||
|
<text x="90" y="350" font-size="12" fill="#33343a">OBSERVED/FACT/FAIL/CLAIM/PATCH_SUMMARY</text>
|
||||||
|
<text x="90" y="370" font-size="12" fill="#33343a">有界 append-only 存储 · 布尔检索 board_grep</text>
|
||||||
|
<text x="90" y="390" font-size="11.5" fill="#6b6e78">以普通 peer 入房;自有存储,broker 零持久化</text>
|
||||||
|
|
||||||
|
<rect x="430" y="300" width="330" height="110" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="450" y="328" font-size="14" font-weight="600" fill="#111113">pi-jobd —— 后台作业守护</text>
|
||||||
|
<text x="450" y="350" font-size="12" fill="#33343a">脱离 turn 的进程组 · 存活于 /reload 与会话之外</text>
|
||||||
|
<text x="450" y="370" font-size="12" fill="#33343a">诚实状态机(lost = 无法证明归属则拒操作)</text>
|
||||||
|
<text x="450" y="390" font-size="11.5" fill="#6b6e78">mesh_job_control 跨 peer 远程 stop/pause/resume</text>
|
||||||
|
|
||||||
|
<rect x="70" y="424" width="690" height="38" rx="8" fill="rgba(52,84,209,0.05)" stroke="#3454d1" stroke-dasharray="6 4" stroke-width="1.1"/>
|
||||||
|
<text x="415" y="448" text-anchor="middle" font-size="12" fill="#33343a">pi-subagents(worker 控制:spawn/steer/abort,mesh_run_control)与上面三者同族协作</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,37 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="440" viewBox="0 0 960 440" font-family="ui-sans-serif, system-ui, 'Noto Sans SC', 'PingFang SC', sans-serif">
|
||||||
|
<rect width="960" height="440" fill="#ffffff"/>
|
||||||
|
<text x="480" y="40" text-anchor="middle" font-size="19" font-weight="600" fill="#111113">任务包解剖:.spec/specs/YYYY-MM-DD_slug/</text>
|
||||||
|
|
||||||
|
<!-- 左:目录树 -->
|
||||||
|
<rect x="40" y="70" width="400" height="250" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="60" y="102" font-size="15" font-weight="600" fill="#111113" font-family="ui-monospace, monospace">2026-10-26_blog-5f/</text>
|
||||||
|
<text x="80" y="140" font-size="14" fill="#33343a" font-family="ui-monospace, monospace">├── spec.md</text>
|
||||||
|
<text x="240" y="140" font-size="13" fill="#6b6e78">范围 · 假设 · 已证伪</text>
|
||||||
|
<text x="80" y="180" font-size="14" fill="#33343a" font-family="ui-monospace, monospace">├── tasks.md</text>
|
||||||
|
<text x="240" y="180" font-size="13" fill="#6b6e78">boundary + verify</text>
|
||||||
|
<text x="80" y="220" font-size="14" fill="#33343a" font-family="ui-monospace, monospace">└── handoff.md</text>
|
||||||
|
<text x="240" y="220" font-size="13" fill="#6b6e78">跨会话交接(可选)</text>
|
||||||
|
<line x1="80" y1="248" x2="400" y2="248" stroke="#e3e5ea"/>
|
||||||
|
<text x="80" y="278" font-size="13" fill="#6b6e78">收尾后整包归档至 .spec/specs/archive/,</text>
|
||||||
|
<text x="80" y="300" font-size="13" fill="#6b6e78">归档目录就是项目的工程日志。</text>
|
||||||
|
|
||||||
|
<!-- 箭头 -->
|
||||||
|
<line x1="440" y1="195" x2="500" y2="195" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="500,195 490,190 490,200" fill="#6b6e78"/>
|
||||||
|
<text x="470" y="180" text-anchor="middle" font-size="12" fill="#6b6e78">每个任务</text>
|
||||||
|
|
||||||
|
<!-- 右:任务卡片 -->
|
||||||
|
<rect x="500" y="70" width="420" height="250" rx="10" fill="#ffffff" stroke="#3454d1" stroke-width="1.5"/>
|
||||||
|
<rect x="500" y="70" width="420" height="40" rx="10" fill="rgba(52,84,209,0.06)"/>
|
||||||
|
<text x="520" y="96" font-size="13.5" font-weight="600" fill="#111113">- [x] T3 构建期内容 lint + build.sh 守卫</text>
|
||||||
|
<text x="520" y="136" font-size="12.5" fill="#33343a" font-family="ui-monospace, monospace">claim: op-gitea(mesh 车道)</text>
|
||||||
|
<text x="520" y="164" font-size="12" fill="#33343a" font-family="ui-monospace, monospace">boundary: 新建 content-lint.mjs + 守卫挂载</text>
|
||||||
|
<text x="520" y="200" font-size="12.5" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">verify:</text>
|
||||||
|
<text x="540" y="224" font-size="12" fill="#33343a" font-family="ui-monospace, monospace">node scripts/build-perf/content-lint.mjs dist</text>
|
||||||
|
<text x="540" y="246" font-size="12" fill="#33343a" font-family="ui-monospace, monospace">&& bash -n infra/deploy/build.sh</text>
|
||||||
|
<text x="520" y="290" font-size="12.5" fill="#6b6e78">verify 是只读断言:跑出退出码 0 才许勾选,</text>
|
||||||
|
<text x="520" y="310" font-size="12.5" fill="#6b6e78">口头"过了"永远不算数。</text>
|
||||||
|
|
||||||
|
<text x="480" y="372" text-anchor="middle" font-size="13.5" fill="#6b6e78">boundary 划定"能动什么",verify 划定"怎么证明做完"——两行合同,缺一不勾</text>
|
||||||
|
<line x1="360" y1="392" x2="600" y2="392" stroke="#e3e5ea"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,51 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="350" viewBox="0 0 960 350" font-family="ui-sans-serif, system-ui, 'Noto Sans SC', 'PingFang SC', sans-serif">
|
||||||
|
<rect width="960" height="350" fill="#ffffff"/>
|
||||||
|
<text x="480" y="38" text-anchor="middle" font-size="19" font-weight="600" fill="#111113">阶段流水线:五个核心命令与它们把守的门</text>
|
||||||
|
|
||||||
|
<!-- 5 个节点 -->
|
||||||
|
<!-- new -->
|
||||||
|
<rect x="36" y="72" width="152" height="120" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="112" y="100" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">/spec:new</text>
|
||||||
|
<text x="112" y="126" text-anchor="middle" font-size="12.5" fill="#33343a">建任务包</text>
|
||||||
|
<text x="112" y="146" text-anchor="middle" font-size="12.5" fill="#33343a">spec.md + tasks.md</text>
|
||||||
|
<text x="112" y="172" text-anchor="middle" font-size="11.5" fill="#6b6e78">模板占位未填 → 拒 run</text>
|
||||||
|
<!-- run -->
|
||||||
|
<rect x="220" y="72" width="152" height="120" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="296" y="100" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">/spec:run</text>
|
||||||
|
<text x="296" y="126" text-anchor="middle" font-size="12.5" fill="#33343a">逐任务执行</text>
|
||||||
|
<text x="296" y="146" text-anchor="middle" font-size="12.5" fill="#33343a">verify 跑通才勾选</text>
|
||||||
|
<text x="296" y="172" text-anchor="middle" font-size="11.5" fill="#6b6e78">阻塞任务打 ! 上浮</text>
|
||||||
|
<!-- check -->
|
||||||
|
<rect x="404" y="72" width="152" height="120" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="480" y="100" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">/spec:check</text>
|
||||||
|
<text x="480" y="126" text-anchor="middle" font-size="12.5" fill="#33343a">复核轮</text>
|
||||||
|
<text x="480" y="146" text-anchor="middle" font-size="12.5" fill="#33343a">问题当轮回写修复</text>
|
||||||
|
<text x="480" y="172" text-anchor="middle" font-size="11.5" fill="#6b6e78">闭环项五选一登记</text>
|
||||||
|
<!-- done -->
|
||||||
|
<rect x="588" y="72" width="152" height="120" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="664" y="100" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">/spec:done</text>
|
||||||
|
<text x="664" y="126" text-anchor="middle" font-size="12.5" fill="#33343a">归档 + 包提交</text>
|
||||||
|
<text x="664" y="146" text-anchor="middle" font-size="12.5" fill="#33343a">证据随包入档</text>
|
||||||
|
<text x="664" y="172" text-anchor="middle" font-size="11.5" fill="#6b6e78">验收面证据必须真实</text>
|
||||||
|
<!-- push -->
|
||||||
|
<rect x="772" y="72" width="152" height="120" rx="10" fill="#f6f7f9" stroke="#d0d4da"/>
|
||||||
|
<text x="848" y="100" text-anchor="middle" font-size="14" font-weight="600" fill="#3454d1" font-family="ui-monospace, monospace">/spec:push</text>
|
||||||
|
<text x="848" y="126" text-anchor="middle" font-size="12.5" fill="#33343a">合并 + 推送</text>
|
||||||
|
<text x="848" y="146" text-anchor="middle" font-size="12.5" fill="#33343a">删已并分支</text>
|
||||||
|
<text x="848" y="172" text-anchor="middle" font-size="11.5" fill="#6b6e78">只认 spec/ 前缀分支</text>
|
||||||
|
|
||||||
|
<!-- 箭头 -->
|
||||||
|
<line x1="188" y1="132" x2="218" y2="132" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="218,132 209,127.5 209,136.5" fill="#6b6e78"/>
|
||||||
|
<line x1="372" y1="132" x2="402" y2="132" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="402,132 393,127.5 393,136.5" fill="#6b6e78"/>
|
||||||
|
<line x1="556" y1="132" x2="586" y2="132" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="586,132 577,127.5 577,136.5" fill="#6b6e78"/>
|
||||||
|
<line x1="740" y1="132" x2="770" y2="132" stroke="#6b6e78" stroke-width="1.5"/>
|
||||||
|
<polygon points="770,132 761,127.5 761,136.5" fill="#6b6e78"/>
|
||||||
|
|
||||||
|
<!-- 恢复车道 -->
|
||||||
|
<rect x="36" y="228" width="888" height="76" rx="10" fill="rgba(52,84,209,0.05)" stroke="#3454d1" stroke-dasharray="6 4" stroke-width="1.2"/>
|
||||||
|
<text x="480" y="258" text-anchor="middle" font-size="13.5" font-weight="600" fill="#111113">中断恢复是常态设计</text>
|
||||||
|
<text x="480" y="284" text-anchor="middle" font-size="12.5" fill="#33343a">任意时刻重跑 /spec:run,从 tasks.md 勾选状态续跑;/spec:autorun 可续自动链(默认 5 轮上限,--max-rounds 可调)</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 772 KiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
@@ -1,11 +1,16 @@
|
|||||||
---
|
---
|
||||||
title: "等待戈多"
|
title: 等待戈多
|
||||||
pubDate: 2026-03-31
|
pubDate: 2026-03-31
|
||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags:
|
||||||
|
- read
|
||||||
|
pinned: false
|
||||||
mood: '#b45309'
|
mood: '#b45309'
|
||||||
bookAuthor: '萨缪尔·贝克特'
|
bookAuthor: 萨缪尔·贝克特
|
||||||
description: '无望之待里,陪伴是囚笼唯一的呼吸缝隙。'
|
description: 无望之待里,陪伴是囚笼唯一的呼吸缝隙。
|
||||||
|
cover: ./assets/等待戈多.png
|
||||||
|
hidden: false
|
||||||
|
draft: false
|
||||||
---
|
---
|
||||||
|
|
||||||
### 谁是戈多?为何等待?
|
### 谁是戈多?为何等待?
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: "排版能力样例"
|
||||||
|
pubDate: 2026-11-02
|
||||||
|
category: tech
|
||||||
|
tags: [test, hidden]
|
||||||
|
# 排版/斜体/白名单测试样张(fix3 车道):hidden 不列出,构建与 E2E 验证用
|
||||||
|
draft: false
|
||||||
|
---
|
||||||
|
|
||||||
|
本篇验证三件事:斜体行内渲染、受限排版类白名单、图注类。
|
||||||
|
|
||||||
|
## 斜体(应为行内,不换行不居中)
|
||||||
|
|
||||||
|
这一句里有 *行内斜体强调* 夹在文字中间,它必须与正文同处一行。
|
||||||
|
|
||||||
|
*这一整段是斜体独占段,它不该变成块级居中。*
|
||||||
|
|
||||||
|
## 受限排版类(白名单)
|
||||||
|
|
||||||
|
<span class="ink-muted">这一段是 ink-muted 灰字。</span>
|
||||||
|
<span class="font-kai">这一段是霞鹜文楷字族。</span>
|
||||||
|
<span class="font-mono">这一段是等宽字族 mono。</span>
|
||||||
|
|
||||||
|
<p class="drop-cap">首字下沉段落:秋天的海面像一块被风抚平的铁皮,落日把它的边缘烧红了一圈。</p>
|
||||||
|
|
||||||
|
<p class="quote-line">所谓暮色,就是光慢慢地承认自己要走。</p>
|
||||||
|
|
||||||
|
## 白名单剔除(非法类与内联样式必须被剥离)
|
||||||
|
|
||||||
|
<span class="evil-class ink-2" style="color:#ff0000" onclick="alert(1)">这一段同时带了非法类、内联颜色与 onclick,构建后应只剩 ink-2 且 style/onclick 消失。</span>
|
||||||
|
|
||||||
|
## 图注(类驱动)
|
||||||
|
|
||||||
|
<p class="fig-cap">这是显式图注样式(fig-cap),居中灰字。</p>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: "字体族样例"
|
||||||
|
pubDate: 2026-11-02
|
||||||
|
category: reading
|
||||||
|
tags: [test, hidden]
|
||||||
|
draft: false
|
||||||
|
---
|
||||||
|
|
||||||
|
字体测试样张(hidden):验证系统回退字族的渲染。
|
||||||
|
|
||||||
|
<p class="font-song">宋体族(系统回退):秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="font-kai-sys">楷体族(系统回退):秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="font-hei-sys">黑体族(系统回退):秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="font-fangsong">仿宋族(系统回退):秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="ink-warm">暖调正文:秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="ink-cool">冷调正文:秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="ink-faint">最淡可读档:秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="ink-accent-soft">弱强调:秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="c-blue">蓝色标准:秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
<p class="c-yellow">黄色标准(已压暗):秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
<p class="c-red-soft">红色 soft:秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="font-huiwen">汇文明朝体(OFL,切片自托管):秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
|
|
||||||
|
<p class="font-kuaile">站酷快乐体(OFL,艺术字):秋天的海面像一块被风抚平的铁皮。</p>
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
---
|
---
|
||||||
title: "海风中失落的血色馈赠"
|
title: 海风中失落的血色馈赠
|
||||||
pubDate: 2026-03-25
|
pubDate: 2026-03-25
|
||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags:
|
||||||
|
- read
|
||||||
mood: '#0f766e'
|
mood: '#0f766e'
|
||||||
bookAuthor: '阿利斯泰尔·麦克劳德'
|
bookAuthor: 阿利斯泰尔·麦克劳德
|
||||||
description: '秋是萧瑟之感:父亲、老马与少年,在失落后各自直面现实。'
|
description: 秋是萧瑟之感:父亲、老马与少年,在失落后各自直面现实。
|
||||||
|
cover: ./assets/海风中失落的血色馈赠.png
|
||||||
|
hiddenTag: false
|
||||||
|
draft: false
|
||||||
---
|
---
|
||||||
|
|
||||||
### 秋-斯科特之秋、父亲之秋、少年之秋(冬的预演)
|
### 秋-斯科特之秋、父亲之秋、少年之秋(冬的预演)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
title: "链接卡片样例"
|
||||||
|
pubDate: 2026-11-02
|
||||||
|
category: tech
|
||||||
|
tags: [test, hidden]
|
||||||
|
# 链接卡片(OG 预览)测试样张:hidden 不列出,构建验证用;保留为活样例
|
||||||
|
draft: false
|
||||||
|
---
|
||||||
|
|
||||||
|
本篇验证链接卡片:独立成段的裸链接转 OG 预览卡,内联链接保持普通样式。
|
||||||
|
|
||||||
|
## 正例:有 OG 的站点
|
||||||
|
|
||||||
|
https://wait4x.online/
|
||||||
|
|
||||||
|
https://astro.build/
|
||||||
|
|
||||||
|
## 反例:404 与无 OG 站点(应回落普通链接,构建不报错)
|
||||||
|
|
||||||
|
https://wait4x.online/definitely-not-a-real-page-404/
|
||||||
|
|
||||||
|
https://example.com/
|
||||||
|
|
||||||
|
## 内联链接保持普通样式(不应变卡)
|
||||||
|
|
||||||
|
这里有一个内联链接 [Astro 官网](https://astro.build/) 夹在句子中间,它必须保持普通链接样式,不能整篇变卡片墙。
|
||||||
|
|
||||||
|
## 卡片首行文字(markdown 链接形态同样触发)
|
||||||
|
|
||||||
|
[wait4x.online 首页](https://wait4x.online/)
|
||||||
@@ -14,7 +14,7 @@ draft: false
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图注写法:斜体单独成段,photos 目录样片副本(1600×2400 竖幅)。*
|
<p class="fig-cap">图注新写法:fig-cap 类,photos 目录样片副本(1600×2400 竖幅)。</p>
|
||||||
|
|
||||||
## 视频(raw HTML,H.264 320×240,ffmpeg testsrc2 生成)
|
## 视频(raw HTML,H.264 320×240,ffmpeg testsrc2 生成)
|
||||||
|
|
||||||
|
|||||||