Compare commits
216
Commits
5efbbb2f18
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a29e9c32cb | ||
|
|
ba83217048 | ||
|
|
ef2bd97267 | ||
|
|
5d187e1240 | ||
|
|
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 | ||
|
|
1fdd0cc162 | ||
|
|
aa050d8035 | ||
|
|
a347ce8baf | ||
|
|
8fe9455c23 | ||
|
|
b18d019a2e | ||
|
|
f57b1bde5f | ||
|
|
e57c853f20 | ||
|
|
a10141dfd8 | ||
|
|
bcfd0c35bf | ||
|
|
48a2383efa | ||
|
|
709b18eb45 | ||
|
|
dd1a59bc21 | ||
|
|
ad731aea69 | ||
|
|
93992cb515 | ||
|
|
2802278be6 | ||
|
|
147f00fe1c | ||
|
|
f8add5ad15 | ||
|
|
ad5f9d3d95 | ||
|
|
0e792a70cc | ||
|
|
cef648db9a | ||
|
|
40346c3f21 | ||
|
|
ce587236e2 | ||
|
|
90574edce7 | ||
|
|
3bd8369eda | ||
|
|
b789d30a5d |
No files matched your search
@@ -0,0 +1,92 @@
|
|||||||
|
# Phase 5c:裸 clone 构建 + 移动端布局排查 + Lighthouse 真测 + en/archives 补页 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-10-25 20:02
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- 320px 超窄 en 残溢记录为已知边界(现实机型 ≥360)
|
||||||
|
- 正文排版断点为可读性台阶(有意保留)
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- T1 裸 clone 构建改造(Sveltia 自动上线硬前置)
|
||||||
|
- T2 移动端布局全页排查(用户实测报告问题)
|
||||||
|
- T3 Lighthouse 移动端真测(Phase 4 欠账)
|
||||||
|
- T4 /en/archives/ 列表页补缺(现 404)
|
||||||
|
- T5 验证收官
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简单可行方案:构建仓 fetch+reset 硬跟 main;Header clamp 替换阶梯;字体 ?url 非阻塞+按轨挂载
|
||||||
|
- 暂不引入:hamburger 菜单、流体排版(正文有意台阶)
|
||||||
|
- 不做的抽象/配置化:多构建源
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- Sveltia 站内入口(下轮)、320px 超窄支持(已记录边界)
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- Sveltia 站内入口(下轮)、320px 超窄支持(已记录边界)
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:5/5
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-10-25 20:02
|
||||||
|
- 范围:Phase 5c:裸 clone 构建 + 移动端布局排查 + Lighthouse 真测 + en/archives 补页
|
||||||
|
- 功能:T1 裸 clone 构建改造(Sveltia 自动上线硬前置);T2 移动端布局全页排查(用户实测报告问题);T3 Lighthouse 移动端真测(Phase 4 欠账);T4 /en/archives/ 列表页补缺(现 404);T5 验证收官
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:48a2383efa796b36275f39380e2bd2c805f49690..48a2383efa796b36275f39380e2bd2c805f49690
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
响应式纪律:布局层流式(clamp/auto-fill/容器查询)+固定行 clamp 收缩+超窄内滚;验证=密集宽度扫描(320-768)。@fontsource CSS ?url+preload 非阻塞+按轨挂载(614 @font-face 曾致 Perf 62)。裸 clone 后 staging=origin/main,未推代码用本地 dist 临时口验证。spec 纪律:spec.md 必须当轮写完(模板占位会在归档门禁连环炸);verify 与 complete 之间零提交。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{"task": "task_0001", "cmd": "bash infra/deploy/build.sh && grep -q \"0e792a7\\|[a-f0-9]\\{7\\}\" ~/docker_apps/blog/www-staging/.build-stamp 2>/dev/null || bash infra/deploy/build.sh", "exit": 0, "duration_s": 8.418, "stdout_tail_sha256": "058885ce03c380062ca1fd981422cd0035ec6519c81f42ba7c284ea017c29328", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:14+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.408, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:29+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f /tmp/e2e-shots/lighthouse-report.json", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:29+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "pnpm astro build && test -f dist/en/archives/index.html", "exit": 0, "duration_s": 5.983, "stdout_tail_sha256": "716c18bb1350481ad0930179291341dd6d0622a6c2cc029d84cd4e5fd67f9e79", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:35+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.512, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "81a104fc3ebd61038f938c2d941d11747896a6c2494c5c0144641d3b44f6aa6d", "at": "2026-10-25T11:46:49+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash infra/deploy/build.sh && grep -q \"0e792a7\\|[a-f0-9]\\{7\\}\" ~/docker_apps/blog/www-staging/.build-stamp 2>/dev/null || bash infra/deploy/build.sh", "exit": 0, "duration_s": 6.931, "stdout_tail_sha256": "69918ee818f11f5a3ec97f35763d7feea09c48198237dc83711c555916ed6e9b", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:50:41+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 19.577, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:51:00+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f /tmp/e2e-shots/lighthouse-report.json", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:51:00+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "pnpm astro build && test -f dist/en/archives/index.html", "exit": 0, "duration_s": 6.947, "stdout_tail_sha256": "e23c70f1eaae7d14f05c341a03368cd098d32940deb8ec85842378195894e5d0", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:51:07+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 1, "duration_s": 14.487, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:51:22+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.536, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "35a4b2d05d6ebe56e564ca23cffe9ca3ed016d955387eeb098aafc2f909668da", "at": "2026-10-25T11:52:36+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash infra/deploy/build.sh && grep -q \"0e792a7\\|[a-f0-9]\\{7\\}\" ~/docker_apps/blog/www-staging/.build-stamp 2>/dev/null || bash infra/deploy/build.sh", "exit": 0, "duration_s": 7.396, "stdout_tail_sha256": "6e25b47f32694e788da65f94e8e04ad1b4e989b0a43c95599a2810c5bb05cb56", "tree_fp": "928f1bb96240f7215ba426974e5a9394b627a96ed4fc729c53e9be9dfc16277d", "at": "2026-10-25T11:55:16+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.559, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "928f1bb96240f7215ba426974e5a9394b627a96ed4fc729c53e9be9dfc16277d", "at": "2026-10-25T11:55:30+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f /tmp/e2e-shots/lighthouse-report.json", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "928f1bb96240f7215ba426974e5a9394b627a96ed4fc729c53e9be9dfc16277d", "at": "2026-10-25T11:55:30+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "pnpm astro build && test -f dist/en/archives/index.html", "exit": 0, "duration_s": 5.268, "stdout_tail_sha256": "f785c12360c62b0f6859cb2fb4b44d8e5e43df5c3e69c77cef10734b46b51404", "tree_fp": "928f1bb96240f7215ba426974e5a9394b627a96ed4fc729c53e9be9dfc16277d", "at": "2026-10-25T11:55:35+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.491, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "928f1bb96240f7215ba426974e5a9394b627a96ed4fc729c53e9be9dfc16277d", "at": "2026-10-25T11:55:50+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash infra/deploy/build.sh && grep -q \"0e792a7\\|[a-f0-9]\\{7\\}\" ~/docker_apps/blog/www-staging/.build-stamp 2>/dev/null || bash infra/deploy/build.sh", "exit": 0, "duration_s": 6.389, "stdout_tail_sha256": "b81e216373ca24382cdc40ca95fef60f49354bfd7503f9e1dfc331f29a06ac2f", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T11:58:21+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.469, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T11:58:36+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f /tmp/e2e-shots/lighthouse-report.json", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T11:58:36+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "pnpm astro build && test -f dist/en/archives/index.html", "exit": 0, "duration_s": 5.504, "stdout_tail_sha256": "4cce1df670e136194f3948ab1e2adea77fcc503397cfe10bb308f0a88cd25cf3", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T11:58:41+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.425, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T11:58:56+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "bash infra/deploy/build.sh && grep -q \"0e792a7\\|[a-f0-9]\\{7\\}\" ~/docker_apps/blog/www-staging/.build-stamp 2>/dev/null || bash infra/deploy/build.sh", "exit": 0, "duration_s": 6.665, "stdout_tail_sha256": "d32d454fb8b15f14778a5dadabf84e948a7599e56e9e6b71a0461170ada259a0", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T12:01:25+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.424, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T12:01:39+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f /tmp/e2e-shots/lighthouse-report.json", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T12:01:39+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "pnpm astro build && test -f dist/en/archives/index.html", "exit": 0, "duration_s": 5.079, "stdout_tail_sha256": "1fc2c0b4b5b5d5d14eb765e97d0bbcac5cc9a79b3cfb7c4dac486d84ba211333", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T12:01:45+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.408, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b2d37e7c0927340b6545abc81df7d6916cf8fd84cf28e02307a018cf02e3ed17", "at": "2026-10-25T12:01:59+00:00"}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Phase 5c:裸 clone 构建 + 移动端布局排查 + Lighthouse 真测 + en/archives 补页 - 项目范围
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:发布管线确定性(构建源=origin/main);移动端全站溢出修复(用户实测报告);Lighthouse 真分数与 P0 性能优化落地;en/archives 404 补页
|
||||||
|
- **目标用户**:站长(发布/管理)与移动端读者
|
||||||
|
- **核心价值**:Sveltia 保存即上线的硬前置落地;手机体验从"可晃动破版"到全宽段合身
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- 移动审计实锤:Header 7 项一行在 390px 溢出 51-88px(B1);.prose-tech 缺断词(M1)
|
||||||
|
- Lighthouse 诊断:614 个 @font-face 708KB 渲染阻塞=Perf 62 元凶
|
||||||
|
- blog 仓公开 → 裸 clone 匿名 fetch 可行
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- 320px 超窄 en 残溢记录为已知边界(现实机型 ≥360)
|
||||||
|
- 正文排版断点为可读性台阶(有意保留)
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 页面自适应方式 → 结论:布局层流式(clamp/auto-fill)+ Header clamp 连续收缩 + <350px 导航内滚兜底(用户设计诉求"根据设备自适应"驱动)
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- clamp 流式 > 阶梯断点补丁(任意宽度合身,无逐档维护)-> 选流式
|
||||||
|
- 导航内滚 < hamburger 抽屉(保单行设计语言,改动最小)-> 选内滚
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 阶梯断点逐档补丁(430/370 两轮后仍要第三档——治标)
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 裸 clone 构建改造
|
||||||
|
- [x] T2 移动端布局全页排查与修复
|
||||||
|
- [x] T3 Lighthouse 真测与 P0/P1 优化落地
|
||||||
|
- [x] T4 /en/archives/ 补页
|
||||||
|
- [x] T5 验证收官
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- Sveltia 站内入口(下轮)、320px 超窄支持(已记录边界)
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简单可行方案:构建仓 fetch+reset 硬跟 main;Header clamp 替换阶梯;字体 ?url 非阻塞+按轨挂载
|
||||||
|
- 暂不引入:hamburger 菜单、流体排版(正文有意台阶)
|
||||||
|
- 不做的抽象/配置化:多构建源
|
||||||
|
|
||||||
|
## 5. 技术决策
|
||||||
|
- 技术栈:git fetch/reset、CSS clamp、@fontsource ?url、Playwright 密集宽度扫描
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- 本轮允许改动:infra/deploy/、src/components/Header.astro、src/styles/global.css、src/pages/、scripts/lighthouse/
|
||||||
|
- 本轮不应触碰:nginx、gitea 运行时、生产内容
|
||||||
|
- Git integration branch:`spec/2026-10-25_add-blog-5c`
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:构建管线、Header 流式化、移动端缺陷修复面、字体加载策略、en/archives 页
|
||||||
|
- 快速检查:`pnpm astro build`
|
||||||
|
- 集成检查:`node tests/e2e.mjs`(30 项)+ 移动溢出密集扫描(宽段 320-768 × scrollWidth 断言)
|
||||||
|
- 全项目检查:Lighthouse 复跑对账(Perf 62→88 基线)+ 生产移动端抽查;仅在构建链或 Header 结构再变更时升级范围
|
||||||
|
- 升级触发:仅在 nginx 面、构建链、发布载荷变更时升级验证范围并记录理由
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] 移动溢出密集扫描 44 检全过(320-768) -> verify: task_0002
|
||||||
|
- [x] staging 恒等 origin/main(裸 clone 构建) -> verify: task_0001
|
||||||
|
- [x] Lighthouse Perf 62→88 实测 -> verify: task_0003
|
||||||
|
- [x] en/archives 200 -> verify: task_0004
|
||||||
|
|
||||||
|
## 7. 风险与约束
|
||||||
|
- ESA 边缘缓存旧页会伪装修复失效(刷新后为准);构建仓 node_modules 留存做增量(loader 逻辑变更须清 .astro 数据仓——前包教训)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Phase 5c:裸 clone 构建 + 移动端布局排查 + Lighthouse 真测 + en/archives 补页 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- boundary/verify 齐备;verify 只读断言
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] T1 裸 clone 构建改造(Sveltia 自动上线硬前置)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: 只改 infra/deploy/build.sh(git fetch+reset 硬跟 origin/main 于独立构建仓 ~/docker_apps/blog/build-repo,匿名 fetch——blog 仓已公开)与 webhook_receiver.py 的 BUILD 常量;webhook 触发实测 main 提交自动上线
|
||||||
|
- verify: `bash infra/deploy/build.sh && grep -q "0e792a7\|[a-f0-9]\{7\}" ~/docker_apps/blog/www-staging/.build-stamp 2>/dev/null || bash infra/deploy/build.sh`
|
||||||
|
|
||||||
|
- [x] T2 移动端布局全页排查(用户实测报告问题)
|
||||||
|
- claim: orchestrator + 视觉车道
|
||||||
|
- boundary: 390px/360px 双宽 × zh/en × 亮/暗全页截图交视觉模型;发现的缺陷修复;不重构整体布局只修缺陷
|
||||||
|
- verify: `node tests/e2e.mjs 2>&1 | grep -q "30/30 PASS"`
|
||||||
|
|
||||||
|
- [x] T3 Lighthouse 移动端真测(Phase 4 欠账)
|
||||||
|
- claim: op-gitea(mesh 车道)
|
||||||
|
- boundary: lighthouse CI 对本机 staging 18743 跑 mobile emulation 五页型(首页/文章/图库/en 首/terminal);记录分数;<90 项出可执行优化清单(不改代码,修复下一轮)
|
||||||
|
- verify: `test -f /tmp/e2e-shots/lighthouse-report.json`
|
||||||
|
|
||||||
|
- [x] T4 /en/archives/ 列表页补缺(现 404)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: src/pages/en/archives/index.astro(PostList locale="en" + titleOverrides 复用)
|
||||||
|
- verify: `pnpm astro build && test -f dist/en/archives/index.html`
|
||||||
|
|
||||||
|
- [x] T5 验证收官
|
||||||
|
- depends-on: task_0001, task_0002, task_0003, task_0004
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: E2E + 移动端修复后视觉复审 + 归档推送上线
|
||||||
|
- verify: `node tests/e2e.mjs 2>&1 | grep -q "30/30 PASS"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**当前进度**:由脚本计算,无需手动维护
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Phase 5b:/terminal 彩蛋 + /books 书评画廊 + Sveltia 管理台试水 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-10-25 13:46
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- Sveltia 内建图片优化保持关闭(开了会抹 EXIF 断构建期提取管线)
|
||||||
|
- 书评 description 由正文首段精炼可接受(用户后续可自行策展润色)
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- T1 /terminal 终端彩蛋页
|
||||||
|
- T2 /books 书评画廊
|
||||||
|
- T3 Sveltia 管理台试水
|
||||||
|
- T5 /opensource 开源独立页(用户追加需求)
|
||||||
|
- T4 验证收官
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简单可行方案:三车道并行按预研方案实施;Sveltia 试水不改现有任何流(只加不改)
|
||||||
|
- 暂不引入:终端插件化命令系统、书封真实图片扫描
|
||||||
|
- 不做的抽象/配置化:多管理后端适配
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- moments PWA 上传端点、/stack·/colophon 页(后续池)
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- moments PWA 上传端点、/stack·/colophon 页(后续池)
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:5/5
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git Records
|
||||||
|
- Date Time:2026-10-25 13:46
|
||||||
|
- Scope:Phase 5b:/terminal 彩蛋 + /books 书评画廊 + Sveltia 管理台试水
|
||||||
|
- Feature:T1 /terminal 终端彩蛋页;T2 /books 书评画廊;T3 Sveltia 管理台试水;T5 /opensource 开源独立页(用户追加需求);T4 验证收官
|
||||||
|
- Action:Commit / Push
|
||||||
|
- Effect:Development Record 行为门禁通过
|
||||||
|
- Commit:pending local commit
|
||||||
|
- 提交区间:f1ee07838d577125f8f1677e6b662a3bb320ce32..1b610f0bbf430aae3f001cf0c8138ab775fc0662
|
||||||
|
- Push:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
Astro 7 内容层双坑(EXIF 全空调试半日定位):①loader 包装层在 baseLoader.load() 之后 ctx.store.set 的字段会被解析阶段静默丢弃——必须拦截 baseLoader 的写入时机内联增强(代理 store.set,enrich Promise 收集后 await);②持久化数据仓(node_modules/.astro)增量复用旧条目——改 loader 逻辑后必须清数据仓或换 loader name,否则旧值复利胜出。团队协作:车道临时服务端口要与主验证端口分离(18742 Sveltia 冲突教训);多车道并发时 add -A 会卷入他车道文件(提交信息如实备案或收窄 add)。Sveltia×gitea 机制:整文件 JSON=sidecar 同构、删条目连带独占 jpg(实测推翻文档)、omit_empty_optional_fields 会丢必需空字段。裸 clone 构建改造是 Sveltia 自动上线的硬前置(下一包首任务)。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{"task": "task_0001", "cmd": "pnpm astro build && grep -q \"terminal\" dist/terminal/index.html", "exit": 0, "duration_s": 5.636, "stdout_tail_sha256": "8523a98a4dd54e5d8b6298e9f9e56fc6eb56afd1d70821ce5c1a0a8d871673a7", "tree_fp": "9325637dacb5cbd2a4a86329e669a85bbf5428735a2bb72c5ad7a82e00ea1624", "at": "2026-10-25T05:42:41+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "pnpm astro build && test -f dist/books/index.html -a -f dist/en/books/index.html", "exit": 0, "duration_s": 4.88, "stdout_tail_sha256": "b05fcfb369a91ea39a036fc67dd5a29bcefe1db26e624bfd9b7a678b40f57b87", "tree_fp": "9325637dacb5cbd2a4a86329e669a85bbf5428735a2bb72c5ad7a82e00ea1624", "at": "2026-10-25T05:42:46+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f public/admin/config.yml && docker exec gitea grep -q \"cors\" /data/gitea/conf/app.ini", "exit": 0, "duration_s": 0.094, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "9325637dacb5cbd2a4a86329e669a85bbf5428735a2bb72c5ad7a82e00ea1624", "at": "2026-10-25T05:42:46+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.517, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "9325637dacb5cbd2a4a86329e669a85bbf5428735a2bb72c5ad7a82e00ea1624", "at": "2026-10-25T05:43:01+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "pnpm astro build && grep -q \"terminal\" dist/terminal/index.html", "exit": 0, "duration_s": 10.825, "stdout_tail_sha256": "39e841a84204b8c060604577adb41f13605ef755f90a4dac28ae4ed7f6b07dee", "tree_fp": "9325637dacb5cbd2a4a86329e669a85bbf5428735a2bb72c5ad7a82e00ea1624", "at": "2026-10-25T05:45:10+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "pnpm astro build && test -f dist/books/index.html -a -f dist/en/books/index.html", "exit": 0, "duration_s": 4.703, "stdout_tail_sha256": "167f39f390b7753e6f76bab112f849fca4f16d8fa06e319b0e5363c6fb7ba8eb", "tree_fp": "9325637dacb5cbd2a4a86329e669a85bbf5428735a2bb72c5ad7a82e00ea1624", "at": "2026-10-25T05:45:15+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "test -f public/admin/config.yml && docker exec gitea grep -q \"cors\" /data/gitea/conf/app.ini", "exit": 0, "duration_s": 0.088, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "9325637dacb5cbd2a4a86329e669a85bbf5428735a2bb72c5ad7a82e00ea1624", "at": "2026-10-25T05:45:15+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "pnpm astro build && test -f dist/opensource/index.html -a -f dist/en/opensource/index.html", "exit": 0, "duration_s": 5.218, "stdout_tail_sha256": "0975341de4c977c9261a1b80c4619480804a65775c330f6bb6a389c73d158868", "tree_fp": "9325637dacb5cbd2a4a86329e669a85bbf5428735a2bb72c5ad7a82e00ea1624", "at": "2026-10-25T05:45:20+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.389, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "9325637dacb5cbd2a4a86329e669a85bbf5428735a2bb72c5ad7a82e00ea1624", "at": "2026-10-25T05:45:34+00:00"}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Phase 5b:/terminal 彩蛋 + /books 书评画廊 + Sveltia 管理台试水 - 项目范围
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:三个预研方案落地——终端彩蛋(极客人格记忆点)、书评索引画廊(阅读轨策展面)、Sveltia 管理台(照片上传/编辑体验升级试水)
|
||||||
|
- **目标用户**:站长(写作/管理体验)与读者(浏览乐趣)
|
||||||
|
- **核心价值**:二期池前三项交付;Sveltia 试水失败可全身而退
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- 三方案预研完成(op-artalk A+B / op-gitea Sveltia,含坑清单与工作量)
|
||||||
|
- Sveltia 机制全对上:整文件 JSON=sidecar 同构、media_folder '' 裸文件名、token 登录、保存即 commit→webhook 自动构建
|
||||||
|
- gitea CORS 未配是唯一硬前置
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- Sveltia 内建图片优化保持关闭(开了会抹 EXIF 断构建期提取管线)
|
||||||
|
- 书评 description 由正文首段精炼可接受(用户后续可自行策展润色)
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] Sveltia 试水体验是否合手 → 结论:值得留(双冒烟过,编辑器 aria/草稿保存扎实);生产自动化待裸 clone 改造(下一包首任务)
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- /books 不进主导航(书评列表行内入口)> 导航加第五项 -> 防膨胀
|
||||||
|
- 色脊排版卡 > 满色书封卡(mood 深浅不一跨亮暗风险)-> 色脊
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 /terminal 彩蛋页
|
||||||
|
- [x] T2 /books 书评画廊(zh/en)
|
||||||
|
- [x] T3 Sveltia 管理台试水(含 gitea CORS)
|
||||||
|
- [x] T5 /opensource 开源独立页(2026-10-25 用户追加:与文章/图库/关于平权)
|
||||||
|
- [x] T4 验证收官
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- /books 进导航(若用户要求)
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- moments PWA 上传端点、/stack·/colophon 页(后续池)
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简单可行方案:三车道并行按预研方案实施;Sveltia 试水不改现有任何流(只加不改)
|
||||||
|
- 暂不引入:终端插件化命令系统、书封真实图片扫描
|
||||||
|
- 不做的抽象/配置化:多管理后端适配
|
||||||
|
|
||||||
|
## 5. 技术决策
|
||||||
|
- 技术栈:预研方案原样(vanilla TS 终端 / 色脊卡静态页 / Sveltia 官方集成)
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- 本轮允许改动:src/pages/、src/content.config.ts、content/posts/(五篇补字段)、public/admin/、infra/gitea/README、gitea 运行时 [cors]
|
||||||
|
- 本轮不应触碰:content/photos 既有 sidecar 结构、发布链、生产 nginx
|
||||||
|
- Git integration branch:`spec/2026-10-25_add-terminal-books-admin`
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:两个新页面、posts schema 扩展、admin 静态目录、gitea 运行时
|
||||||
|
- 快速检查:`pnpm astro build`
|
||||||
|
- 集成检查:terminal/books 产物断言 + Sveltia 双冒烟走通 commit→webhook→build
|
||||||
|
- 全项目检查:E2E 30 项 + 新页视觉复核;仅在 gitea 运行时或发布链变更时升级范围
|
||||||
|
- 升级触发:只有 Sveltia 冒烟引发 gitea 行为异常时,才升级验证范围并记录理由
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] /terminal 可交互(help/ls/theme/sudo 彩蛋实跑) -> verify: task_0004
|
||||||
|
- [x] /books zh/en 五书呈现(色脊+mood) -> verify: task_0002
|
||||||
|
- [x] Sveltia 冒烟双通过(改+建全链) -> verify: task_0003
|
||||||
|
|
||||||
|
## 7. 风险与约束
|
||||||
|
- gitea restart 窗口(秒级,公告无需);Sveltia 保存直连 main——已有 webhook 管线为安全网
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Phase 5b:/terminal 彩蛋 + /books 书评画廊 + Sveltia 管理台试水 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- boundary/verify 齐备;verify 只读断言;三车道并行(mesh_reserve 各自路径)
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] T1 /terminal 终端彩蛋页
|
||||||
|
- claim: op-artalk(预研方案 A 已在案:命令注册表/真实 input/历史翻阅/Tab 补全/sudo 彩蛋/XSS=textContent 纪律/isComposing 守卫)
|
||||||
|
- boundary: 新建 src/pages/terminal.astro 单文件组件(--code-bg 恒深色例外区);不进主导航(? 帮助浮层加一行暗入口提示);零外部依赖
|
||||||
|
- verify: `pnpm astro build && grep -q "terminal" dist/terminal/index.html`
|
||||||
|
|
||||||
|
- [x] T2 /books 书评画廊
|
||||||
|
- claim: orchestrator(预研方案 B 在案:色脊排版卡/年份分组/规整网格)
|
||||||
|
- boundary: posts schema 加可选 description/book_author 两字段;新建 src/pages/books.astro(+en/books);五篇书评补字段(description 用正文首段精炼,book_author 按书实填);导航"文章"旁不加新项——/books 从书评列表行入口链接(防导航膨胀)
|
||||||
|
- verify: `pnpm astro build && test -f dist/books/index.html -a -f dist/en/books/index.html`
|
||||||
|
|
||||||
|
- [x] T3 Sveltia 管理台试水
|
||||||
|
- claim: op-gitea(预研方案在案:CORS 前置/config.yml 草案/双冒烟/五坑清单)
|
||||||
|
- boundary: gitea app.ini 加 [cors] 段并重启(运行时操作,README 台账补记);public/admin/{index.html,config.yml,sveltia 本地 js};双冒烟(改旧 sidecar 补 alt / 新建条目传图走全链);不加站内链接(试水期)
|
||||||
|
- verify: `test -f public/admin/config.yml && docker exec gitea grep -q "cors" /data/gitea/conf/app.ini`
|
||||||
|
|
||||||
|
- [x] T5 /opensource 开源独立页(用户追加需求)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: ProjectWall 加 limit prop(默认 6 不变首页);opensource.astro + en/opensource.astro(全量仓+热力图+开源叙事);Header 导航第四项(i18n nav_opensource);BooksPage 回退契约与自托管徽标随后续提交补强
|
||||||
|
- verify: `pnpm astro build && test -f dist/opensource/index.html -a -f dist/en/opensource/index.html`
|
||||||
|
|
||||||
|
- [x] T4 验证收官
|
||||||
|
- depends-on: task_0001, task_0002, task_0003
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: E2E 复跑 + terminal/books 截图视觉复核 + Sveltia 冒烟结论 + 归档推送
|
||||||
|
- verify: `node tests/e2e.mjs 2>&1 | grep -q "30/30 PASS"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**当前进度**:由脚本计算,无需手动维护
|
||||||
@@ -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`
|
||||||
+40
-1
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
站点更新记录:瞬间富文本与逐条评论上线。
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 19 MiB |
@@ -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,6 @@
|
|||||||
|
{
|
||||||
|
"title": "夜色金茂湾",
|
||||||
|
"image": "1000039852_0_1_20397409.jpg",
|
||||||
|
"group": "landscape",
|
||||||
|
"caption": "小船儿飘呀飘"
|
||||||
|
}
|
||||||
@@ -3,18 +3,33 @@
|
|||||||
"sha256": "bd23b63a6453829fe3dccd4e261f2e9526917edc5d2fb8e23cac7e7371db140f"
|
"sha256": "bd23b63a6453829fe3dccd4e261f2e9526917edc5d2fb8e23cac7e7371db140f"
|
||||||
},
|
},
|
||||||
"posts/deng-dai-ge-duo.md": {
|
"posts/deng-dai-ge-duo.md": {
|
||||||
"sha256": "45dee1a0c7572ac0e18392798cc9c9fd755bc64562bf87872ed238de72b30892"
|
"sha256": "936c0cc3fbdc9c9510b685de479c3160ef7d3aa23a685b902f1b020dec1a1c66"
|
||||||
},
|
},
|
||||||
"posts/ni-tan.md": {
|
"posts/ni-tan.md": {
|
||||||
"sha256": "07881bcae9ab5549ed888af4abbf314a487d29bdc03b22a60d0a19977c30321f"
|
"sha256": "1fa83fb0aa6a5909c6383cccf52501a61db2d65505903a34e640204c73ccb81a"
|
||||||
},
|
},
|
||||||
"posts/shu-zui.md": {
|
"posts/shu-zui.md": {
|
||||||
"sha256": "da9c4603b7e9bb03336466bbd725df2b25230d2d1e6621c8cfd616cfdc6b7ae2"
|
"sha256": "faa724ae11121af6206b976a1f1e2cf1ea17ad4c6a823f1c1515f0659bae673f"
|
||||||
},
|
},
|
||||||
"posts/hai-feng-zhong-shi-luo-de-xie-se-kui-zeng.md": {
|
"posts/hai-feng-zhong-shi-luo-de-xie-se-kui-zeng.md": {
|
||||||
"sha256": "52d1221b6193600b54d1bd4676786604207662651769ed503802de5555d87edc"
|
"sha256": "3affd0c78ac14610ace4c7f7d814ac4dd15f94e677a74e30780e081ecd91a930"
|
||||||
},
|
},
|
||||||
"posts/mian-sha.md": {
|
"posts/mian-sha.md": {
|
||||||
"sha256": "0f80f12b0a3811d026e8300e49921c4a14056b5b04b8cb61b04b04afdcec1eb5"
|
"sha256": "31b00cb598e703742438375728dc6623f25975d1ee414a9eaac8daba940459d3"
|
||||||
|
},
|
||||||
|
"posts/fix3-test.md": {
|
||||||
|
"sha256": "61defdfdd1bb47bcf927e777e0009261c874b5b9764fd605eb79a8c34d30b491"
|
||||||
|
},
|
||||||
|
"posts/fonts-test.md": {
|
||||||
|
"sha256": "3c594c19ec8e3192b5228273f79b326d140c65624d2361e006ab4a3d1212a1f2"
|
||||||
|
},
|
||||||
|
"posts/link-cards-test.md": {
|
||||||
|
"sha256": "c1908338d20f77571433a478493a466ef46ab2b21b42d1ca4496c3e5a14875fd"
|
||||||
|
},
|
||||||
|
"posts/media-sample.md": {
|
||||||
|
"sha256": "727d82a59fd4f2a910a0a4bd2123bd64a80e369ced581d100cf1b140dec7de2c"
|
||||||
|
},
|
||||||
|
"posts/佩德罗·巴拉莫.md": {
|
||||||
|
"sha256": "982a011b4894743c64016008320f0358090a202b7de5542b67821eb3b44179d5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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/)
|
||||||
@@ -2,61 +2,64 @@
|
|||||||
title: "Waiting for Godot"
|
title: "Waiting for Godot"
|
||||||
pubDate: 2026-03-31
|
pubDate: 2026-03-31
|
||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags:
|
||||||
|
- read
|
||||||
|
pinned: false
|
||||||
mood: '#b45309'
|
mood: '#b45309'
|
||||||
|
bookAuthor: 萨缪尔·贝克特
|
||||||
|
description: 无望之待里,陪伴是囚笼唯一的呼吸缝隙。
|
||||||
|
cover: ./assets/等待戈多.png
|
||||||
|
hidden: false
|
||||||
|
draft: false
|
||||||
|
locked: true
|
||||||
---
|
---
|
||||||
|
|
||||||
### Who is Gordo? Why wait?
|
### Who Is Godot? Why Wait?
|
||||||
|
|
||||||
**Hopeless Waiting: Self-Salvation and the Prisoner’s Dilemma**
|
**Hopeless Waiting: Self-Salvation and the Predicament of the Cage**
|
||||||
|
|
||||||
Godo is the character who runs through the entire book. However, no one has actually seen Godo in reality. Gogo and Didi simply wait for Godo. Is Godo a kind of hope? Or perhaps a belief? Waiting, waiting… Sometimes, they even forget about life itself, choosing to spend their time waiting by committing suicide. So what’s the meaning of waiting for Godo? Isn’t it a spiritual pursuit and a way of redemption? Or maybe waiting for Godo has become their only goal in life. They don’t know what the purpose of waiting is, but waiting for Godo is the only thing they can do, and it’s also the only way for them to escape reality. Whether Godo exists or not doesn’t matter anymore. What matters is the act of “waiting” itself, which serves as a reminder that they haven’t completely lost their physical vitality.
|
Godot is the character who runs through the entire book, yet not one person has ever seen him. Gogo and Didi simply go on waiting for Godot. Is Godot a kind of hope? Or perhaps a kind of faith? As the waiting drags on, it can leave Gogo and Didi indifferent even to life itself — they even treat suicide as a way of whiling away the hours of waiting. What, then, is the meaning of waiting for Godot? Is it not a hope for living, but a spiritual pursuit, a form of redemption? Or has waiting for Godot already become their only purpose in life — they no longer know what the waiting means, but waiting for Godot is the only thing left for them to do, the only straw to clutch at in escaping reality. Whether Godot exists no longer matters; what matters is the act of "waiting" itself, which has become the physiological rhythm by which they confirm that they have not yet come completely apart.
|
||||||
|
|
||||||
Perhaps Gordo doesn’t actually exist at all. Gordo is simply a way for Gogo and Didi to escape the real world. It’s the only way they can feel like they still have something to do. It’s a kind of absurd waiting, a waiting that has no meaning at all. And the process of waiting itself is also absurd. Gogo and Didi wait just because they have to wait. The waiting period becomes a very desolate experience for them. They don’t know what to do, they don’t know how to pass the time during this waiting. They just wait, waiting for Gordo to arrive.
|
Perhaps Godot does not exist at all. Godot is simply a way for Gogo and Didi to escape the real world — the only way they can feel they still have something to do. It is an absurd kind of waiting, a waiting utterly without meaning. The process of waiting is itself absurd: Gogo and Didi seem to wait for the sake of waiting, and the waiting becomes a stretch of utterly barren time. They do not know what to do; they do not know how to pass the hours of waiting. They simply wait, waiting for Godot.
|
||||||
|
|
||||||
The way humans sustain themselves in the meaningless world is often not through dramatic resistance, but through trivial, repetitive, and even ridiculous daily efforts. This is both a form of compromise and a kind of tragic resilience. However, why not take the brave step of dying, or breaking away from the status quo, and follow the passing of time? Why stay stuck in the present, trapped in the past? Perhaps dying requires determination, and breaking free requires a clear direction. But when both these resources are exhausted, “waiting” becomes the only viable action left.
|
The way human beings keep themselves alive amid meaninglessness is rarely a spectacular act of rebellion; more often it is the trivial, repetitive, even laughable persistence of daily routine. This is both a compromise and a kind of tragic resilience. Yet why keep clinging to this meaningless waiting — why not bravely go to death, or break the status quo and follow time as it flows away, instead of being trapped in this moment, trapped in the past? Perhaps dying takes resolve, and breaking out takes a direction. And when both have been drained away, "waiting" becomes the only verb still left to execute.
|
||||||
|
|
||||||
Didi always ponders back in time, seeking consistency within the passage of time. Meanwhile, Gogo simply wants to live in the present. We are constantly torn between “trying to understand meaning” and “being forced to endure the moment at hand”. No one truly lives in the future—because it seems that no one has a future at all. The future is merely a waiting period, a constant state of anticipation. People go through day after day, resisting the real world and life in their own unique ways. They coexist with time, sometimes even opposing it, as if to prove that they still exist, and that what they do makes sense.
|
Didi keeps interrogating the past, seeking consistency within time; Gogo wants only to live in the present. We are forever torn between "trying to sort out meaning" and "being able only to endure the moment at hand". No one lives in the future, for it seems that no one has a future at all: the future is only waiting — forever living inside waiting, inside the same waiting repeated day after day. Within that day-after-day repetition, everyone resists the real world and their own life in a way uniquely their own, living with time and fighting against it, as if to prove that they still exist, that what they are doing still means something.
|
||||||
|
|
||||||
So, in this absurd life… time continues to pass as usual. What should we do? With what can we confront emptiness? When waiting becomes fate, when the future is reduced to mere “tomorrow” over and over again, how should we face this inevitable passage of time?
|
So, under this absurd life — time keeps passing all the same. What should we do? With what should we confront the void? When waiting becomes fate, when the future is diluted into a repeated "tomorrow", with what bearing should we face this time that is bound to slip away?
|
||||||
|
|
||||||
Between Decline and Rebirth: The Anchorage of Memories and the Flow of Time
|
**Between Withering and Blooming: The Anchor of Memory and the Flow of Time**
|
||||||
|
|
||||||
There are five characters in the play, but it seems that only Didi is awake. Only he remembers the past, remembers what happened yesterday. Gogo, Pozo, the lucky guy, and the young man none of them remember anything that happened yesterday. It seems as though they are always living in the present. In reality, time continues to pass. The growth of green leaves on that tree signifies that everything keeps moving forward. They live in the present, but they also bear witness to the past and step towards the future. However, every day that passes is just a repetition of the same events, just like the constant discussions about suicide. Going to death is merely a way to pass time, without truly having the courage to take those steps.
|
There are five characters in the play, yet only Didi seems truly awake — only he remembers the past, remembers what happened yesterday. Gogo, Pozzo, Lucky, and the Boy remember nothing of yesterday; they seem to live forever in the present. In reality, time has been flowing all along: the tree putting out green leaves means that everything is still moving forward. They live in the present, but they have witnessed the past, and they are stepping toward the future — only the step always lands on a repeated today, much like the ever-repeated talk of hanging. Going to death is merely one way of passing the time; they lack the courage for any real death.
|
||||||
|
|
||||||
The lucky person’s incoherent speeches in the play resemble both rational desperation and absurd reflections of reality. When ordered to think, his monologues initially follow a logical structure, using various terms and concepts. But soon, they become chaotic, with words failing to form any coherent meaning at all. It’s as if people try to use reason to combat absurdity, to explain the world that has lost its logic through thought… but all their efforts are in vain, and everything ultimately leads to nothingness.
|
Lucky's long, disordered speech in the play reads like a requiem for a collapsing reason, and like a mirror of absurdity. When Lucky is ordered to think, his monologue begins within a logical framework, citing all kinds of terminology, but it soon sinks into chaos — words piling up without order or method. It is as if people wanted to fight absurdity with reason, wanted to explain, by thinking, a world that has itself lost its logic — but every such effort is futile, and everything must return to nothingness in the end.
|
||||||
|
|
||||||
On the other hand, Didi’s clarity seems to be another form of luck. He constantly tries to piece together fragments of memories to recall the past—that past which no one else remembers anymore. No one remembers yesterday; only Didi does. Didi fights against the passage of time with his memories, enduring pain yet remaining awake. Gogo, on the other hand, tries to ease the burden of hope, feeling numb but at the same time relaxed. For those around him, time simply doesn’t exist at all—it’s a state of utter absurdity. Dead trees grow leaves, witnessing the changes in the world, but people remain stuck in the same place, cycling endlessly. Didi is aware of the passing of time, but he still remains trapped in the cage of time. All he can do is wait quietly for Gotham to arrive.
|
Seen the other way round, Didi's lucidity is like another form of Lucky: he keeps trying to piece together yesterday out of fragments of memory — a yesterday that no longer exists in anyone else's memory. No one remembers yesterday; only Didi does. Didi sets memory against the passing of time — painful but awake; Gogo dissolves the weight with hope — numb but at ease; for the others, time simply does not exist to begin with, an absurdity carried to its extreme. The bare tree grows leaves, bearing witness to the world's change, while the human situation keeps circling in the same place. Didi sees the passing of time with perfect clarity, yet he remains trapped in time's cage, and all he can do is still to wait, quietly, for Godot.
|
||||||
|
|
||||||
A tree can be dead, or it can still have leaves.
|
***A tree can stand bare, or it can put out leaves;***
|
||||||
Waiting can be futile, or it can be a proof of existence.
|
***Waiting can be futile, or it can be the proof of existence;***
|
||||||
Memory can be a burden, or it can serve as a point of reference.
|
***Memory can be a burden, or it can be an anchor;***
|
||||||
Absurdity can represent despair, or it can be the beginning of freedom.
|
***Absurdity can be despair, or it can be the starting point of freedom.***
|
||||||
|
|
||||||
Perhaps in this absurd world, in this meaningless life, there are times when we don’t need to “overcome” the void. Sometimes, simply “continuing” is enough to bring meaning. Just like that tree—between its blooming and withering, that is the answer.
|
Perhaps in an absurd world, in a life of nothingness, there is sometimes no need to "defeat" the void. Sometimes, "continuing" is itself the meaning. Just like that tree — between withering and blooming, therein lies the answer.
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
- Estragon: (after a long silence) What you’re saying is the opposite of what I said, right?
|
- Estragon: (after thinking for a good while) What you are saying is the exact opposite of what I said, is that it? Vladimir: It's a question of temperament. Estragon: Of character. Vladimir: There is nothing anyone can do about it. Estragon: Striving is no use. Vladimir: One is what one is born to be. Estragon: Struggling is no use either. Vladimir: Rivers and mountains may change, but a man's nature never does.
|
||||||
Fradimir: It’s a matter of temperament.
|
|
||||||
Estragon: It’s a matter of personality.
|
|
||||||
Fradimir: People are powerless against this.
|
|
||||||
Estragon: It’s useless to try anymore.
|
|
||||||
Fradimir: One’s nature remains unchanged, no matter how hard one tries to change it.
|
|
||||||
|
|
||||||
The more people I meet, the happier I become. Even when dealing with the most humble people, I feel like I’m gaining new experiences, enriching my knowledge, and able to appreciate happiness in a better way.
|
- The more people I meet, the happier I am. Even in dealings with the lowliest of men, you can feel yourself gaining in knowledge, enriching your experience, tasting happiness all the better.
|
||||||
|
|
||||||
- There is a certain amount of tears that can be shed in this world. When someone in one place starts crying, there must be someone else who stops crying in another place. The same goes for laughter. (He laughs.) Therefore, we shouldn’t speak ill of our time. It isn’t any worse than times in the past. (Silence.) Nor should we speak well of our time either.
|
- The tears of the world hold to a fixed measure. Somewhere someone weeps, and somewhere else someone must stop weeping. The same holds for laughter. (He laughs.) So let us not speak ill of our times: it is no worse than the times before it. (Silence.) Nor let us speak well of it.
|
||||||
|
|
||||||
- Without him, my thoughts and feelings would probably always be confined to the trivial and mundane matters of everyday life—things that don’t really matter at all. As for what constitutes the highest form of beauty, goodness, or truth, I truly cannot attain it. I also know that I simply don’t deserve it at all.
|
- Without him, my thinking, my feeling, would no doubt have stayed confined to mediocre trifles, to the vulgar business of keeping a family fed — nothing that mattered in the least. As for supreme beauty, supreme goodness, supreme truth, I could never reach them; I know full well I am not worthy of them.
|
||||||
|
|
||||||
- What exactly is so extraordinary about it? Is it actually the heavens themselves? It’s pale yet bright, just like the sky at any moment during the day. (Loudon) The same sky, in the same direction.
|
- What is there about it that is so extraordinary? That it is the sky itself? It is pale and bright, like the sky anywhere at this hour of the day. (Pause.) The same sky, in the same quarter.
|
||||||
|
|
||||||
The lights suddenly dimmed. For a moment, the sky turned dark. The moon rose at the end of the stage, climbing higher and higher, silently scattering silver light across the stage.
|
- The light suddenly fails. In a moment the sky turns black. The moon rises at the far end of the stage, climbs high into the sky, stops dead, and sheds its silver light over the stage.
|
||||||
|
|
||||||
People don’t need us every day. To be honest, people don’t exactly need us that much. Others can also do these things. Even if they may not do them as well as we do, at least they can do them just as well. We just heard a cry for help… It seems like he is sending a message to all of humanity. But right now, at this moment, there are only two humans here: us. Whether we like it or not. We must seize this opportunity, while time isn’t yet too late. Since fate has brought us together among these despicable creatures, let’s at least try to represent them properly. Indeed, when we weigh the pros and cons, we remain worthy of our conditions of existence. A tiger will instinctively run to help its kind. Otherwise, it will flee into the depths of the jungle. But that’s not the issue. What we do here is what we must ask ourselves. We are honored to know the answer to that question. Yes, in this vast chaos, there is one thing that is clear: we are waiting for Gordo’s arrival.
|
- People do not need us every day of the year; to be honest, people do not precisely need us. Others could do these things too — if not better, at least as well. The cry for help we have just heard might rather be said to have been addressed to all mankind. But at this place, at this hour, mankind is us two, whether we like it or not. Let us make the most of it, before it is too late. Since ill fortune has swept us in among this shameless rabble, let us for once represent them worthily. Truly, when we stand with folded arms weighing profit and loss, we are no less worthy of the conditions of our existence. A tiger will rush without a thought to the aid of its own kind — or else it flees into the deepest jungle. But that is not the question. What we are doing here — that is the question we must put to ourselves. And we are fortunate in knowing the answer: yes, in the midst of this immense confusion one thing alone is clear — we are waiting for Godot to come.
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: "Typography Sample"
|
||||||
|
pubDate: 2026-11-02
|
||||||
|
category: tech
|
||||||
|
tags: [test, hidden]
|
||||||
|
# 排版/斜体/白名单测试样张(fix3 车道):hidden 不列出,构建与 E2E 验证用
|
||||||
|
draft: false
|
||||||
|
locked: true
|
||||||
|
---
|
||||||
|
|
||||||
|
This post verifies three things: inline italic rendering, the restricted typography-class whitelist, and caption classes.
|
||||||
|
|
||||||
|
## Italics (should stay inline: no line break, no centering)
|
||||||
|
|
||||||
|
This sentence has *inline italic emphasis* set in the middle of the text; it must stay on the same line as the body text.
|
||||||
|
|
||||||
|
*This whole paragraph is an italic standalone paragraph; it must not turn into a block-level centered element.*
|
||||||
|
|
||||||
|
## Restricted typography classes (whitelist)
|
||||||
|
|
||||||
|
<span class="ink-muted">This run is ink-muted grey text.</span>
|
||||||
|
<span class="font-kai">This run is in the LXGW WenKai type family.</span>
|
||||||
|
<span class="font-mono">This run is in the monospace mono family.</span>
|
||||||
|
|
||||||
|
<p class="drop-cap">Drop-cap paragraph: the autumn sea lies like a sheet of tin smoothed flat by the wind, and the setting sun has scorched a ring of red around its rim.</p>
|
||||||
|
|
||||||
|
<p class="quote-line">Dusk, so-called, is simply the light slowly admitting that it has to go.</p>
|
||||||
|
|
||||||
|
## Whitelist stripping (illegal classes and inline styles must be removed)
|
||||||
|
|
||||||
|
<span class="evil-class ink-2" style="color:#ff0000" onclick="alert(1)">This run carries an illegal class, an inline color, and an onclick all at once; after the build only ink-2 should remain, with style/onclick gone.</span>
|
||||||
|
|
||||||
|
## Captions (class-driven)
|
||||||
|
|
||||||
|
<p class="fig-cap">This is the explicit caption style (fig-cap): centered, grey text.</p>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: "Font Family Sample"
|
||||||
|
pubDate: 2026-11-02
|
||||||
|
category: reading
|
||||||
|
tags: [test, hidden]
|
||||||
|
draft: false
|
||||||
|
locked: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Font test specimen (hidden): verifying the rendering of system-fallback font families.
|
||||||
|
|
||||||
|
<p class="font-song">Songti serif family (system fallback): the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="font-kai-sys">Kai script family (system fallback): the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="font-hei-sys">Hei sans-serif family (system fallback): the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="font-fangsong">Fangsong family (system fallback): the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="ink-warm">Warm-toned body text: the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="ink-cool">Cool-toned body text: the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="ink-faint">Faintest readable tier: the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="ink-accent-soft">Soft emphasis: the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="c-blue">Standard blue: the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
<p class="c-yellow">Standard yellow (darkened): the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
<p class="c-red-soft">Red soft: the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="font-huiwen">Huiwen Mincho (OFL, self-hosted subsets): the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
|
|
||||||
|
<p class="font-kuaile">ZCOOL KuaiLe (OFL, decorative display type): the autumn sea lies like a sheet of tin smoothed flat by the wind.</p>
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "The lost blood-red gift, lost in the sea breeze"
|
title: "The Lost Salt Gift of Blood"
|
||||||
pubDate: 2026-03-25
|
pubDate: 2026-03-25
|
||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags: [read]
|
||||||
mood: '#0f766e'
|
mood: '#0f766e'
|
||||||
|
locked: true
|
||||||
---
|
---
|
||||||
|
|
||||||
### Autumn – The Autumn of Scott, the Autumn of a Father, the Autumn of a Youth (A Prelude to Winter)
|
### Autumn – The Autumn of Scott, the Autumn of a Father, the Autumn of a Youth (A Prelude to Winter)
|
||||||
@@ -140,7 +141,7 @@ She held my hand so tightly, as if I could pull her out of that dark, dreamlike
|
|||||||
|
|
||||||
Sometimes, in the fear of darkness, it’s difficult to distinguish between dreams and reality. We may wake up in the middle of the night, but because the world from our dreams seemed so real, we try to return to that comforting state of oblivion by using our own willpower. Other times, the situation is reversed: we might pinch ourselves or bang our fists against the iron bedposts. Sometimes, nightmares have no boundaries at all.
|
Sometimes, in the fear of darkness, it’s difficult to distinguish between dreams and reality. We may wake up in the middle of the night, but because the world from our dreams seemed so real, we try to return to that comforting state of oblivion by using our own willpower. Other times, the situation is reversed: we might pinch ourselves or bang our fists against the iron bedposts. Sometimes, nightmares have no boundaries at all.
|
||||||
|
|
||||||
All expectations and fears, both those from the past and those of the present, collide and entangle with each other. Sometimes, when we reach the end of the “present,” the “past” seems to become even more significant. Because there is nothing else besides the past; we feel that there is nothing else we can understand. I feel like I’m falling back into the past, hoping to possess more of it, because my future seems to be getting fewer and fewer. Twenty-six years is too short—it isn’t enough. I want to follow in the footsteps of our ancestors, so that I can gain more from what remains of this insignificant present. Those superstitions, herbal remedies, the calls of fate, the sounds of violins that linger in our minds, and those cancer treatments that resemble spider webs… I want to understand them all. What do those所谓的“divine eyes,” psychic visions, dogs’ instincts, and seagulls’ cries have to say about survival and its end? I also want to know those things. I want to return to those priests who can cast spells with their hands. I want to return to those healers who believe in faith-based therapies… but my faith may not be enough. I can go back to any past, but science is too indifferent. I don’t want to die at its hands.
|
All expectations and fears, both those from the past and those of the present, collide and entangle with each other. Sometimes, when we reach the end of the “present,” the “past” seems to become even more significant. Because there is nothing else besides the past; we feel that there is nothing else we can understand. I feel like I’m falling back into the past, hoping to possess more of it, because my future seems to be getting fewer and fewer. Twenty-six years is too short—it isn’t enough. I want to follow in the footsteps of our ancestors, so that I can gain more from what remains of this insignificant present. Those superstitions, herbal remedies, the calls of fate, the sounds of violins that linger in our minds, and those cancer treatments that resemble spider webs… I want to understand them all. What do those so-called “divine eyes,” psychic visions, dogs’ instincts, and seagulls’ cries have to say about survival and its end? I also want to know those things. I want to return to those priests who can cast spells with their hands. I want to return to those healers who believe in faith-based therapies… but my faith may not be enough. I can go back to any past, but science is too indifferent. I don’t want to die at its hands.
|
||||||
|
|
||||||
At that moment, just like the music, the darkness both inside and outside gradually merged into one. They flowed towards each other, blending together, becoming indistinguishable from one another. The differences were erased, leaving only purity behind. There was no space between them, no sound to be heard. This encounter brought all things back into unity.
|
At that moment, just like the music, the darkness both inside and outside gradually merged into one. They flowed towards each other, blending together, becoming indistinguishable from one another. The differences were erased, leaving only purity behind. There was no space between them, no sound to be heard. This encounter brought all things back into unity.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: "Link Card Sample"
|
||||||
|
pubDate: 2026-11-02
|
||||||
|
category: tech
|
||||||
|
tags: [test, hidden]
|
||||||
|
# 链接卡片(OG 预览)测试样张:hidden 不列出,构建验证用;保留为活样例
|
||||||
|
draft: false
|
||||||
|
locked: true
|
||||||
|
---
|
||||||
|
|
||||||
|
This post verifies link cards: bare links standing alone as their own paragraph turn into OG preview cards, while inline links keep their normal style.
|
||||||
|
|
||||||
|
## Positive cases: sites with OG metadata
|
||||||
|
|
||||||
|
https://wait4x.online/
|
||||||
|
|
||||||
|
https://astro.build/
|
||||||
|
|
||||||
|
## Negative cases: a 404 and a site without OG (should fall back to plain links; the build must not fail)
|
||||||
|
|
||||||
|
https://wait4x.online/definitely-not-a-real-page-404/
|
||||||
|
|
||||||
|
https://example.com/
|
||||||
|
|
||||||
|
## Inline links keep their normal style (must not become cards)
|
||||||
|
|
||||||
|
Here is an inline link, [the official Astro site](https://astro.build/), set in the middle of a sentence; it must keep its normal link style — the page must not turn into a wall of cards.
|
||||||
|
|
||||||
|
## Card via first-line text (the markdown link form triggers it too)
|
||||||
|
|
||||||
|
[wait4x.online homepage](https://wait4x.online/)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: "Media Support Sample"
|
||||||
|
pubDate: 2026-10-25
|
||||||
|
category: tech
|
||||||
|
tags: [test, hidden]
|
||||||
|
# 视觉/管线测试样张:draft:true 会被 getStaticPaths 全链路过滤(无静态产物),
|
||||||
|
# 故用非 draft + hidden 标记保证 /archives/media-sample/ 产物存在;视觉测试完成后应删除本篇
|
||||||
|
draft: false
|
||||||
|
locked: true
|
||||||
|
---
|
||||||
|
|
||||||
|
This post is a three-media pipeline specimen: it verifies how a markdown image and HTML5 video/audio are handled along the build chain, for use in visual-regression and multi-device testing (marked hidden — do not list).
|
||||||
|
|
||||||
|
## Image (markdown syntax, optimized by sharp at build time)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<p class="fig-cap">The new caption syntax: the fig-cap class — a copy of a specimen shot from the photos directory (1600×2400 portrait).</p>
|
||||||
|
|
||||||
|
## Video (raw HTML, H.264 320×240, generated with ffmpeg testsrc2)
|
||||||
|
|
||||||
|
<!-- Video/audio use absolute public/media/ paths: relative references in raw HTML bypass the
|
||||||
|
bundler (this once caused a verified 404), in line with the writing guide's rule that
|
||||||
|
"large videos live in an out-of-repo store under the /media/ namespace" -->
|
||||||
|
|
||||||
|
<video controls preload="metadata" width="320" height="240" src="/media/media-sample/sample-video.mp4"></video>
|
||||||
|
|
||||||
|
*Native `<video>` control, no JS; source file 45KB, faststart enabled.*
|
||||||
|
|
||||||
|
## Audio (raw HTML, 440 Hz sine-wave mp3)
|
||||||
|
|
||||||
|
<audio controls preload="metadata" src="/media/media-sample/sample-audio.mp3"></audio>
|
||||||
|
|
||||||
|
*Native `<audio>` control; source file 18KB.*
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "Veil"
|
title: "The Painted Veil"
|
||||||
pubDate: 2026-03-09
|
pubDate: 2026-03-09
|
||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags: [read]
|
||||||
mood: '#7c3aed'
|
mood: '#7c3aed'
|
||||||
|
locked: true
|
||||||
---
|
---
|
||||||
|
|
||||||
****Under the Veil: Ideal Martyrs and Real Survivors****
|
****Under the Veil: Ideal Martyrs and Real Survivors****
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pubDate: 2026-02-09
|
|||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags: [read]
|
||||||
mood: '#166534'
|
mood: '#166534'
|
||||||
|
locked: true
|
||||||
---
|
---
|
||||||
|
|
||||||
When lost in the darkness of the night, it’s advisable to look up at the stars. If one doesn’t know what to believe, then one must face their own conscience.
|
When lost in the darkness of the night, it’s advisable to look up at the stars. If one doesn’t know what to believe, then one must face their own conscience.
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -4,6 +4,7 @@ pubDate: 2026-02-26
|
|||||||
category: reading
|
category: reading
|
||||||
tags: [read]
|
tags: [read]
|
||||||
mood: '#9f1239'
|
mood: '#9f1239'
|
||||||
|
locked: true
|
||||||
---
|
---
|
||||||
|
|
||||||
### You must come back, I will wait for you.
|
### You must come back, I will wait for you.
|
||||||
|
|||||||
@@ -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.
|
||||||
Loaded 100 of 1307 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user