Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
5efbbb2f18 | ||
|
|
6f711819e2 | ||
|
|
cc4a71b968 | ||
|
|
9db1ce11e5 | ||
|
|
86c9e105f2 | ||
|
|
3479e53789 | ||
|
|
37ecb2e97b | ||
|
|
f1ee07838d | ||
|
|
6da2b7cecf | ||
|
|
76b6b6b022 | ||
|
|
80ab3ee3f3 | ||
|
|
bbbd4ef11c | ||
|
|
a4388df35e | ||
|
|
71a799bd97 | ||
|
|
dcc20e9fb3 | ||
|
|
19ffcf180b | ||
|
|
b0b7e2cd00 | ||
|
|
b9dbece2df | ||
|
|
59b7baae51 | ||
|
|
86e8009f34 | ||
|
|
ebafa415b2 | ||
|
|
e21fac7e1d | ||
|
|
7a2880b65d | ||
|
|
c1085de1a0 | ||
|
|
7c15b0dbc1 | ||
|
|
bfda43d7d1 | ||
|
|
06b782ea4a | ||
|
|
ca829f09c6 | ||
|
|
fb97d76ddb | ||
|
|
938558ad4c | ||
|
|
0c54a43bb5 |
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,93 @@
|
||||
# Phase 4:极客层 P1 + 发布链闭环 + 上线准备 - 完成总结
|
||||
|
||||
## 交付结论
|
||||
- 结果:完成
|
||||
- 完成时间:2026-10-25 09:54
|
||||
|
||||
## 假设回顾
|
||||
### 已验证假设
|
||||
- gitea webhook 目标 URL 用 http://127.0.0.1:13020 能回环触达本机接收器(同宿主)
|
||||
- ESA 对静态资源缓存友好(hash 文件名 + immutable)
|
||||
|
||||
### 仍未完全验证的假设
|
||||
- 无
|
||||
|
||||
## 交付范围
|
||||
### 已交付
|
||||
- T1 命令面板
|
||||
- T2 gitea 贡献热力图
|
||||
- T3 webhook HMAC 闭环
|
||||
- T4 预发布全端验收
|
||||
- T5 上线脚本与检查单
|
||||
- T6 验收归档
|
||||
|
||||
### 未交付
|
||||
- 无
|
||||
|
||||
### 偏差说明
|
||||
- 无
|
||||
|
||||
## 简化决策
|
||||
### 保持简单的关键选择
|
||||
- 最简单可行方案:面板=SearchDialog 扩展动作层;热力图=lib fetch+缓存 JSON+纯 CSS 组件;webhook=现骨架填 HMAC
|
||||
- 暂不引入:常驻接收服务(gitea 直接 POST 本机脚本经 systemd-path 或手动验证模式)
|
||||
- 不做的抽象/配置化:多 webhook 源、面板插件系统
|
||||
|
||||
### 本轮明确不做的内容
|
||||
- 实际执行上线切换(用户动作)、Halo 停用(Phase 5)
|
||||
|
||||
## 变更边界
|
||||
### 本轮主要改动模块
|
||||
- 见 tasks.md 的 boundary 记录
|
||||
|
||||
### 明确未触碰的区域
|
||||
- 实际执行上线切换(用户动作)、Halo 停用(Phase 5)
|
||||
|
||||
## 验证证据
|
||||
### 备注
|
||||
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||
|
||||
## 验证范围
|
||||
- 级别:package
|
||||
- 机器验证率:6/6
|
||||
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||
|
||||
## 哲学生效证据
|
||||
### 行为成效回填
|
||||
- Development Record 行为门禁通过
|
||||
|
||||
### 一致性门禁回顾
|
||||
- 验收面(checklist 或成功标准)已通过
|
||||
|
||||
## Git 记录
|
||||
- 日期时间:2026-10-25 09:54
|
||||
- 范围:Phase 4:极客层 P1 + 发布链闭环 + 上线准备
|
||||
- 功能:T1 命令面板;T2 gitea 贡献热力图;T3 webhook HMAC 闭环;T4 预发布全端验收;T5 上线脚本与检查单;T6 验收归档
|
||||
- 操作:提交 / 推送
|
||||
- 成效:Development Record 行为门禁通过
|
||||
- 提交:pending local commit
|
||||
- 提交区间:06b782ea4a8aab698dec4949c7c2d80e1fdb361f..06b782ea4a8aab698dec4949c7c2d80e1fdb361f
|
||||
- 推送:not-run (push stage follows commit)
|
||||
|
||||
## AGENTS.md 增量提议
|
||||
```diff
|
||||
发布链运维要点:gitea 容器回宿主必须走桥网关 IP 非 127.0.0.1;gitea 28 egress 拦私网 webhook 目标须 [webhook] ALLOWED_HOST_LIST(ALLOWLIST 废弃名无效);systemd --user 服务必须补 Environment=PATH(用户级 pnpm 不在默认 PATH,缺失时构建静默失败);reboot 自启需一次性 loginctl enable-linger。launch.sh/rollback.sh 双向可逆 dry-run 默认。spec tasks 里"depends-on: 无"非法——无依赖直接省行。
|
||||
```
|
||||
|
||||
## 被拒绝的扩展提议
|
||||
- 无
|
||||
|
||||
## 门禁证据
|
||||
- check_spec_package.py exit 0
|
||||
|
||||
## 遗留事项
|
||||
- 无;问题处置见下方结构化区块
|
||||
|
||||
## 问题处置
|
||||
|
||||
```json
|
||||
{
|
||||
"issues": [],
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
{"task": "task_0001", "cmd": "pnpm astro build && grep -q \"command-palette\\|cmd-item\" dist/index.html", "exit": 0, "duration_s": 4.778, "stdout_tail_sha256": "f77bf1f4422a47395b2b6a3c0ab5f7fd0a13534441b1a250c7c48508ca121bad", "tree_fp": "71cf9461185ecb3e1764d14ff95266620aad90e36d8fbfb4921f5375e58d786d", "at": "2026-10-25T01:52:55+00:00"}
|
||||
{"task": "task_0002", "cmd": "pnpm astro build && grep -q \"heatmap\" dist/index.html", "exit": 0, "duration_s": 4.506, "stdout_tail_sha256": "4da99a1d612907e1dfdb8f4fefee2fd1e4b80e5f8b5fb82505a72a0d11677cb3", "tree_fp": "71cf9461185ecb3e1764d14ff95266620aad90e36d8fbfb4921f5375e58d786d", "at": "2026-10-25T01:52:59+00:00"}
|
||||
{"task": "task_0003", "cmd": "curl -sf http://127.0.0.1:13021/healthz && grep -q \"built at\" ~/docker_apps/blog/www-staging/.build-stamp", "exit": 0, "duration_s": 0.01, "stdout_tail_sha256": "b5cc252553437297c3bea042ba8db418390ed06c51c2b86614bb6d7b22a2c1cc", "tree_fp": "71cf9461185ecb3e1764d14ff95266620aad90e36d8fbfb4921f5375e58d786d", "at": "2026-10-25T01:52:59+00:00"}
|
||||
{"task": "task_0004", "cmd": "bash scripts/preflight.sh", "exit": 0, "duration_s": 0.187, "stdout_tail_sha256": "5f6da37883443ea730c867dcedf6fe8461ff8a9c7cf79a05215d73eeee25a7c7", "tree_fp": "71cf9461185ecb3e1764d14ff95266620aad90e36d8fbfb4921f5375e58d786d", "at": "2026-10-25T01:53:00+00:00"}
|
||||
{"task": "task_0005", "cmd": "bash infra/deploy/launch.sh --dry-run", "exit": 0, "duration_s": 0.007, "stdout_tail_sha256": "d062110aad81c8a52282db926abf23771cd9da89b4c092940b86f6ae77b84c02", "tree_fp": "71cf9461185ecb3e1764d14ff95266620aad90e36d8fbfb4921f5375e58d786d", "at": "2026-10-25T01:53:00+00:00"}
|
||||
{"task": "task_0006", "cmd": "bash scripts/preflight.sh && bash infra/deploy/launch.sh --dry-run", "exit": 0, "duration_s": 0.182, "stdout_tail_sha256": "fecfa8c8a461c2863a1dfc97ed21070b79677bef846a7fe6d2a20f8b4a81a027", "tree_fp": "71cf9461185ecb3e1764d14ff95266620aad90e36d8fbfb4921f5375e58d786d", "at": "2026-10-25T01:53:00+00:00"}
|
||||
{"task": "task_0001", "cmd": "pnpm astro build && grep -q \"command-palette\\|cmd-item\" dist/index.html", "exit": 0, "duration_s": 4.824, "stdout_tail_sha256": "a2bc51f1edc0a962a98e514261c5349bf908b3c7f7670cd1f8614f716b5608ec", "tree_fp": "279582471432e50ab07567c2b021d10e073fcb15f4e3bbafc3e3fa238d3a5e50", "at": "2026-10-25T01:54:38+00:00"}
|
||||
{"task": "task_0002", "cmd": "pnpm astro build && grep -q \"heatmap\" dist/index.html", "exit": 0, "duration_s": 4.484, "stdout_tail_sha256": "b155e3d23c3d7ee884d3b77f1229535906bd5c9db4756fe9410a604abc83ae91", "tree_fp": "279582471432e50ab07567c2b021d10e073fcb15f4e3bbafc3e3fa238d3a5e50", "at": "2026-10-25T01:54:42+00:00"}
|
||||
{"task": "task_0003", "cmd": "curl -sf http://127.0.0.1:13021/healthz && grep -q \"built at\" ~/docker_apps/blog/www-staging/.build-stamp", "exit": 0, "duration_s": 0.011, "stdout_tail_sha256": "b5cc252553437297c3bea042ba8db418390ed06c51c2b86614bb6d7b22a2c1cc", "tree_fp": "279582471432e50ab07567c2b021d10e073fcb15f4e3bbafc3e3fa238d3a5e50", "at": "2026-10-25T01:54:42+00:00"}
|
||||
{"task": "task_0004", "cmd": "bash scripts/preflight.sh", "exit": 0, "duration_s": 0.178, "stdout_tail_sha256": "5f6da37883443ea730c867dcedf6fe8461ff8a9c7cf79a05215d73eeee25a7c7", "tree_fp": "279582471432e50ab07567c2b021d10e073fcb15f4e3bbafc3e3fa238d3a5e50", "at": "2026-10-25T01:54:43+00:00"}
|
||||
{"task": "task_0005", "cmd": "bash infra/deploy/launch.sh --dry-run", "exit": 0, "duration_s": 0.007, "stdout_tail_sha256": "28c0fdc7a6c74b8bcf8240bb5411675f6a5a514b52a3ed94496655e84de34dc0", "tree_fp": "279582471432e50ab07567c2b021d10e073fcb15f4e3bbafc3e3fa238d3a5e50", "at": "2026-10-25T01:54:43+00:00"}
|
||||
{"task": "task_0006", "cmd": "bash scripts/preflight.sh && bash infra/deploy/launch.sh --dry-run", "exit": 0, "duration_s": 0.185, "stdout_tail_sha256": "311bcbf528c1735fb0cbe18c189b4578d68046f5e205f33140f9e99bb002dc54", "tree_fp": "279582471432e50ab07567c2b021d10e073fcb15f4e3bbafc3e3fa238d3a5e50", "at": "2026-10-25T01:54:43+00:00"}
|
||||
@@ -0,0 +1,77 @@
|
||||
# Phase 4:极客层 P1 + 发布链闭环 + 上线准备 - 项目范围
|
||||
|
||||
<!-- spec-format: 2 -->
|
||||
|
||||
## 1. 问题定义
|
||||
- **项目目标**:命令面板 + 贡献热力图(极客层 P1);gitea webhook→构建→staging 发布闭环;预发布全端验收;一键上线脚本(用户 sudo 执行)与回滚配套
|
||||
- **目标用户**:站长(发布即构建;一条命令上线)
|
||||
- **核心价值**:从"staging 就绪"到"可上线状态"的全部工程准备
|
||||
|
||||
## 2. 假设与待确认
|
||||
|
||||
### 2.1 已确认事实
|
||||
- gitea heatmap API 匿名可用(调研 05 实测:/api/v1/users/{u}/heatmap,CORS *,返回 {contributions,timestamp}[])
|
||||
- gitea admin token 在宿主 init-credentials.txt(webhook 注册用)
|
||||
- staging 全量内容 21+2 页(zh/en)+ photos-originals
|
||||
- SearchDialog 已有 Pagefind 懒加载与 / 键入口(面板升级的基座)
|
||||
|
||||
### 2.2 关键假设
|
||||
- gitea webhook 目标 URL 用 http://127.0.0.1:13020 能回环触达本机接收器(同宿主)
|
||||
- ESA 对静态资源缓存友好(hash 文件名 + immutable)
|
||||
|
||||
### 2.3 待确认问题
|
||||
- [x] 上线切换执行者 → 结论:脚本备好,用户 sudo 执行(红线:nginx 属 root 域)
|
||||
|
||||
### 2.4 可选解释与取舍
|
||||
- 命令面板升级现有 SearchDialog(保 Pagefind 数据面)> 引 cmdk/kbar(拖 React 运行时)-> 选自写扩展
|
||||
- 热力图构建时拉取 + 仓内缓存兜底 > 运行时拉(静态站纪律)-> 选构建时
|
||||
|
||||
### 2.5 已证伪路径
|
||||
- 无
|
||||
|
||||
## 3. 功能范围
|
||||
|
||||
### 3.1 核心功能(MVP)
|
||||
- [x] T1 命令面板(搜索+导航+动作聚合)
|
||||
- [x] T2 gitea 贡献热力图(构建时+兜底)
|
||||
- [x] T3 webhook HMAC 闭环(验签+注册+push→build→staging 实测)
|
||||
- [x] T4 预发布全端验收(渲染矩阵+OG+微信 UA+responsive)
|
||||
- [x] T5 上线脚本与回滚配套+检查单
|
||||
- [x] T6 验收归档
|
||||
|
||||
### 3.2 扩展功能
|
||||
- /terminal 彩蛋、书评索引画廊(Phase 5 二期池)
|
||||
|
||||
### 3.3 不在范围内
|
||||
- 实际执行上线切换(用户动作)、Halo 停用(Phase 5)
|
||||
|
||||
## 4. 最小实现路径
|
||||
- 最简单可行方案:面板=SearchDialog 扩展动作层;热力图=lib fetch+缓存 JSON+纯 CSS 组件;webhook=现骨架填 HMAC
|
||||
- 暂不引入:常驻接收服务(gitea 直接 POST 本机脚本经 systemd-path 或手动验证模式)
|
||||
- 不做的抽象/配置化:多 webhook 源、面板插件系统
|
||||
|
||||
## 5. 技术决策
|
||||
- 技术栈:vanilla TS/CSS + gitea API + shell
|
||||
- 证据协议:evidence: runs
|
||||
- 本轮允许改动:src/、infra/deploy/、scripts/、.spec/
|
||||
- 本轮不应触碰:/etc/nginx(脚本只生成不安装)、生产 Halo、content/
|
||||
- Git integration branch:`spec/2026-10-25_add-blog-launch`
|
||||
|
||||
### 5.1 验证策略
|
||||
- 范围级别:package
|
||||
- 变更对象:面板/热力图组件、webhook 脚本、上线脚本
|
||||
- 快速检查:`pnpm astro build`
|
||||
- 集成检查:面板动作标记与热力图格子产出;webhook 干跑+真实 push 全链;验收矩阵脚本
|
||||
- 全项目检查:staging 全端验收矩阵 + rollback-dryrun;仅在 nginx 面或部署链变更时升级范围
|
||||
- 升级触发:只有上线脚本被执行(nginx 实切换)时,才升级为全项目验证并记录(当前包内只备不执行)
|
||||
|
||||
## 6. 成功标准与验证方式
|
||||
- [x] 面板含 ≥3 动作与全站导航(dist 标记断言) -> verify: task_0001
|
||||
- [x] 热力图渲染 ≥52 周格子且失败兜底不破构建 -> verify: task_0002
|
||||
- [x] 真实 push 触发构建并更新 staging 戳 -> verify: task_0003
|
||||
- [x] 验收矩阵脚本全绿(渲染/OG/微信 UA/responsive) -> verify: task_0004
|
||||
- [x] 上线脚本 --dry-run 通过且检查单落档 -> verify: task_0005
|
||||
|
||||
## 7. 风险与约束
|
||||
- webhook 回环接收的安全面(监听面收窄到 127.0.0.1 + HMAC 强制)-> T3 覆盖
|
||||
- 上线脚本误执行 -> 只生成不安装,--dry-run 默认,真实执行需用户显式 sudo
|
||||
@@ -0,0 +1,42 @@
|
||||
# Phase 4:极客层 P1 + 发布链闭环 + 上线准备 - 任务拆解
|
||||
|
||||
## 使用规则
|
||||
- boundary/verify 齐备;verify 只读断言(Phase 2 教训);上线脚本绝不自动执行
|
||||
|
||||
## 阶段一:实现
|
||||
|
||||
- [x] T1 命令面板
|
||||
- claim: op-gitea(mesh 车道,预约 src/components/CommandPalette.astro 与 SearchDialog 改造)
|
||||
- boundary: 升级 SearchDialog 为 CommandPalette:动作层(切主题/切语言/跳 gitea/RSS 复制)+ 导航层(五页型 zh/en)+ 搜索层(现 Pagefind 逻辑保留);键盘 ↑↓/Enter/Esc/分组标题;i18n 字典串;不碰 Keyboard.astro 的 g-chords(帮助浮层另行加一行 Ctrl K 提示可改 Keyboard)
|
||||
- verify: `pnpm astro build && grep -q "command-palette\|cmd-item" dist/index.html`
|
||||
|
||||
- [x] T2 gitea 贡献热力图
|
||||
- claim: op-artalk(mesh 车道,预约 src/lib/heatmap.ts 与 src/components/geek/Heatmap.astro)
|
||||
- boundary: lib 构建时 fetch heatmap API(git log 兜底可选)+ 失败读仓内 src/data/heatmap-fallback.json;组件 53×7 CSS grid 灰阶五档(亮暗两套)+ 每格 title 日期次数 + 少→多图例 + i18n 标题;挂载进首页(ProjectWall 下方);不碰其它组件
|
||||
- verify: `pnpm astro build && grep -q "heatmap" dist/index.html`
|
||||
|
||||
- [x] T3 webhook HMAC 闭环
|
||||
- claim: orchestrator
|
||||
- boundary: infra/deploy/webhook-receive.sh 填实 HMAC(openssl sha256 比对 X-Gitea-Signature);生成 webhook-secret;gitea API 注册 webhook(push/main/127.0.0.1 接收端点);接收端常驻方式用 python http.server 微型包装(监听 127.0.0.1:13021,仅 HMAC 通过触发 build.sh);实测一次 push→build→staging 戳更新
|
||||
- verify: `curl -sf http://127.0.0.1:13021/healthz && grep -q "built at" ~/docker_apps/blog/www-staging/.build-stamp`
|
||||
|
||||
- [x] T4 预发布全端验收
|
||||
- depends-on: task_0001, task_0002
|
||||
- claim: orchestrator
|
||||
- boundary: scripts/preflight.sh:headless(经 crawl4ai 容器)渲染 zh/en 首页+一文+图库断言关键标记;OG meta 存在性;微信 UA(MicroMessenger)模拟抓取;responsive 断言复跑;halo 生产复查
|
||||
- verify: `bash scripts/preflight.sh`
|
||||
|
||||
- [x] T5 上线脚本与检查单
|
||||
- claim: orchestrator
|
||||
- boundary: infra/deploy/launch.sh(root 执行):备份 vhost→wait4x.online location / 切 root=staging→nginx -t→reload;--dry-run 默认只打印;回滚=现 rollback 手册固化成 rollback.sh;docs/上线检查单.md(含 ESA 刷新步骤)
|
||||
- verify: `bash infra/deploy/launch.sh --dry-run`
|
||||
|
||||
- [x] T6 验收归档
|
||||
- depends-on: task_0003, task_0004, task_0005
|
||||
- claim: orchestrator
|
||||
- boundary: 全 verify 复跑 + 板房汇报 + 归档提交
|
||||
- verify: `bash scripts/preflight.sh && bash infra/deploy/launch.sh --dry-run`
|
||||
|
||||
---
|
||||
|
||||
**当前进度**:由脚本计算,无需手动维护
|
||||
@@ -0,0 +1,93 @@
|
||||
# 媒体支持(图/视频/音频)+ 灯箱按钮与评论区美化 - 完成总结
|
||||
|
||||
## 交付结论
|
||||
- 结果:完成
|
||||
- 完成时间:2026-10-25 12:26
|
||||
|
||||
## 假设回顾
|
||||
### 已验证假设
|
||||
- 视频进 git 仓在短小(<5MB)场景可接受;大视频走仓外 store(photos-originals 同模式)+ /media/ 路径
|
||||
- Artalk 页内渲染可被页面级 CSS 覆盖定制(同源同文档)
|
||||
|
||||
### 仍未完全验证的假设
|
||||
- 无
|
||||
|
||||
## 交付范围
|
||||
### 已交付
|
||||
- T1 灯箱原图/下载按钮美化
|
||||
- T2 Artalk 评论区美化
|
||||
- T3 markdown 媒体支持
|
||||
- T4 三媒样张
|
||||
- T5 写作指南
|
||||
- T6 验证收官
|
||||
|
||||
### 未交付
|
||||
- 无
|
||||
|
||||
### 偏差说明
|
||||
- 无
|
||||
|
||||
## 简化决策
|
||||
### 保持简单的关键选择
|
||||
- 最简单可行方案:原生标签 + global.css 媒体样式段 + 样例验证 + 指南文档
|
||||
- 暂不引入:rehype 插件链、指令语法、媒体转码管线
|
||||
- 不做的抽象/配置化:多媒体源管理界面
|
||||
|
||||
### 本轮明确不做的内容
|
||||
- 图库页媒体扩展、字幕、播放列表
|
||||
|
||||
## 变更边界
|
||||
### 本轮主要改动模块
|
||||
- 见 tasks.md 的 boundary 记录
|
||||
|
||||
### 明确未触碰的区域
|
||||
- 图库页媒体扩展、字幕、播放列表
|
||||
|
||||
## 验证证据
|
||||
### 备注
|
||||
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||
|
||||
## 验证范围
|
||||
- 级别:package
|
||||
- 机器验证率:6/6
|
||||
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||
|
||||
## 哲学生效证据
|
||||
### 行为成效回填
|
||||
- Development Record 行为门禁通过
|
||||
|
||||
### 一致性门禁回顾
|
||||
- 验收面(checklist 或成功标准)已通过
|
||||
|
||||
## Git 记录
|
||||
- 日期时间:2026-10-25 12:26
|
||||
- 范围:媒体支持(图/视频/音频)+ 灯箱按钮与评论区美化
|
||||
- 功能:T1 灯箱原图/下载按钮美化;T2 Artalk 评论区美化;T3 markdown 媒体支持;T4 三媒样张;T5 写作指南;T6 验证收官
|
||||
- 操作:提交 / 推送
|
||||
- 成效:Development Record 行为门禁通过
|
||||
- 提交:pending local commit
|
||||
- 提交区间:b9dbece2dfc7e3726154a07ac11599329f73adff..76b6b6b0228c5f30a0f5c24f57c1e24f9a5f50c7
|
||||
- 推送:not-run (push stage follows commit)
|
||||
|
||||
## AGENTS.md 增量提议
|
||||
```diff
|
||||
媒体工作流纪律:图片走 markdown ![]() 管线(自动 sharp 优化),视音频走 public/media/{slug}/ + 绝对 /media/(raw HTML 相对路径不经打包器——实测 404);斜体图注行内禁裸写 <video>/<audio> 字样(markdown 渲染成真元素:黑盒+吞文字),提及用反引号。hidden 标签=可访问不列出(列表/RSS 过滤、详情页全量出页、prev/next 滤)。Artalk 样式必须手动 link dist/Artalk.css(JS 不自动注入);18 个 --at-color-* 映射 tokens 自动双态。python replace 改源码必须 assert(静默失配=死代码上线,本次灯箱胶囊两轮翻车根因)。
|
||||
```
|
||||
|
||||
## 被拒绝的扩展提议
|
||||
- 无
|
||||
|
||||
## 门禁证据
|
||||
- check_spec_package.py exit 0
|
||||
|
||||
## 遗留事项
|
||||
- 无;问题处置见下方结构化区块
|
||||
|
||||
## 问题处置
|
||||
|
||||
```json
|
||||
{
|
||||
"issues": [],
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
{"task": "task_0001", "cmd": "pnpm astro build && grep -q \"lb-orig\" dist/gallery/index.html", "exit": 0, "duration_s": 5.364, "stdout_tail_sha256": "6eae0335cafe0d1e24f54d2da2faad789aafb149520c6e7aa6dd887e5408e6c9", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:22:26+00:00"}
|
||||
{"task": "task_0002", "cmd": "pnpm astro build && grep -q \"atk-\" src/components/ArtalkComments.astro", "exit": 0, "duration_s": 4.752, "stdout_tail_sha256": "bfa0df7049956ce7f88c15baf9d92aa6a666cea3e6c52e29b71aef53fab2f992", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:22:30+00:00"}
|
||||
{"task": "task_0003", "cmd": "pnpm astro build", "exit": 0, "duration_s": 4.768, "stdout_tail_sha256": "8c2286b96c36ba938681b73128e68200ebd4a30b31a682590d0eeee50413fae8", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:22:35+00:00"}
|
||||
{"task": "task_0005", "cmd": "test -s docs/写作指南.md", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:22:35+00:00"}
|
||||
{"task": "task_0001", "cmd": "pnpm astro build && grep -q \"lb-orig\" dist/gallery/index.html", "exit": 0, "duration_s": 5.77, "stdout_tail_sha256": "ba3cb065938be0602c31bcbc1567b8ca2ccd336c7f33dc1edf61524e10e21014", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:25:52+00:00"}
|
||||
{"task": "task_0002", "cmd": "pnpm astro build && grep -q \"atk-\" src/components/ArtalkComments.astro", "exit": 0, "duration_s": 5.493, "stdout_tail_sha256": "e7c1ec86a87016deb1872ac1a525ea846811d3e4b2f5e204d578703383e30e97", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:25:58+00:00"}
|
||||
{"task": "task_0003", "cmd": "pnpm astro build", "exit": 0, "duration_s": 5.307, "stdout_tail_sha256": "fcb5b66f3f505b9f2dee356714459b8146a5556bc711ecc5b52f17eb6f171629", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:26:03+00:00"}
|
||||
{"task": "task_0004", "cmd": "pnpm astro build && grep -q \"<video\" dist/archives/*/index.html -r | head -1", "exit": 0, "duration_s": 5.383, "stdout_tail_sha256": "defdcb3c46daab3549b8e7b9d5b3815f0e4fb841522d7112ee587b1f1e4b188a", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:26:09+00:00"}
|
||||
{"task": "task_0005", "cmd": "test -s docs/写作指南.md", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:26:09+00:00"}
|
||||
{"task": "task_0006", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 15.076, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "e0185c60d3ae811f641c91ec71a1b0583c007e2fcfbf1b18d2f75f06e076649c", "at": "2026-10-25T04:26:24+00:00"}
|
||||
@@ -0,0 +1,73 @@
|
||||
# 媒体支持(图/视频/音频)+ 灯箱按钮与评论区美化 - 项目范围
|
||||
|
||||
<!-- spec-format: 2 -->
|
||||
|
||||
## 1. 问题定义
|
||||
- **项目目标**:书评/技术文 markdown 支持插入图片、视频、音频(含样式与工作流文档);灯箱原图/下载按钮与 Artalk 评论区视觉美化对齐站点设计系统
|
||||
- **目标用户**:站长(多媒体写作能力)与读者(一致的视觉体验)
|
||||
- **核心价值**:内容表达力补全(三轨中的摄影/技术教程常需媒体)+ 收尾两处观感
|
||||
|
||||
## 2. 假设与待确认
|
||||
|
||||
### 2.1 已确认事实
|
||||
- ffmpeg 7.1.5 可用(测试媒体生成)
|
||||
- Astro 5+ 内容集合 markdown 正文相对路径图片走图片服务优化;raw HTML(video/audio 标签)默认透传
|
||||
- Artalk 2.x 支持 CSS 变量主题(--at-* 系)与 darkMode 配置
|
||||
|
||||
### 2.2 关键假设
|
||||
- 视频进 git 仓在短小(<5MB)场景可接受;大视频走仓外 store(photos-originals 同模式)+ /media/ 路径
|
||||
- Artalk 页内渲染可被页面级 CSS 覆盖定制(同源同文档)
|
||||
|
||||
### 2.3 待确认问题
|
||||
- [x] 图/视/音三媒都要 → 用户明示
|
||||
|
||||
### 2.4 可选解释与取舍
|
||||
- 视频/音频用原生 <video>/<audio> 标签(markdown raw HTML)> 自研指令解析器(:::video 语法)-> 选原生(零解析器、写作直观、降级天然)
|
||||
- 评论区美化走页面级 CSS 覆盖 > fork Artalk 主题 -> 选覆盖(升级安全)
|
||||
|
||||
### 2.5 已证伪路径
|
||||
- 无
|
||||
|
||||
## 3. 功能范围
|
||||
|
||||
### 3.1 核心功能(MVP)
|
||||
- [x] T1 灯箱原图/下载按钮美化
|
||||
- [x] T2 Artalk 评论区美化(tokens 对齐 + 明暗双态)
|
||||
- [x] T3 markdown 媒体支持(图片优化+样式+图注;video/audio 原生标签+统一样式)
|
||||
- [x] T4 测试样张(ffmpeg 生成音视频样例 + 含三媒的样例文章)
|
||||
- [x] T5 写作指南(docs/写作指南.md:三种媒体的插入方法/体积纪律/目录约定)
|
||||
- [x] T6 E2E + 视觉复核 + 收官上线
|
||||
|
||||
### 3.2 扩展功能
|
||||
- 文内图片点击放大(复用灯箱)——二期
|
||||
|
||||
### 3.3 不在范围内
|
||||
- 图库页媒体扩展、字幕、播放列表
|
||||
|
||||
## 4. 最小实现路径
|
||||
- 最简单可行方案:原生标签 + global.css 媒体样式段 + 样例验证 + 指南文档
|
||||
- 暂不引入:rehype 插件链、指令语法、媒体转码管线
|
||||
- 不做的抽象/配置化:多媒体源管理界面
|
||||
|
||||
## 5. 技术决策
|
||||
- 技术栈:Astro markdown 原生能力 + CSS
|
||||
- 证据协议:evidence: runs
|
||||
- 本轮允许改动:src/styles/、src/components/(ArtalkComments/GalleryPage)、content/posts(样例)、docs/、tests/
|
||||
- 本轮不应触碰:infra 生产面、content/posts 既有六篇正文
|
||||
- Git integration branch:`spec/2026-10-25_add-media-and-polish`
|
||||
|
||||
### 5.1 验证策略
|
||||
- 范围级别:package
|
||||
- 变更对象:媒体样式与组件、样例内容、指南文档
|
||||
- 快速检查:`pnpm astro build`
|
||||
- 集成检查:`node tests/e2e.mjs`
|
||||
- 全项目检查:视觉复核样例页与评论区双主题截图;仅在组件骨架或发布链变更时升级范围
|
||||
- 升级触发:只有改动 Artalk 集成骨架或发布链时,才升级验证范围并记录理由
|
||||
|
||||
## 6. 成功标准与验证方式
|
||||
- [x] 样例文章三媒齐渲染(断言 video/audio/img 元素存在) -> verify: task_0004
|
||||
- [x] 评论区双主题截图过视觉审 -> verify: task_0006
|
||||
- [x] E2E 30/30 保持全绿 -> verify: task_0006
|
||||
|
||||
## 7. 风险与约束
|
||||
- 大视频入仓风险 → 指南纪律 + photos-ingest 类体积闸(后续可加 media-ingest)
|
||||
@@ -0,0 +1,42 @@
|
||||
# 媒体支持(图/视频/音频)+ 灯箱按钮与评论区美化 - 任务拆解
|
||||
|
||||
## 使用规则
|
||||
- boundary/verify 齐备;verify 只读断言
|
||||
|
||||
## 阶段一:实现
|
||||
|
||||
- [x] T1 灯箱原图/下载按钮美化
|
||||
- claim: orchestrator
|
||||
- boundary: 只改 GalleryPage.astro 灯箱 lb-orig 区(升级为与 lbnav 同语言的胶囊按钮:图标+文字+悬停态)
|
||||
- verify: `pnpm astro build && grep -q "lb-orig" dist/gallery/index.html`
|
||||
|
||||
- [x] T2 Artalk 评论区美化
|
||||
- claim: op-gitea(mesh 车道,预约 src/components/ArtalkComments.astro)
|
||||
- boundary: ArtalkComments.astro 内注入页面级覆盖样式(.atk-* 选择器,用 tokens 变量,明暗双态随 [data-theme]);可调研 Artalk darkMode 配置项并在 init 传参;不改 Artalk 服务端
|
||||
- verify: `pnpm astro build && grep -q "atk-" src/components/ArtalkComments.astro`
|
||||
|
||||
- [x] T3 markdown 媒体支持
|
||||
- claim: orchestrator
|
||||
- boundary: src/styles/global.css 增媒体样式段(.prose-reading/.prose-tech 内 img/figure/video/audio:宽度/圆角/居中/图注样式);验证 raw HTML 透传与图片优化
|
||||
- verify: `pnpm astro build`
|
||||
|
||||
- [x] T4 三媒样张
|
||||
- claim: op-artalk(mesh 车道,预约 content/posts/media-sample.md 与其资产目录)
|
||||
- boundary: ffmpeg 生成 3s 测试视频(<1MB)与 3s 音频;一张测试图;写一篇 draft:true 的样例文章(category: tech, draft 置 true 不上线仅构建验证)三媒齐用;不碰既有六篇
|
||||
- verify: `pnpm astro build && grep -q "<video" dist/archives/*/index.html -r | head -1`
|
||||
|
||||
- [x] T5 写作指南
|
||||
- depends-on: task_0003
|
||||
- claim: orchestrator
|
||||
- boundary: 新建 docs/写作指南.md(三媒插入语法/资产目录约定 content/posts/assets/{slug}/)/体积纪律(图≤1MB 压缩后、音≤5MB、视频>5MB 走仓外 store+绝对 /media/ 路径待 build.sh 同步)
|
||||
- verify: `test -s docs/写作指南.md`
|
||||
|
||||
- [x] T6 验证收官
|
||||
- depends-on: task_0001, task_0002, task_0004, task_0005
|
||||
- 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,92 @@
|
||||
# 上线四缺陷修复 + E2E 全功能测试与视觉复核 - 完成总结
|
||||
|
||||
## 交付结论
|
||||
- 结果:完成
|
||||
- 完成时间:2026-10-25 11:30
|
||||
|
||||
## 假设回顾
|
||||
### 已验证假设
|
||||
- Artalk E2E fetch 失败为测试环境跨域(127.0.0.1 页面拉 wait4x.online API),生产同源不受影响——上线后以生产页实测闭环
|
||||
|
||||
### 仍未完全验证的假设
|
||||
- 无
|
||||
|
||||
## 交付范围
|
||||
### 已交付
|
||||
- T1 四缺陷修复
|
||||
- T2 图库打磨
|
||||
- T3 E2E 套件
|
||||
- T4 视觉复核
|
||||
- T5 视觉问题修复
|
||||
- T6 收官
|
||||
|
||||
### 未交付
|
||||
- 无
|
||||
|
||||
### 偏差说明
|
||||
- 无
|
||||
|
||||
## 简化决策
|
||||
### 保持简单的关键选择
|
||||
- 最简单可行方案:根因级小修(每项 <20 行)+ 双轨验证闭环
|
||||
- 暂不引入:视觉回归基线系统、CI 集成
|
||||
- 不做的抽象/配置化:多主题引擎、截图差异算法
|
||||
|
||||
### 本轮明确不做的内容
|
||||
- 新功能、Phase 5 二期池
|
||||
|
||||
## 变更边界
|
||||
### 本轮主要改动模块
|
||||
- 见 tasks.md 的 boundary 记录
|
||||
|
||||
### 明确未触碰的区域
|
||||
- 新功能、Phase 5 二期池
|
||||
|
||||
## 验证证据
|
||||
### 备注
|
||||
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||
|
||||
## 验证范围
|
||||
- 级别:package
|
||||
- 机器验证率:6/6
|
||||
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||
|
||||
## 哲学生效证据
|
||||
### 行为成效回填
|
||||
- Development Record 行为门禁通过
|
||||
|
||||
### 一致性门禁回顾
|
||||
- 验收面(checklist 或成功标准)已通过
|
||||
|
||||
## Git 记录
|
||||
- 日期时间:2026-10-25 11:30
|
||||
- 范围:上线四缺陷修复 + E2E 全功能测试与视觉复核
|
||||
- 功能:T1 四缺陷修复;T2 图库打磨;T3 E2E 套件;T4 视觉复核;T5 视觉问题修复;T6 收官
|
||||
- 操作:提交 / 推送
|
||||
- 成效:Development Record 行为门禁通过
|
||||
- 提交:pending local commit
|
||||
- 提交区间:c1085de1a0d745774d1745e5156080c8c4585f7b..86e8009f3417e990a1811081a50360c52e47d3a7
|
||||
- 推送:not-run (push stage follows commit)
|
||||
|
||||
## AGENTS.md 增量提议
|
||||
```diff
|
||||
质量闭环方法论:Playwright E2E×30(真实点击/软导航回归/懒加载滚动预热)+ glm-5.3-flash 视觉子代理四轮截图审(像素级列洞/末列裁切/粘连都能抓)。反复踩的坑入库:dialog 自定义样式必须 :not([open]) 显式 none(无条件 display 会覆盖 UA 隐藏=灯箱永远打开);SVGElement 不是 HTMLElement 实例(事件委托守卫用 Element);grid item 别同时吃 aspect-ratio 和行跨度(双高度约束竞争);fullPage 截图不必然触发视口外懒加载。E2E 环境 Artalk 跨域失败≠生产失败(同源判定要分环境)。
|
||||
```
|
||||
|
||||
## 被拒绝的扩展提议
|
||||
- 无
|
||||
|
||||
## 门禁证据
|
||||
- check_spec_package.py exit 0
|
||||
|
||||
## 遗留事项
|
||||
- 无;问题处置见下方结构化区块
|
||||
|
||||
## 问题处置
|
||||
|
||||
```json
|
||||
{
|
||||
"issues": [],
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
{"task": "task_0001", "cmd": "pnpm astro build && node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 19.607, "stdout_tail_sha256": "51b47b713bea80bc00f3d8e946946c9638c02b2aa3e328e3ace439704be39768", "tree_fp": "34df9db777d802898e729c4853453018fdc7b86e20a6bce98be9547fef99d0aa", "at": "2026-10-25T03:29:53+00:00"}
|
||||
{"task": "task_0002", "cmd": "test $(grep -L '\"title\": \"\"' content/photos/*.json | wc -l) -eq 0", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "34df9db777d802898e729c4853453018fdc7b86e20a6bce98be9547fef99d0aa", "at": "2026-10-25T03:29:53+00:00"}
|
||||
{"task": "task_0003", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 14.342, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "34df9db777d802898e729c4853453018fdc7b86e20a6bce98be9547fef99d0aa", "at": "2026-10-25T03:30:08+00:00"}
|
||||
{"task": "task_0004", "cmd": "test $(ls /tmp/e2e-shots/*.png | wc -l) -ge 14", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "34df9db777d802898e729c4853453018fdc7b86e20a6bce98be9547fef99d0aa", "at": "2026-10-25T03:30:08+00:00"}
|
||||
{"task": "task_0005", "cmd": "pnpm astro build && node tests/e2e.mjs 2>&1 | grep -q \"30/30 PASS\"", "exit": 0, "duration_s": 19.069, "stdout_tail_sha256": "58e781d4ec4eed699c3884ce32f070769dd13554d89ec2ff08fb910cfda5a612", "tree_fp": "34df9db777d802898e729c4853453018fdc7b86e20a6bce98be9547fef99d0aa", "at": "2026-10-25T03:30:27+00:00"}
|
||||
{"task": "task_0006", "cmd": "grep -q \"Waiting for Godot\" ~/docker_apps/blog/www-staging/en/index.html", "exit": 0, "duration_s": 0.002, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "34df9db777d802898e729c4853453018fdc7b86e20a6bce98be9547fef99d0aa", "at": "2026-10-25T03:30:27+00:00"}
|
||||
@@ -0,0 +1,72 @@
|
||||
# 上线四缺陷修复 + E2E 全功能测试与视觉复核 - 项目范围
|
||||
|
||||
<!-- spec-format: 2 -->
|
||||
|
||||
## 1. 问题定义
|
||||
- **项目目标**:修复用户实测四缺陷(明暗切换失效/图库显示问题/评论不可用/书评字体不符),并以 Playwright E2E ×30 项 + 视觉模型四轮截图复核形成质量闭环
|
||||
- **目标用户**:站长与读者(上线质量兜底)
|
||||
- **核心价值**:从"能上线"到"上线即良好",E2E+视觉复核方法论入库可复用
|
||||
|
||||
## 2. 假设与待确认
|
||||
|
||||
### 2.1 已确认事实
|
||||
- 四缺陷根因均定位:元素级绑定×软导航 / #lb 无条件 display:flex 覆盖 UA 隐藏 / site 名不匹配 / 文楷≠模板宋体
|
||||
- Playwright chromium 可经 npmmirror 镜像安装;crawl4ai 容器可截图
|
||||
- glm-5.3-flash 子代理具备看图能力
|
||||
|
||||
### 2.2 关键假设
|
||||
- Artalk E2E fetch 失败为测试环境跨域(127.0.0.1 页面拉 wait4x.online API),生产同源不受影响——上线后以生产页实测闭环
|
||||
|
||||
### 2.3 待确认问题
|
||||
- [x] 金句引文是否保留文楷 → 结论:保留(宋正文×文楷金句层次)
|
||||
|
||||
### 2.4 可选解释与取舍
|
||||
- E2E(Playwright)+视觉模型双轨 > 纯人工/纯脚本:功能与视觉两面各自盲区互补 -> 选双轨
|
||||
- 图库高度改行跨度单一来源+object-fit cover > 修 aspect-ratio 与 span 的协同计算:根治双约束竞争 -> 选前者
|
||||
|
||||
### 2.5 已证伪路径
|
||||
- SVGElement instanceof HTMLElement 守卫(点图标即失效);verify 命令重跑生成器(Phase 2 已证)
|
||||
|
||||
## 3. 功能范围
|
||||
|
||||
### 3.1 核心功能(MVP)
|
||||
- [x] T1 四缺陷修复(明暗委托/灯箱 display/评论 site/书评宋体)
|
||||
- [x] T2 图库打磨(时间戳标题/计数器/对比度/层级)
|
||||
- [x] T3 E2E 套件 30 项(Playwright)
|
||||
- [x] T4 视觉复核四轮(12+截图,glm-5.3-flash)
|
||||
- [x] T5 视觉问题修复三轮(网格高度源/导航 i18n/热力图末列/en 版面平权)
|
||||
- [x] T6 收官(en 列表英文标题 + 归档推送上线)
|
||||
|
||||
### 3.2 扩展功能
|
||||
- 无
|
||||
|
||||
### 3.3 不在范围内
|
||||
- 新功能、Phase 5 二期池
|
||||
|
||||
## 4. 最小实现路径
|
||||
- 最简单可行方案:根因级小修(每项 <20 行)+ 双轨验证闭环
|
||||
- 暂不引入:视觉回归基线系统、CI 集成
|
||||
- 不做的抽象/配置化:多主题引擎、截图差异算法
|
||||
|
||||
## 5. 技术决策
|
||||
- 技术栈:现有 Astro 站 + Playwright + c4ai 截图 + glm-5.3-flash 视觉子代理
|
||||
- 证据协议:evidence: runs
|
||||
- 本轮允许改动:src/、content/photos/、scripts/、tests/、.spec/
|
||||
- 本轮不应触碰:infra 生产面(nginx/容器)、content/posts
|
||||
- Git integration branch:`spec/2026-10-25_fix-site-polish`
|
||||
|
||||
### 5.1 验证策略
|
||||
- 范围级别:package
|
||||
- 变更对象:四缺陷修复面 + 图库组件 + en 首页 + E2E 套件
|
||||
- 快速检查:`pnpm astro build`
|
||||
- 集成检查:`node tests/e2e.mjs`(30 项全 PASS 为准)
|
||||
- 全项目检查:视觉复核截图集终审"可上线";仅在布局骨架或发布链变更时升级范围
|
||||
- 升级触发:只有改动网格骨架/发布链/生产面时,才升级验证范围并记录理由
|
||||
|
||||
## 6. 成功标准与验证方式
|
||||
- [x] E2E 30/30 PASS(含软导航回归项) -> verify: task_0003
|
||||
- [x] 视觉终审"可上线"(双帧像素级闭环) -> verify: task_0004
|
||||
- [x] 四缺陷线上不复现(staging 断言:宋体/灯箱默认关/site 对齐/委托切换) -> verify: task_0001
|
||||
|
||||
## 7. 风险与约束
|
||||
- 全页截图不必然触发懒加载(E2E 已加滚动预热);生产 Artalk 需上线后同源实测
|
||||
@@ -0,0 +1,42 @@
|
||||
# 上线四缺陷修复 + E2E 全功能测试与视觉复核 - 任务拆解
|
||||
|
||||
## 使用规则
|
||||
- boundary/verify 齐备;verify 只读断言
|
||||
|
||||
## 阶段一:实现
|
||||
|
||||
- [x] T1 四缺陷修复
|
||||
- claim: orchestrator
|
||||
- boundary: Header 委托/ArtalkComments site/tokens 字体/GalleryPage display
|
||||
- verify: `pnpm astro build && node tests/e2e.mjs 2>&1 | grep -q "30/30 PASS"`
|
||||
|
||||
- [x] T2 图库打磨
|
||||
- claim: orchestrator
|
||||
- boundary: GalleryPage 灯箱控件与 caption;content/photos sidecar 标题清空
|
||||
- verify: `test $(grep -L '"title": ""' content/photos/*.json | wc -l) -eq 0`
|
||||
|
||||
- [x] T3 E2E 套件
|
||||
- claim: orchestrator
|
||||
- boundary: 只新增 tests/e2e.mjs
|
||||
- verify: `node tests/e2e.mjs 2>&1 | grep -q "30/30 PASS"`
|
||||
|
||||
- [x] T4 视觉复核
|
||||
- claim: lane-vision(glm-5.3-flash 子代理)
|
||||
- boundary: 只读截图分析;产物 /tmp/e2e-shots/
|
||||
- verify: `test $(ls /tmp/e2e-shots/*.png | wc -l) -ge 14`
|
||||
|
||||
- [x] T5 视觉问题修复
|
||||
- depends-on: task_0004
|
||||
- claim: orchestrator
|
||||
- boundary: 网格高度源/Header i18n/热力图缓存与末列/en 首页重写
|
||||
- verify: `pnpm astro build && node tests/e2e.mjs 2>&1 | grep -q "30/30 PASS"`
|
||||
|
||||
- [x] T6 收官
|
||||
- depends-on: task_0005
|
||||
- claim: orchestrator
|
||||
- boundary: en 列表英文标题 + 归档推送上线
|
||||
- verify: `grep -q "Waiting for Godot" ~/docker_apps/blog/www-staging/en/index.html`
|
||||
|
||||
---
|
||||
|
||||
**当前进度**:由脚本计算,无需手动维护
|
||||
@@ -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'`
|
||||
@@ -3,6 +3,9 @@ import { defineConfig } from 'astro/config';
|
||||
import pagefind from 'astro-pagefind';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import { execSync } from 'node:child_process';
|
||||
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 引用。
|
||||
// 四重回退:CI 环境变量优先(GITEA_ACTIONS/GITHUB_SHA)→ 本地 git → dev 占位。
|
||||
@@ -17,7 +20,13 @@ const date =
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://wait4x.online',
|
||||
integrations: [pagefind(), sitemap()],
|
||||
integrations: [pagefind(), sitemap(), linkCardsReport()],
|
||||
markdown: {
|
||||
// 独立成段的裸链接 → OG 预览卡(构建期抓取,失败回落普通链接;见 src/lib/rehype-link-cards.ts)
|
||||
// 顺序:rehypeRaw 先把 markdown 里的 raw HTML 解析成 hast element(否则白名单看不到 span),
|
||||
// 再白名单净化(剥 style/on*/非白名单类),最后链接卡片(节点由插件自建,不经净化)
|
||||
rehypePlugins: [rehypeRaw, rehypeClassWhitelist, rehypeLinkCards],
|
||||
},
|
||||
i18n: {
|
||||
defaultLocale: 'zh',
|
||||
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"
|
||||
---
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
pubDate: 2026-11-02
|
||||
tags: [站点更新]
|
||||
text: |
|
||||
瞬间现在和文章同级表达力:`text` 支持 markdown 与排版类,<span class="c-teal font-kai">这一句是青色 + 楷体</span>,也可以**加粗**。
|
||||
|
||||
另外每条瞬间自带评论区——展开才加载,不刷时间线就不为它付成本。
|
||||
---
|
||||
|
||||
站点更新记录:瞬间富文本与逐条评论上线。
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"title": "1766156983093",
|
||||
"title": "防波堤暮色",
|
||||
"image": "./1766156983093.jpg",
|
||||
"group": "street",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"group": "landscape",
|
||||
"alt": "暮色海岸与远处防波堤(T3 冒烟补写)。",
|
||||
"caption": "落日把海与城一齐烫成金箔,海鸟的剪影恰好掠过日轮"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766156992123",
|
||||
"title": "",
|
||||
"image": "./1766156992123.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "书架暗处,那本宫崎骏分镜集被灯光单独打亮"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766156992265",
|
||||
"title": "",
|
||||
"image": "./1766156992265.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "一面白墙只问一个问题,笔锋落下处是想活出的人生"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766156992559",
|
||||
"title": "",
|
||||
"image": "./1766156992559.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "黑暗中独一句发光的话,像有人在夜里旋起一阵热风"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766156992840",
|
||||
"title": "",
|
||||
"image": "./1766156992840.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "一抹红墙,几笔白线,龙猫的轮廓就是吉卜力的门牌"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766156997939",
|
||||
"title": "",
|
||||
"image": "./1766156997939.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "夜游船驮着绿光驶过,东方明珠在它身后蓝得像天外来物"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766156998072",
|
||||
"title": "",
|
||||
"image": "./1766156998072.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "对岸灯火压得很低,渡轮拖着一线亮色划开墨色的江面"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766156998205",
|
||||
"title": "",
|
||||
"image": "./1766156998205.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "海关大楼的钟面亮成金色,人群在灯下攒动成剪影"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766156998344",
|
||||
"title": "",
|
||||
"image": "./1766156998344.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "旗在风里翻卷,塔身亮得像一支随时点火的火箭"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766156998590",
|
||||
"title": "",
|
||||
"image": "./1766156998590.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "大楼只占一个角落,剩下的天空都交给云慢慢写"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766157003595",
|
||||
"title": "",
|
||||
"image": "./1766157003595.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "草坡绿得发暗,牦牛散落其间,低头啃食高原的安静"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766157003732",
|
||||
"title": "",
|
||||
"image": "./1766157003732.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "灰白天色下,雪与黑岩互相渗透,山脊线细得像一笔勾勒"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766157003865",
|
||||
"title": "",
|
||||
"image": "./1766157003865.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "雾把山谷对折,只留下碎石和雪在白茫茫里彼此确认"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766157003992",
|
||||
"title": "",
|
||||
"image": "./1766157003992.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "一队人影在乱石坡上缓缓上移,雪山在他们头顶沉默俯视"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766157004116",
|
||||
"title": "",
|
||||
"image": "./1766157004116.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "雪原中央蹲着两团颜色,出发前把装备一件件理顺"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "1766157012003",
|
||||
"title": "",
|
||||
"image": "./1766157012003.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "太阳缩成一粒橘色的句点,悬在灰橙色的茫茫天幕上"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "mmexport1771816358254",
|
||||
"title": "",
|
||||
"image": "./mmexport1771816358254.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "成群海鸥同时起飞,蓝海与红屋顶都被翅膀搅动"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "mmexport1771816377628",
|
||||
"title": "",
|
||||
"image": "./mmexport1771816377628.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "垂钓的人立在礁石上,几根鱼竿替他守着整片海"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "mmexport1771816382115",
|
||||
"title": "",
|
||||
"image": "./mmexport1771816382115.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "长堤探进海里,堤上的人站成一小排,各自望着各自的远方"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "mmexport1776564084994",
|
||||
"title": "",
|
||||
"image": "./mmexport1776564084994.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "海鸥飞过之处,玻璃高塔与红瓦老城在同一帧里相遇"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"title": "mmexport1776564092433",
|
||||
"title": "",
|
||||
"image": "./mmexport1776564092433.jpg",
|
||||
"group": "street",
|
||||
"group": "landscape",
|
||||
"alt": "",
|
||||
"exif": {}
|
||||
"exif": {},
|
||||
"caption": "画面只留一圈亮处:老楼、松树、栏杆边与海鸥相伴的人群"
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "Agensh: Scaling Organizational Intelligence to 1,024 Agents"
|
||||
pubDate: 2026-11-01
|
||||
category: tech
|
||||
tags: [Paper Notes, AI Collaboration]
|
||||
locked: true
|
||||
---
|
||||
|
||||
Multi-agent systems compress the latency of complex tasks through concurrency, but mainstream frameworks share one ceiling: the central orchestrator. Task allocation, progress aggregation, and result adjudication all squeeze through a single context — the bigger the scale, the sooner the orchestrator dies. In October 2026, Microsoft Research open-sourced [Agensh](https://github.com/microsoft/Agensh) — a self-organized multi-agent harness **with no central orchestrator**, and the paper [*Scaling Organizational Intelligence to 1,024 Agents*](https://arxiv.org/abs/2609.26781) pushed it to a thousand and twenty-four agents.
|
||||
|
||||
Its answer is disarmingly simple: instead of building a smarter scheduling brain, hand the agents the coordination infrastructure of a human organization, as is.
|
||||
|
||||
> 📄 **[Read the full paper PDF online →](/en/papers/agensh/)** · arXiv 2609.26781 · Microsoft Research
|
||||
|
||||
## 1. Three pillars: an organization's coordination infrastructure
|
||||
|
||||

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

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

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

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

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

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

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

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

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

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

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

|
||||
|
||||
<p class="fig-cap">图注新写法:fig-cap 类,photos 目录样片副本(1600×2400 竖幅)。</p>
|
||||
|
||||
## 视频(raw HTML,H.264 320×240,ffmpeg testsrc2 生成)
|
||||
|
||||
<!-- 视/音频走 public/media/ 绝对路径:raw HTML 相对引用不经打包器(曾实测 404),
|
||||
与写作指南“大视频走仓外 store 的 /media/ 命名空间”对齐 -->
|
||||
|
||||
<video controls preload="metadata" width="320" height="240" src="/media/media-sample/sample-video.mp4"></video>
|
||||
|
||||
*原生 `<video>` 控件,无 JS;源文件 45KB,faststart 已开启。*
|
||||
|
||||
## 音频(raw HTML,440Hz 正弦波 mp3)
|
||||
|
||||
<audio controls preload="metadata" src="/media/media-sample/sample-audio.mp3"></audio>
|
||||
|
||||
*原生 `<audio>` 控件;源文件 18KB。*
|
||||
@@ -1,18 +1,25 @@
|
||||
---
|
||||
title: "面纱"
|
||||
title: 面纱
|
||||
pubDate: 2026-03-09
|
||||
category: reading
|
||||
tags: [read]
|
||||
tags:
|
||||
- read
|
||||
pinned: true
|
||||
mood: '#7c3aed'
|
||||
bookAuthor: 威廉·萨默塞特·毛姆
|
||||
description: 面纱之下:理想的殉道者与真实的幸存者。
|
||||
cover: ./assets/面纱.png
|
||||
hiddenTag: false
|
||||
draft: false
|
||||
---
|
||||
|
||||
****面纱之下:理想的殉道者和真实的幸存者****
|
||||
**_面纱之下:理想的殉道者和真实的幸存者_**
|
||||
|
||||
### 你用面纱遮蔽他人的窥探,自己也难以一睹世界之实。
|
||||
|
||||
这是译者在后记中写下的一句话。很好印证了《面纱》这本书或者说这个题目所蕴含的真相。人们活在面纱之下,以此遮蔽他人的窥探,却也因此困于自己的面纱之下。
|
||||
|
||||
见沃尔特像是见最温柔的爱和最悲痛的爱情,以最炽热的心和最真挚的感情去爱,爱上了一个不爱自己的人,最后在爱中将自己陷入漩涡🌀,不得解脱。他富有智慧,又富有对这个世界和社会以及人性最明锐的洞察力,他了解世界的苦难,了解社会的种种表象以及深层次的真相,了解人的“伪装”背后的真实人性。他像是个圣洁的存在,却同样又有着拙劣和“恶毒”的想法,以至于至死都未能从感情漩涡中游出。在我看来,沃尔特对于凯蒂的爱是真切的,对于背叛的恨也是真切的,但从始至终爱始终贯穿永远。在去湄潭府之前,他是冷静的旁观者,他是理性的化身,他有真对于真理的精准见解,有着自己衡量一切的道德标准和不可被践踏的信仰,这是源于理性的不可被磨灭的印记。在感知到背叛,并带着赴死的姿态奔赴湄潭府的过程中,他被感情所裹挟,他既爱又恨,恨凯蒂的背叛,也恨自己爱上了一个与自己原则背道而驰的爱人,他从一开始想让凯蒂也走向死亡,到最后让她离开,看似对于婚姻和这段感情有了一点点解脱,可实际上到死他都未能跨过自己给自己筑起的情感高墙。他是矛盾的,他至死未能宽恕自己,也正如他对世界最后的留言所述:“死的是一条狗”。这不仅是对凯蒂的隐喻,更是他对自己灵魂的终极审判。他终于承认,那个满怀怨恨、试图以爱之名行毁灭之实的自己,才是那条被仇恨吞噬的‘疯狗’。沃尔特的一生,是用最锋利的理性去剖析世界,却唯独不敢直视自己内心那片荒芜的废墟。他死于霍乱,实则死于那层由傲慢与偏见织就的、永远无法揭下的面纱。
|
||||
见沃尔特像是见最温柔的爱和最悲痛的爱情,以最炽热的心和最真挚的感情去爱,爱上了一个不爱自己的人,最后在爱中将自己陷入漩涡🌀,不得解脱。他富有智慧,又富有对这个世界和社会以及人性最明锐的洞察力,他了解世界的苦难,了解社会的种种表象以及深层次的真相,了解人的“伪装”背后的真实人性。他像是个圣洁的存在,却同样又有着拙劣和“恶毒”的想法,以至于至死都未能从感情漩涡中游出。在我看来,沃尔特对于凯蒂的爱是真切的,对于背叛的恨也是真切的,但从始至终爱始终贯穿永远。在去湄潭府之前,他是冷静的旁观者,他是理性的化身,他有着对于真理的精准见解,有着自己衡量一切的道德标准和不可被践踏的信仰,这是源于理性的不可被磨灭的印记。在感知到背叛,并带着赴死的姿态奔赴湄潭府的过程中,他被感情所裹挟,他既爱又恨,恨凯蒂的背叛,也恨自己爱上了一个与自己原则背道而驰的爱人,他从一开始想让凯蒂也走向死亡,到最后让她离开,看似对于婚姻和这段感情有了一点点解脱,可实际上到死他都未能跨过自己给自己筑起的情感高墙。他是矛盾的,他至死未能宽恕自己,也正如他对世界最后的留言所述:“死的是一条狗”。这不仅是对凯蒂的隐喻,更是他对自己灵魂的终极审判。他终于承认,那个满怀怨恨、试图以爱之名行毁灭之实的自己,才是那条被仇恨吞噬的‘疯狗’。沃尔特的一生,是用最锋利的理性去剖析世界,却唯独不敢直视自己内心那片荒芜的废墟。他死于霍乱,实则死于那层由傲慢与偏见织就的、永远无法揭下的面纱。
|
||||
|
||||
相比之下,凯蒂是幸运的,也最终得到了自由。在虚荣的面纱下,遮住的是世界的残酷和社会的复杂以及人性的不可知。一开始的虚荣心作祟,让她步入了和沃尔特的婚姻,却发现婚后的生活并非是自己所期望的那样,甚至从一开始她就清楚的知道自己并不爱沃尔特。于是,当她到了香港,并遇上查理的时候,她沦陷了,她在查理身上看见了自己所期待的伴侣所拥有的那些特性,幽默风趣,性感迷人,但是却忽视了人性。在去往湄潭府之前,她虚荣,她害怕,她怕沃尔特发现一切,她软弱,她想逃离这样的婚姻和查理相拥,但是她不敢付诸行动。当沃尔特推着她去面对一切的时候,当一切都败露的时候,她终究是看见了人性的恶,她见到了查理的无情和所谓的爱只是言语上的“美好”,她也真正的感到了恐惧,面对死亡的恐惧和面对沃尔特不可捉摸的想法的恐惧。在湄潭府之行的初期,她依旧活在对于过去的回忆之中,她害怕恐惧,她看不见未来。但被圣光照耀的庙堂始终矗立在彼岸,她向往那迷人的色彩,她想要去追求新的生活。当她跨出第一步并开始在修道院工作的时候,她的觉醒之路从此慢慢开始,她慢慢和自己和解,慢慢看淡背叛,慢慢看清查理,慢慢找寻着自己在生活中该有的样子,她开始慢慢摘掉自己的面纱,看清自己的虚伪和软弱,开始追寻道的意义,她感悟周围的一切,甚至想着能够将沃尔特从他的痛苦中将他拉出。然而直到沃尔特死去,他们都没能和解,她愧对沃尔特,她看到了自己的轻贱,可是她自始至终都跨不过沃尔特竖起的高墙,她在墙外,沃尔特在墙内,各自在各自的面纱下痛苦的活着。沃尔特的死,是沃尔特的一种“解脱”,也是凯蒂的一种解脱。
|
||||
|
||||
@@ -20,7 +27,7 @@ mood: '#7c3aed'
|
||||
>
|
||||
> 所谓《面纱》,遮住的不仅是真相,更是我们面对真实自我的勇气。沃尔特缺乏这份勇气去接纳不完美的爱与人性,所以他困死其中;凯蒂在生死关头被迫拥有了这份勇气,所以她获得了自由。
|
||||
>
|
||||
> 故事的最后,死去的人得到了安息,活着的人带着记忆与教训,继续前行。这或许就是毛姆想告诉我们的:****人生没有完美的结局,只有在认清生活的残酷真相后,依然选择热爱并真实地活下去,才是唯一的救赎。****
|
||||
> 故事的最后,死去的人得到了安息,活着的人带着记忆与教训,继续前行。这或许就是毛姆想告诉我们的:**_人生没有完美的结局,只有在认清生活的残酷真相后,依然选择热爱并真实地活下去,才是唯一的救赎。_**
|
||||
|
||||
从背叛到自我的救赎,凯蒂在远离家乡的异国,没能逃脱欲望作祟,再次回到香港之后又一次沦陷在了查理的怀抱中,但此刻的她早已不是去湄潭府的之前的自己,她的思想早已骤变。她厌恶自己肉体对自己灵魂的出卖,她选择远离这个是非之地,她选择去重新追求自由,亦如从湄潭府离开一样,她再一次走上了追寻自由的路上。她回到故土,见母亲离世,见妹妹怀有身孕,见父亲终于脱离过去的自己,她亦想脱离过去的自己,拥抱一个崭新的未来。活在面纱之下的她和她的父亲,在故事的结局,两人袒露心声,一同前往新的地方开始新的生活,去活出新的自己。
|
||||
|
||||
@@ -30,7 +37,7 @@ mood: '#7c3aed'
|
||||
|
||||
沃尔特死在了他的‘正确’里,而凯蒂活在了她的‘真实’中。沃尔特的故事是带有悲惨的个人主义的牺牲,凯蒂的故事是带有幸运的个人主义的新生。沃尔特自觉没有希望,在困顿的泥潭中深陷,他没有勇气去面对新的生活和新的世界,他将自己紧紧隐藏在自己的面纱之后,他活在自己为自己构建的信念世界之中,为自己的信念,"悲壮”赴死。凯蒂活在纷繁的社会中,从小我见大我,从假我见真我,从困局中得自由。她一步一步从虚伪的面纱下得见人性、社会冷暖以及新世界的希望,湄潭府之前的生活让她自省,湄潭府之行让她感知到一切,在死亡和黑暗中得到感悟和觉醒,得见光明,再次回到香港,欲望再次作祟,这一次她没有沉沦于虚妄的现实,她终于携手勇气做出了自己的决定,踏上了"离开”的归程。
|
||||
|
||||
故事的最后,面纱已落。凯蒂带着伤痕,牵着父亲的手,走向巴哈马的阳光。那里没有豪门的虚妄,没有激情的幻象,只有平凡而坚实的生活。这,或许就是毛姆留给所有在虚荣与迷茫中挣扎的人们,最温柔的答案:****唯有真实,可得自由。****
|
||||
故事的最后,面纱已落。凯蒂带着伤痕,牵着父亲的手,走向巴哈马的阳光。那里没有豪门的虚妄,没有激情的幻象,只有平凡而坚实的生活。这,或许就是毛姆留给所有在虚荣与迷茫中挣扎的人们,最温柔的答案:**_唯有真实,可得自由。_**
|
||||
|
||||
在《面纱》的故事中,沃尔特是极具“英雄主义”的个人,为自己固有的信仰慷然赴死;凯蒂是在纷杂的社会中逐步成长,在欲望和虚荣的交叉间度过早年,湄潭府一行,在黑暗中见光明,在死亡中见希望,在异国见真我,在“新世界”得自由。
|
||||
|
||||
@@ -39,51 +46,28 @@ mood: '#7c3aed'
|
||||
------------------------------------------------------------------------
|
||||
|
||||
- 我不了解你,我一点儿都不了解你。
|
||||
|
||||
- 我知道你愚蠢、轻浮、没有头脑,但是我爱你。我知道你的目标和理想既庸俗又普通,但是我爱你。我知道你是二流货色,但是我爱你。想一想真是好笑,我竭力去喜欢那些讨你喜欢的东西,忍受折磨也要对你隐瞒起自己,实际上我并不无知粗俗、不爱散播丑闻也不愚蠢。我知道你何等害怕智慧,便尽我所能让你觉得我是个大傻瓜,跟你认识的其他人一样。我知道你嫁给我只图一时利益,我是那样爱你,我不在乎。大多数人,就我所知,当他们爱一个人,却没有得到爱的回报时就会觉得委屈不平,甚至愈发愤怒和痛苦。我不是那样,我从来没有指望你爱我,我看不出任何理由让你爱我,我也从来没想过自己会被人爱。我很感激能被允许爱你,当我时常想起你高兴跟我在一起,或者当我发现你眼中闪烁着愉快的爱意时,我就会欣喜若狂。我尽量不让我的爱来烦扰你,我知道那会让我承受不起,所以我一直察言观色,留意我的爱让你厌烦的最初迹象。大部分丈夫认为那是一种权力,我却准备当成恩惠来接受。
|
||||
|
||||
- 他知道你虚荣、懦弱、自私自利,他想让我亲眼看到这一切。他知道你会像野兔一样,危险一来就跑得远远的。他知道我深受蒙骗才会认为你爱上了我,因为他知道你不会爱任何人,只爱你自己。他知道你会牺牲我,好让自己毫发无损地逃脱出去。
|
||||
|
||||
- 死亡既突然又无情,在一条条曲折的街巷上匆匆穿过。
|
||||
|
||||
- 黎明乍现,阳光触到雾霭,令其闪闪发白,犹如雪之幽灵降至即将熄灭的星宿之上。
|
||||
|
||||
- 你瞧,这里只有你跟我踏踏实实在坚实的地上行走。修女们走在天上,而你丈夫则走在黑暗里。
|
||||
|
||||
- 他的灵魂撕裂了,他一直活在一种虚假的构想之中,当真相击碎了幻象,他便认为现实本身也被击碎了。
|
||||
|
||||
- 这个时辰的城市街巷空空荡荡,比任何时候都更像一座死城。路人一个个神色迷离,不禁让人怀疑他们全都是鬼魂。天空晴朗无云,初升的太阳将圣洁和煦的光芒洒满大地。很难想象,在这个愉悦、清新、爽朗的黎明,这座城市却像被疯子一手扼住咽喉的人,在瘟疫的黑暗掌控下苟延残喘。不可思议的是,当人类在痛苦挣扎、在恐惧中走向死亡时,大自然(蓝色的天空如孩童的心一般清澈)竟会如此无动于衷。
|
||||
|
||||
- 安宁只存在于人的灵魂中。
|
||||
|
||||
- 所有的人,乃至整个人类,就如同这条河里的水滴一样,流淌不定,一滴滴彼此接近,却又相距遥远,汇成一股无名的巨流奔向大海。既然一切转瞬即逝,任何事物都无关宏旨,人们竟还要荒唐地看重那些微不足道的事情,让自己也让别人遭受不幸,这实在太可悲了。
|
||||
|
||||
- 小小的两滴水随着河流默默流向未知,这两滴水相对而言是那样独特,但在旁观者的眼里,不过是河流中无法辨识的组成部分。
|
||||
|
||||
- 我又明白什么呢,生命是那么奇特。我觉得自己就像个一辈子都住在小池塘边上的人,突然间看见了大海,让我有点喘不过气来,但心里又充满了喜悦。我不想死,想活下去,于是感到一股新的勇气。我就好像那些老水手,起航驶向尚未发现的海洋,我的灵魂渴求未知的一切。
|
||||
|
||||
- 小小的两滴水默默地、默默地流向那黑暗、永恒的大海。
|
||||
|
||||
- 道。我们有些人在鸦片中寻找道,有些人在上帝那儿,有些人寻求威士忌,或去爱里寻找。这道终归只有一条,可它不通向任何地方。
|
||||
|
||||
- 这一切既新奇又感人,她的心灵犹如一片平静而灰暗的大海,波浪起伏,蔚然壮观,阴郁浩瀚令人畏惧,突然间,一缕阳光投射过来,让一切变得活泼、友好而快乐。
|
||||
|
||||
- 人生何其短暂,世界本来就充满了苦痛,人们却还要折磨自己,这岂不太可怜了吗?
|
||||
|
||||
- 她的性格就像一片乡野,初见时感觉辽阔而冷漠,但不久就会在巍峨山岭的褶皱间发现一个个掩映在果树丛中的欢腾的村庄,看见郁郁葱葱的草地上欢快流淌的一条条小河。这番宜人的景象会让你惊奇,甚至熨帖安心,但远处狂风劲吹的黄褐色高地尚不足以使你感到自如自在。
|
||||
|
||||
- 只有一种办法赢得人心,那就是让自己成为人们会去爱的人。
|
||||
|
||||
- 我说不清。我不知道她们追求幻觉这一点是否真的那么重要,还是这种生活本身就很美好。我有个想法,唯一让我们有可能不带嫌恶地关注我们所生活的这个世界的,就是人类不断从混沌中创造出的美。他们描绘的画,他们谱出的乐曲,他们写的书,还有他们的生活。这一切中,最富于美的就是美好的生活,那是件完美的艺术品。
|
||||
|
||||
- 道就是道路和行道者。那是一条永恒的路,所有的生命存在行走其上,但它并非由生命存在所创造,因为它本身便是生命存在。它什么都是,又什么都不是。万物由道所生,与道相符,最后万物又回归于道。它是一块方形却无四角,是种声音却不被聆听,是幅图画却未有形状;它是一张巨大的网,网眼阔如海洋,却什么都无法穿过;它是万物寻求庇护的避难所;它无处可寻,但你‘不窥牖’便可‘见天道’;它要人学会欲无所欲,让一切顺其自然。谦卑者尽得保全,屈身者终将直立。‘祸兮,福之所依;福兮,祸之所伏’;但谁能说清什么时候会出现转折点?追求柔慈之人会如小孩子一样平和。柔慈为进攻者获取胜利,为守卫者求得保全。战胜自己的人最为强大。
|
||||
|
||||
- 自由!这便是一直萦绕在她心中的念头。尽管未来仍旧模糊,这一念头却像河上的薄雾,在清晨阳光的照耀下焕发虹彩。自由!不仅仅是挣脱烦恼的束缚,解除那让她消沉的伴侣关系的自由。自由!不仅是逃离死亡威胁的自由,更是逃离让她降低人格的爱情,逃脱所有精神束缚的自由,一种抽离出肉体的精神的自由。与自由相伴的,还有勇气,以及无论发生什么都毫不在乎的坚强气质。
|
||||
|
||||
- 她以为自己变了,以为自己意志坚强,会以一个冷静自持的女人的面目回到香港。心中掠过一个个崭新的念头,就像阳光下翻飞的黄色蝴蝶,让她对美好的未来充满期望。自由就像光的精灵召唤她,整个世界犹如一片广袤的平原,任她迈着轻快的步子昂首前行。她原以为自己已摆脱了肉欲和卑劣的情爱,足以过上一种纯洁健康的精神生活。她曾将自己比作黄昏时分悠然飞跃稻田的白鹭,它们就像安闲自处的头脑中翩然翱翔的片片思绪,可实际上她却仍是欲望的奴隶。
|
||||
|
||||
- 我有希望,也有勇气。
|
||||
|
||||
- 你用面纱遮蔽他人的窥探,自己也难以一睹世界之实。
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
---
|
||||
title: "泥潭"
|
||||
title: 泥潭
|
||||
pubDate: 2026-02-09
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#166534'
|
||||
tags:
|
||||
- read
|
||||
pinned: false
|
||||
mood: '#686868'
|
||||
bookAuthor: 刘楚昕
|
||||
description: 迷失在黑夜中时,不妨抬头看看星空;如果不知道该相信什么,人应当面对自己的良知。
|
||||
cover: ./assets/泥潭.png
|
||||
hiddenTag: false
|
||||
draft: false
|
||||
---
|
||||
|
||||
### 迷失在黑夜中时,不妨抬头看看星空;如果不知道该相信什么,人应当面对自己的良知。
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: "pi-mesh 全家桶详解:单机多智能体协同的四件套"
|
||||
pubDate: 2026-11-01
|
||||
category: tech
|
||||
tags: [工程实践, AI 协作]
|
||||
---
|
||||
|
||||
[pi](https://github.com/earendil-works/pi) 是我们日常用的编码智能体宿主。单开一个终端时它是个安静的助手;但同时开三四个终端、每个里面一个 agent 抢同一个仓库时,问题就来了:谁在线?谁在改哪个文件?消息发出去了对方到底看没看见?半夜跑着的构建谁有权停?
|
||||
|
||||
围绕 [pi-mesh](https://github.com/cgarrot/pi-mesh) 长出来的一族小工具把这层协同做成了基建。它们各管一摊,咬合成套:
|
||||
|
||||

|
||||
|
||||
## 一、pi-mesh:通信骨干
|
||||
|
||||
pi-mesh 是一条本机总线:一个脱离会话存活的 broker(NDJSON 帧走 unix socket),每个 pi 终端以别名成为 peer,加入房间(room)。消息 < 50ms 到达,零运行时依赖,Node ≥ 20。
|
||||
|
||||
它最值得称道的是**诚实状态模型**:`delivered`(写进了对方套接字)≠ `read`(注入了对方会话)≠ `answered`(拿到了显式回复)——三者永不混淆,没有一个是"完成"的意思。配套的**持久台账是 hash-only 的**:消息正文永不落盘,重启后能审计"谁在何时给谁发过什么",但看不到内容。这个自我约束后来成了整个工具族的设计公理:**mesh 只做路由器,永不做数据库。**
|
||||
|
||||
协同刚需有三个都在骨干里:
|
||||
|
||||
- **`mesh_reserve` 路径预约**:改文件前先预约,其他 peer 的 `edit`/`write` 工具在该路径上直接被拦——并发编辑靠机制防,不靠喊话。
|
||||
- **`mesh_claim` 任务租约**:单写者锁,带 `epoch:fence`(broker 重启即作废旧围栏);提交信息里写 `Mesh-Lease: task#leaseId#epoch:fence`,配套 commit-msg 钩子直接拒绝过期围栏的提交。
|
||||
- **能力路由 `cap:`**:`mesh_send({ "to": "cap:deploy", ... })` 把任务发给任意一个空闲且自报该能力的 peer,而不是等忙着的队友。消息优先级三档:normal 排队、urgent 插队、force 先中止对方当前回合再送达(force 属管控类,必须带 reason)。
|
||||
|
||||
另有 `mesh_wait_all` 做群裁(一次收回“谁答了、谁没答”的名单)、`mesh_status` 看 peer 忙闲(● 干活 / ○ 空闲 / ✕ 卡死)、`pimesh` CLI 支持无会话接入(`pimesh attach` 能收养死掉会话的身份)。跨机部署也支持:一台起 broker,其余机器做客户端。
|
||||
|
||||
诚实的颗粒度细到退出码:CLI 里 3 表示 `expired`——晚到的答复仍会送达,只是你等起见超时了;5 表示租约已过期,`commit-msg` 钩子据此把提交拒之门外。甚至 ESC 清空消息队列这种宿主边界也考虑到了:批量注入若撞上回合中止,broker 会在该回合结束后经中止安全通道重投(有界重试),状态页能看到“中止清除恢复重投”计数。
|
||||
|
||||
## 二、pi-board:团队记忆板
|
||||
|
||||
消息是易逝的,台账是 hash-only 的,那"我们学到了什么"存哪?[pi-board](https://github.com/wait4xx/pi-board) 补上这层。五种类型化条目:
|
||||
|
||||
- **OBSERVED**——看到了什么(不承诺)
|
||||
- **FACT**——验证过的事实
|
||||
- **FAIL**——一条被证伪的路(价值最高:全队不必再烧第二次预算)
|
||||
- **CLAIM**——认领声明,与 `mesh_claim` 租约配对(板只记录,从不执行)
|
||||
- **PATCH_SUMMARY**——落了什么改动
|
||||
|
||||
设计上处处是“有界与诚实”:append-only 本地存储、refuse-not-evict(超限拒绝写入而非静默淘汰)、条目默认短小、长文 `detail` 只能按 id 展开、布尔检索(`,` = OR,`&` = AND)覆盖全史而 `board_read` 只看近期窗口;检索无果时返回诚实的空,绝不猜一条充数。写入以普通广播帧发进房间,turn 边界有新 FAIL 摘要行;房间角色 member / observer 沿用 mesh 语义,只读 peer 永远不会在重连后被误升为写者。它以普通 peer 身份入房——**存储全在板自己家里,broker 里零持久化**。附带的 `piboard` CLI 五个动词一把梭:join(member / observer)/ watch / write / read / grep,一动词一动作,无会话的终端也能进房只读围观。这套类型学与"小条目+按需展开+近期记忆/全史检索"的分工直接来自学术论文(血统故事见[下一篇](/archives/agensh/))。
|
||||
|
||||
## 三、pi-jobd:后台作业守护
|
||||
|
||||
`bash` 会阻塞当前回合,`/reload` 会杀进程。[pi-jobd](https://github.com/cgarrot/pi-jobd) 把长命令放进独立进程组,由 per-user 守护监管:~150ms 返回,作业存活于会话之外。什么时候用它?三条判据:命令预计超过 60-90 秒;你必须边跑边干别的;或者它得扛过 `/reload` 和会话结束——以及"别的 agent 应该能停它"这一条:`mesh_job_control` 让任一授权 peer 远程 stop/pause/resume 别人的后台作业。
|
||||
|
||||
状态机同样诚实:`running / paused / stopped / exited / failed`,外加一个 `lost`——守护重启后无法证明 pid 归属时,宁可拒绝 stop 也绝不信号一个来路不明的进程。这个包还有个巧思:装包不够,模型还得**知道什么时候用**——它靠 prompt 快照、七个工具上的决策守则和一份按需加载的 skill 三层把使用时机喂给模型。
|
||||
|
||||
## 四、pi-subagents:worker 控制
|
||||
|
||||
[pi-subagents](https://github.com/nicobailon/pi-subagents) 管的是进程内子代理:前台子代理流式跑在对话里,后台子代理活在独立的 runner 进程中随时可查。装完即用,不需要学斜杠命令——直接说"用 reviewer 审这个 diff"就行。它预置了 scout(侦察)、reviewer(审查)这类常用角色,也支持保存的工作流和并行审查。与 mesh 的接口是 `mesh_run_control`:远程 stop 或 steer 一个 peer 的 RPC worker。
|
||||
|
||||
## 五、四件套的一次合奏
|
||||
|
||||

|
||||
|
||||
## 六、上手五分钟
|
||||
|
||||
安装各一行:mesh 与 subagents 走 pi 包管理——`pi install npm:pi-mesh-extension`、`pi install npm:pi-subagents`;pi-board 还没发 npm,源码三步(clone、`npm ci`、`npm run build`),且可 CLI 独立用(不需要 mesh 也能读写本地板);pi-jobd 同样源码构建,`pijob run / list / log / stop` 开箱即用。装完在同一目录开两个 pi 会话,就是两个 peer,`mesh_status` 互相可见——协同基建的“零配置”不是口号。
|
||||
|
||||
对话走 mesh,记忆上 board,长活交 jobd,排他靠租约——四个包各司其职,没有任何一个越界。这个博客的重设计期就是这套东西的实战场:两个 mesh 终端(op-gitea、op-artalk)与主线并行,一天 19 连发;发布链中断的修复窗口里,主线预约了 build.sh 紧急处置,另一边赶去补刀的编辑被预约机制正确拦下——撞车靠规则防住,不靠谁手快。
|
||||
|
||||
四件套加起来没有一个数据库、没有一个中心大脑,却把"谁在线、谁在干啥、学到过啥、长活谁看着"四件事全部钉死。小工具、硬边界、诚实状态——这套哲学比任何一个具体包都值得抄。
|
||||
@@ -1,9 +1,16 @@
|
||||
---
|
||||
title: "赎罪"
|
||||
title: 赎罪
|
||||
pubDate: 2026-02-26
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#9f1239'
|
||||
tags:
|
||||
- read
|
||||
pinned: false
|
||||
mood: '#bf002f'
|
||||
bookAuthor: 伊恩·麦克尤恩
|
||||
description: 亲爱的塞西莉亚,我们缘分未尽
|
||||
cover: ./assets/赎罪.png
|
||||
hiddenTag: false
|
||||
draft: false
|
||||
---
|
||||
|
||||
### 你要回来,我会等你。
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: "Spec Harness 详解:把长程开发装进可验收的任务包"
|
||||
pubDate: 2026-11-01
|
||||
category: tech
|
||||
tags: [工程实践, AI 协作]
|
||||
---
|
||||
|
||||
AI 编程智能体最不缺的是写代码的带宽,最缺的是记忆。一个跨了好几轮会话的任务,改到一半的方案、已经否决的路线、验收口径,散落在聊天记录里就等于丢了——换一个会话、重启一次终端,上下文归零。[Spec Harness](https://github.com/MicTx/spec-harness) 解决的就是这个问题:它是一个面向 AI 编程智能体的任务包工作流工具,把长程改动保存为**可恢复、可验证的 Development Record**,用范围、证据和 Git 门禁管住全程。它不提供模型,也不替你做产品决策。
|
||||
|
||||
一句话概括它的世界观:**当一个编码任务的寿命超过一段对话,缺的不是更聪明的模型,而是一份大家(包括未来的你)都能读的合同。**
|
||||
|
||||
## 一、核心抽象:任务包
|
||||
|
||||
所有工作装进 `.spec/specs/YYYY-MM-DD_slug/` 目录,核心是两个文件:`spec.md` 写范围、假设与取舍(还有一节很特别的"已证伪路径",后面细说),`tasks.md` 写任务清单。收尾后整包归档到 `.spec/specs/archive/`,归档目录本身就是项目的工程日志。
|
||||
|
||||
每个任务必须带两行合同——**boundary**(这个任务能动哪些文件)和 **verify**(怎么证明做完了)。verify 是只读断言,跑出退出码 0 才许勾选。拿这个博客真实归档的 `2026-10-26_blog-5f` 包里 T3 任务举例:
|
||||
|
||||

|
||||
|
||||
boundary 划定"能动什么",verify 划定"怎么证明做完"——两行齐备,缺一不勾。这不是形式主义:多 agent 并行时,boundary 就是车道之间的护栏(我们的实践里,全局样式文件只许主线动,车道要新变量走申请);verify 则把"看起来好了"和"证明做完了"区分开,口头"过了"永远不算数。
|
||||
|
||||
`spec.md` 里最有味道的一节是**已证伪路径**:被否决的方案连否定理由一起入库。我们的 blog-5f 包里记着三条——首屏 banner 化(视觉裁定否决)、热力图换绿(破坏灰阶骨架)、标题 27px 收到 22px(层级锚点,要轻减字重不减号)。下次谁想再试一遍,先读档案,少烧一轮预算。
|
||||
|
||||
## 二、阶段流水线:五个核心命令与它们把守的门
|
||||
|
||||

|
||||
|
||||
五个核心命令串起一条可验证链:**目标 → 范围 → 任务 → 验证 → 归档 → 提交 → 推送**。每个阶段之间都有门把守:`spec.md` 有模板占位符就拒绝进入 run;`done` 要求每个任务 boundary/verify 齐备、验收面全绿且至少有一条非占位证据(脚本输出、测试结果);`push` 只合并 `spec/` 前缀的集成分支,文件改动与提交永远分开做,禁用 `--no-verify`。
|
||||
|
||||
被阻塞的任务会打 `!` 标记上浮给用户,绝不静默跳过;复核轮(check)发现的可执行问题必须当轮写回任务包并修复,不允许把"待办"当交付物甩给用户。
|
||||
|
||||
**中断恢复是常态设计**——这是它对“任务寿命超过对话”最直接的回答:任意时刻重跑 `/spec:run`,从 `tasks.md` 的勾选状态续跑,不依赖任何对话记忆。更进一步,`/spec:autoplan` 做交互式规划(澄清目标后过框架、细节、复核三轮,规划就绪即停,不自动开工),`/spec:autorun` 按规划逐包执行,默认最多 5 轮自动链(`--max-rounds` 可调),暂停后从同一步恢复;一键式入口 `/spec:goal` 则把规划、执行、复核、归档、提交一次跑完。
|
||||
|
||||
命令面共 12 个阶段命令:core 档装五个核心命令,full 档补齐 goal / autoplan / autorun / update / status / doctor / organize 七个扩展件——`doctor` 做环境自检与修复,`organize` 做结构审计。还有一个容易被忽略的细节:安装只是放文件,不会主动逐任务评估;想让它在你动手前主动问一句“这事要不要开包”,需要显式接线一次 `install_activation_hooks.py --mode on`。判据看交付形状——独立验收面、实际实施者、可恢复的在途任务;小修、只读调查、一次性实验都直接做。它防的是“忘了评估”,不防刻意瞒报——文档里对此毫不讳言。
|
||||
|
||||
## 三、多 agent 编排:一个编排者,有边界的帮工
|
||||
|
||||
Spec Harness 对多 agent 的态度很克制:**主会话是唯一的编排者**,路由、集成、验收和 `done`/`push` 门禁永远不出主线。旁支工作(探索、构建、审查这类上下文无关的活)可以委托,但要先过机器路由决策(`scripts/route_decision.py`)并写下任务合同:目标、范围、排除区、产出、验证方式五项齐备才许派。它甚至为"跨会话交接"准备了可选的 `handoff.md`——生成式快照头加 append-only 条目日志,单人、多人、agent、集群四种协作模式各有一套必填小节。
|
||||
|
||||
扩展位(slots)机制则把"别的团队流程"插进来:`team-loop` 管团队循环、`workflow-runner` 管工作流扇出,各自带 README 协议和测试。源码库里还附了 orchestrator/planner/reviewer/confirmer 四个编排代理定义。
|
||||
|
||||
## 四、安装:一个安装器,十余个宿主
|
||||
|
||||
Spec Harness 是 Python 3.9+ 的标准库实现(PolyForm Noncommercial 许可),安装就三步:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
安装器支持十余个宿主:Claude Code、Codex、Gemini CLI、Grok、OpenCode、OpenClaw、Hermes、Pi、ZCode、DeepSeek Harness、Devin、Cursor……不指定时自动探测已装的宿主。安装档位分 core / full:core 只装五个核心阶段命令加 Git 钩子;full 补齐全部 12 个阶段命令和 slots、agents 扩展组件。我们在 Pi 上用 `INSTALL_HOSTS=pi bash install.sh`,装完 `/spec` 系列命令直接可用。
|
||||
|
||||
## 五、跑在一个真实博客上
|
||||
|
||||
这个博客就是它的用户。归档目录里躺着从裸 clone 构建改造、移动端布局修复到 SEO 解封的一串任务包:blog-5f 一个包八个任务、三条车道(两条 mesh 终端加主线)并行,每个任务的 verify 都是能在命令行跑出退出码的断言,页面类任务统一挂 E2E——`E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs`,34 项全过才算数。包与包接力,一天 19 个提交全绿落地。
|
||||
|
||||
门禁也真的拦过烂账:一次一个全 manual、从未执行的任务包混进了 `.spec/specs/`,check 门禁直接判 blocked,连带拦下所有携带归档路径的提交——直到把它移出。门禁防的不只是烂尾,还有“看起来有规划”的空包。
|
||||
|
||||
工具会迭代,但这套思路不会过期:**把"要干什么"写成合同,把"做完了"写成证据,把过程归档成日志。** 无论协作方是人还是 agent,这三件事都是长程工程的地基。
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
title: 写作模板:这篇文章本身就是模板
|
||||
pubDate: 2026-11-02
|
||||
category: tech
|
||||
tags:
|
||||
- 写作
|
||||
- 模板
|
||||
pinned: true
|
||||
description: 一篇文章演示全站可用的排版能力:标题层级、彩色强调、字族、引用、代码窗、表格、图、链接卡、折叠。复制它开始写新文。
|
||||
hiddenTag: true
|
||||
draft: false
|
||||
---
|
||||
|
||||
这篇不是教程,是**可复制的模板**。下面每一节都在真跑——你看到的效果,就是 Markdown 源码能产出的效果。写新文章时,把本文件复制一份、改个 slug、删掉不需要的段落即可。
|
||||
|
||||
它同时是**置顶功能**的活样本:`frontmatter` 里的 `pinned: true` 会让它排在首页、归档与书架的最前面,而日期、URL、订阅源里的时间都不变。
|
||||
|
||||
## 一、标题层级与段落
|
||||
|
||||
正文用 `##` 作一级节标题,`###` 作二级。**不要用 `#`**——文章标题由 frontmatter 的 `title` 渲染,正文再写 `#` 会出现两个一级标题,屏幕阅读器与搜索摘要都会困惑。
|
||||
|
||||
段落之间空一行即可。行内可以**加粗**、*斜体*、~~删除线~~、`行内代码`,也可以放[普通链接](https://astro.build)。
|
||||
|
||||
## 二、彩色强调:想用多少用多少
|
||||
|
||||
颜色现在**没有次数限制**(2026-11-02 起取消了原来的「一页最多三处」)。唯一还守着的是外壳:导航、页脚、按钮、背景保持灰阶——外壳是气质,正文是表达。
|
||||
|
||||
<span class="c-blue">第一处:一个关键结论用蓝色提出。</span> 后面照常叙述。
|
||||
|
||||
<span class="c-red">第二处:需要警示的坑用红色。</span> 比如「改完 Markdown 插件必须清 `node_modules/.astro`,否则改动静默不生效」。
|
||||
|
||||
<span class="c-green">确认可行的路径用绿色。</span> 想让整段都是重点也不必克制,把它们都标出来即可。
|
||||
|
||||
可用色相共 8 组(blue / teal / green / yellow / orange / red / pink / purple),每组有标准与 `-soft` 两档;另有 4 个灰阶层级类(`ink-muted` / `ink-warm` / `ink-cool` / `ink-faint`)用于降调,不算彩色额度。
|
||||
|
||||
## 三、字族:换字体只作用于被包裹的文字
|
||||
|
||||
正文默认是无衬线。想给某句话换气质,用类包起来就行:
|
||||
|
||||
<span class="font-kai">这一句是楷体。适合题记、书评里引的原句,或者需要慢一点读的段落。</span>
|
||||
|
||||
<span class="font-huiwen">这一句是汇文明朝体。长段落用它会明显降低阅读速度,所以只用在短句上。</span>
|
||||
|
||||
可用字族共 6 族(`font-kai` 楷、`font-song` 宋、`font-hei-sys` 黑、`font-fangsong` 仿宋、`font-huiwen` 汇文明朝、`font-kuaile` 快乐体),另有 `font-art-kuaile` 艺术字标题类。**注意体积**:整页换字族会让访问者下载一份中文字体,正文默认字体不要轻易改。
|
||||
|
||||
### 三级标题长这样
|
||||
|
||||
比二级小一档、加粗、上下留白更紧。用于把一节拆成几个小话题。
|
||||
|
||||
## 四、引用与列表
|
||||
|
||||
> 引用块用于摘录别人的话。署名可以换成斜体放在最后。
|
||||
>
|
||||
> —— 一句虚构的署名
|
||||
|
||||
无序列表:
|
||||
|
||||
- 第一项
|
||||
- 第二项,可以带 `行内代码`
|
||||
- 第三项,可以嵌套:
|
||||
- 子项一
|
||||
- 子项二
|
||||
|
||||
有序列表适合步骤:
|
||||
|
||||
1. 复制模板
|
||||
2. 改 frontmatter 的 slug 与标题
|
||||
3. 删掉不需要的段落
|
||||
|
||||
## 五、代码块
|
||||
|
||||
四行及以上的代码块会带上灰阶窗条(三个点),短代码块不加——避免小片段看着像截图。
|
||||
|
||||
```ts
|
||||
// 排序契约:置顶优先 → 时间倒序 → id 兜底
|
||||
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();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
行内提到变量时用 `posts` 这种形式即可。
|
||||
|
||||
## 六、表格
|
||||
|
||||
| 能力 | 写法 | 注意 |
|
||||
| --- | --- | --- |
|
||||
| 彩色强调 | `<span class="c-blue">` | 不限次数(外壳仍灰阶) |
|
||||
| 换字族 | `<span class="font-kai">` | 整页换会拉字体体积 |
|
||||
| 图片 | `` | 体积纪律见写作指南 |
|
||||
| 视频 | frontmatter `video:` | 放 public/media,≤50MB |
|
||||
| 链接卡片 | 裸链接单独成段 | 构建期抓 OG,失败回落普通链接 |
|
||||
|
||||
## 七、图片
|
||||
|
||||
图片用相对路径引用,挂在 `content/posts/assets/<slug>/` 下,点击可放大(矢量图不糊):
|
||||
|
||||

|
||||
|
||||
## 八、链接卡片
|
||||
|
||||
一个裸链接单独成段,构建期会抓取 OG 预览生成卡片;抓不到就退回普通链接,不会坏版:
|
||||
|
||||
https://astro.build
|
||||
|
||||
## 九、折叠长内容
|
||||
|
||||
长附录、完整日志、大段配置可以折叠,零 JS,原生 `details`:
|
||||
|
||||
<details>
|
||||
<summary>展开看一个被折叠的长例子</summary>
|
||||
|
||||
折叠区里可以放任何东西——段落、列表、代码块。
|
||||
|
||||
- 适合放「按需查看」的细节
|
||||
- 不适合放结论(读者可能永远不展开)
|
||||
|
||||
</details>
|
||||
|
||||
## 十、分隔线之后收尾
|
||||
|
||||
---
|
||||
|
||||
收尾三句就够:这篇讲了什么、读者该带走什么、下一步去哪。模板不是要你写满十节,而是**在你需要表达时,能力都在手边**。
|
||||
|
||||
下一篇开始,复制本文件,删到只剩你要说的。
|
||||
@@ -0,0 +1,153 @@
|
||||
# 关于页(后台「页面文案 → 关于页」可改)
|
||||
# ── 一级:blocks 块式正文(按顺序渲染,可拖拽排序;某块 enabled: false 即隐藏)
|
||||
# ── 二级:intro 多段 markdown(仅在 blocks 为空时使用)
|
||||
# ── 三级:内置默认文案(两者都空时兜底,构建永不炸)
|
||||
layout:
|
||||
# 名片区(头像/名字/身份语/社交)开关:头像等数据事实源在「主页设置」
|
||||
showIdCard: true
|
||||
|
||||
blocks:
|
||||
- type: prose
|
||||
width: narrow
|
||||
body:
|
||||
zh: |
|
||||
<span class="font-huiwen c-red">写作者</span> · 读者 · 快门按压者,拥抱开源。
|
||||
|
||||
这里是我三条轨道的公开工作台:**读过的书**留下评注,**写过的代码**进项目墙,**按下的快门**收入图库。
|
||||
|
||||
所有内容都有一个明确去处:
|
||||
|
||||
- 长文沉淀在文章与书架
|
||||
- 短句与随手拍落在瞬间
|
||||
- 影像收进图库,配一点 EXIF 与一句策展
|
||||
|
||||
本站本身即开源,构建与发布全链都在仓里。
|
||||
en: |
|
||||
<span class="font-huiwen c-red">Writer</span> · Reader · Shutter-presser, embracing open source.
|
||||
|
||||
This is the public workbench of my three tracks: **books** that left notes, **code** that reaches the project wall, and **shutters** I pressed that land in the gallery.
|
||||
|
||||
Everything has a clear destination:
|
||||
|
||||
- Long-form writing goes to posts and the bookshelf
|
||||
- Short lines and snapshots land in moments
|
||||
- Images go to the gallery, with a bit of EXIF and one line of curation
|
||||
|
||||
This site is itself open source — the full build and deploy pipeline lives in the repo.
|
||||
|
||||
- type: tracks
|
||||
title:
|
||||
zh: 兴趣版图
|
||||
en: Interests
|
||||
items:
|
||||
- icon: book
|
||||
name:
|
||||
zh: 阅读
|
||||
en: Reading
|
||||
desc:
|
||||
zh: 文学与戏剧为主,读到哪写到哪。
|
||||
en: Mostly literature and drama; I write as I read.
|
||||
- icon: terminal
|
||||
name:
|
||||
zh: 技术
|
||||
en: Tech
|
||||
desc:
|
||||
zh: Linux、容器与自托管,把家里的小机器折腾成顺手的工具。
|
||||
en: Linux, containers, and self-hosting — turning the little machines at home into handy tools.
|
||||
- icon: camera
|
||||
name:
|
||||
zh: 摄影
|
||||
en: Photography
|
||||
desc:
|
||||
zh: 街拍与山野,随身一台无反,在等光的间隙按快门。
|
||||
en: Street and landscapes with a mirrorless camera, pressing the shutter while waiting for the light.
|
||||
|
||||
- type: prose
|
||||
title:
|
||||
zh: 关于我
|
||||
en: About me
|
||||
body:
|
||||
zh: |
|
||||
气象是我起点的专业——一个想做预报的人,依旧把云图当日常读物。
|
||||
|
||||
写代码是第二条主线:Linux、容器、自托管,把日常工具一件件磨顺手。
|
||||
|
||||
作品大多在 [wait4x.online](https://wait4x.online/)。
|
||||
|
||||
信条:**拥抱开源,热爱开源**。
|
||||
en: |
|
||||
Meteorology is where I started — an aspiring forecaster who still reads weather charts for fun.
|
||||
|
||||
Coding is the second thread: Linux, containers, self-hosting — polishing everyday tools one by one.
|
||||
|
||||
Most of my work lives at [wait4x.online](https://wait4x.online/).
|
||||
|
||||
Motto: **embrace open source, love open source**.
|
||||
|
||||
- type: linklist
|
||||
title:
|
||||
zh: 找到我
|
||||
en: Find me
|
||||
items:
|
||||
- label:
|
||||
zh: 本站
|
||||
en: Website
|
||||
href: 'https://wait4x.online/'
|
||||
note:
|
||||
zh: 文章、图库与瞬间
|
||||
en: Posts, gallery and moments
|
||||
- label:
|
||||
zh: 邮件
|
||||
en: Email
|
||||
href: 'mailto:X@alumni.nudt.edu.cn'
|
||||
note:
|
||||
zh: 有话直说
|
||||
en: Say hello directly
|
||||
- label:
|
||||
zh: GitHub
|
||||
en: GitHub
|
||||
href: 'https://github.com/wait4xx'
|
||||
note:
|
||||
zh: 开源项目与提交
|
||||
en: Open-source projects and commits
|
||||
- label:
|
||||
zh: 自托管 gitea
|
||||
en: Self-hosted gitea
|
||||
href: 'https://git.wait4x.online/wait4x/blog'
|
||||
note:
|
||||
zh: 本站源码
|
||||
en: Source of this site
|
||||
- label:
|
||||
zh: RSS
|
||||
en: RSS
|
||||
href: /rss.xml
|
||||
note:
|
||||
zh: 订阅更新
|
||||
en: Subscribe to updates
|
||||
|
||||
- type: prose
|
||||
title:
|
||||
zh: 本站如何构建
|
||||
en: How this site is built
|
||||
body:
|
||||
zh: 页面由 Astro 静态生成,不含运行时框架 JS;内容住在自托管 gitea——本地编辑器与 gitea 网页写的是同一个 git 仓库,push 之后由 homelab 自动构建发布;评论走自托管 Artalk。页脚 built from 一行链到本次构建对应的 commit,构建过程透明可追溯。
|
||||
en: Pages are statically generated by Astro with no runtime framework JS. Content lives on a self-hosted gitea — my local editor and the gitea web UI write to the same git repository, and each push is built and published automatically by the homelab. Comments run on self-hosted Artalk. The built-from line in the footer links to the commit behind this build, keeping the process transparent and traceable.
|
||||
|
||||
# ---- 二级回退(blocks 为空时使用)----
|
||||
intro:
|
||||
zh: |
|
||||
写作者 · 读者 · 快门按压者,拥抱开源。
|
||||
|
||||
这里是我三条轨道的公开工作台:**读过的书**留下评注,**写过的代码**进项目墙,**按下的快门**收入图库。
|
||||
en: |
|
||||
Writer · Reader · Shutter-presser, embracing open source.
|
||||
|
||||
This is the public workbench of my three tracks: **books** that left notes, **code** that reaches the project wall, and **shutters** I pressed that land in the gallery.
|
||||
|
||||
# 页面级排版覆盖(层 2)
|
||||
# 页面级排版覆盖(后台「关于页 → 页面排版覆盖」可改):
|
||||
# 整页换字族会带来字体体积成本(如 font-huiwen 整页 ≈694KB),默认留空=全站默认排版
|
||||
# typography:
|
||||
# font: font-huiwen # 字族:font-serif/font-sans/font-kai/font-huiwen/font-kuaile…
|
||||
# accent: c-red # 强调色类:c-blue/c-red/…(外壳仍灰阶)
|
||||
# scale: 1.05 # 字号缩放 0.8–1.25
|
||||
@@ -0,0 +1,18 @@
|
||||
tagline:
|
||||
zh: 拥抱开源,热爱开源
|
||||
en: Embracing open source, loving open source.
|
||||
latestPhotos: 4
|
||||
perTrackLimit: 6
|
||||
sections:
|
||||
tracks: true
|
||||
photos: true
|
||||
heatmap: true
|
||||
projects: true
|
||||
favicon: /favicon.svg
|
||||
socials:
|
||||
- label: GitHub
|
||||
url: https://github.com/wait4xx
|
||||
icon: github
|
||||
- label: 邮件
|
||||
url: mailto:X@alumni.nudt.edu.cn
|
||||
icon: email
|
||||
@@ -0,0 +1,19 @@
|
||||
typing:
|
||||
- zh: 气象出身,写代码为乐
|
||||
en: Trained in meteorology, coding for fun
|
||||
- zh: 自托管一切能自托管的东西
|
||||
en: Self-hosting whatever can be self-hosted
|
||||
- zh: 拥抱开源,热爱开源
|
||||
en: Embrace open source, love open source
|
||||
blurb:
|
||||
zh: 本站本身即开源:
|
||||
en: 'This site is itself open source:'
|
||||
repo_url: https://git.wait4x.online/wait4x/blog
|
||||
repo_label: git.wait4x.online/wait4x/blog
|
||||
blurb_after:
|
||||
zh: —— Astro SSG、双写作流、构建发布全链皆在仓中。以下是我在自托管 gitea 与 GitHub 上的全部公开仓库,以及最近一年的贡献活动。
|
||||
en: — the Astro SSG, dual-writing workflow, and the full build & deploy pipeline live in the repo. Below are all my public repositories on self-hosted gitea and GitHub, plus contributions from the past year.
|
||||
mode: all
|
||||
sort: pinned-first
|
||||
perSource: github
|
||||
intro_from: 'off'
|
||||
@@ -0,0 +1,37 @@
|
||||
# 主题旋钮(样式即数据 · 层 4):只放白名单内的 9 组旋钮。
|
||||
# 红线:--measure(版心)与断点/纸墨/code 配色不在此列——它们牵动版面结构与分轨身份。
|
||||
# 全部留空 = 沿用 src/styles/tokens.css 的既有值(零配置零变化)。
|
||||
# 钳制与单调性在 src/lib/theme.ts 里做:越界值被夹取而非报错,构建不会因主题配置失败。
|
||||
|
||||
# ① 强调色(明/暗两值;构建期闸门校验正文对比度 ≥4.5,不达标回落默认)
|
||||
accent_light: '#3454d1'
|
||||
accent_dark: '#8ea2ff'
|
||||
|
||||
# ② 图片圆角(4–16;08 文档裁定上限 14)
|
||||
radius_img: 10
|
||||
|
||||
# ③ 标题字号(22–32;必须 h1 > h2 > h3,否则整体回落默认)
|
||||
text_h1: 27
|
||||
text_h2: 22
|
||||
text_h3: 19
|
||||
|
||||
# ④ 正文字号(16–22)
|
||||
prose_size_reading: 20
|
||||
prose_size_tech: 19
|
||||
|
||||
# ⑤ 正文行高(1.5–2.0)
|
||||
leading_reading: 1.75
|
||||
leading_tech: 1.7
|
||||
|
||||
# ⑥ 间距梯度(8–80;必须严格递增 3 < 4 < 5 < 6,否则回落默认)
|
||||
space_3: 16
|
||||
space_4: 24
|
||||
space_5: 40
|
||||
space_6: 64
|
||||
|
||||
# ⑦ 段落流距(0–2em)
|
||||
flow_p: 0.9
|
||||
flow_h2_post: 0.6
|
||||
|
||||
# ⑧ 过渡时长(150–600ms)
|
||||
dur: 350
|
||||
@@ -0,0 +1,314 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>灯箱 v2.1 提案预览 · wait4x</title>
|
||||
<style>
|
||||
/* ===== 灰阶基底(对齐站 tokens.css:四档灰 + 唯一曲线族) ===== */
|
||||
:root{
|
||||
--bg:#fff; --bg-soft:#f7f7f8; --ink-1:#111113; --ink-2:#33343a; --ink-3:#6b6e78;
|
||||
--ink-4:#a0a3ad; --line:#e6e7eb; --radius-img:10px;
|
||||
--mono:"JetBrains Mono",ui-monospace,Consolas,monospace;
|
||||
--sans:"Noto Sans SC Variable","Noto Sans SC",system-ui,-apple-system,"PingFang SC",sans-serif;
|
||||
--ez:cubic-bezier(.4,0,.22,1); /* PhotoSwipe 333ms 节奏同族曲线 */
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--ink-2);font-family:var(--sans);}
|
||||
body.locked{overflow:hidden}
|
||||
.wrap{max-width:896px;margin:0 auto;padding:28px 20px 72px}
|
||||
kbd{font-family:var(--mono);font-size:11.5px;border:1px solid var(--line);background:#fff;
|
||||
border-radius:4px;padding:1px 6px;color:var(--ink-3)}
|
||||
|
||||
/* ===== 顶部说明:这是提案预览稿 ===== */
|
||||
.note{background:var(--bg-soft);border:1px solid var(--line);border-radius:10px;
|
||||
padding:14px 18px;font-size:13.5px;line-height:1.7;color:var(--ink-2);margin:18px 0 30px}
|
||||
.note strong{color:var(--ink-1)}
|
||||
.note ul{margin:6px 0 0;padding-left:18px}
|
||||
.note li{margin:2px 0}
|
||||
h1{font-size:27px;color:var(--ink-1);margin:8px 0 2px;font-weight:600}
|
||||
.sub{font-family:var(--mono);font-size:12.5px;color:var(--ink-4)}
|
||||
|
||||
/* ===== 影像墙:cover 裁切(与线上一致,起飞动画的前提) ===== */
|
||||
.stage{background:rgba(255,255,255,.04);border-radius:6px;padding:14px}
|
||||
.wall{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(3,1fr);
|
||||
grid-auto-rows:8px;grid-auto-flow:dense;gap:14px}
|
||||
.shot{position:relative;overflow:hidden;border-radius:var(--radius-img);grid-row-end:span var(--sp,13)}
|
||||
.shot button{display:block;width:100%;height:100%;padding:0;border:none;background:none;cursor:zoom-in}
|
||||
.shot img{display:block;width:100%;height:100%;object-fit:cover}
|
||||
@media (max-width:820px){.wall{grid-template-columns:repeat(2,1fr)}}
|
||||
@media (max-width:560px){.wall{grid-template-columns:1fr 1fr;gap:10px}}
|
||||
|
||||
/* ===== 灯箱:fixed 覆盖层(生产将等价几何搬回原生 <dialog>) ===== */
|
||||
.lbx{position:fixed;inset:0;z-index:80}
|
||||
.lbx[hidden]{display:none}
|
||||
.lbx-bg{position:absolute;inset:0;background:rgba(8,10,14,.96);opacity:0;
|
||||
transition:opacity 300ms var(--ez)}
|
||||
.lbx.on .lbx-bg{opacity:1}
|
||||
/* 双盒:win=裁切窗(动画 left/top/w/h),pic=内图(动画几何 + 缩放 transform) */
|
||||
.lbx-win{position:fixed;overflow:hidden;border-radius:6px;transform-origin:0 0;
|
||||
box-shadow:0 12px 48px rgba(0,0,0,.5);touch-action:none}
|
||||
.lbx-pic{position:absolute;transform-origin:50% 50%;user-select:none;-webkit-user-drag:none;
|
||||
transition:transform 90ms linear;cursor:zoom-in;display:block}
|
||||
.lbx.landing .lbx-win,.lbx.landing .lbx-pic{
|
||||
transition:left 300ms var(--ez),top 300ms var(--ez),width 300ms var(--ez),
|
||||
height 300ms var(--ez),transform 300ms var(--ez),opacity 300ms var(--ez)}
|
||||
.lbx-pic.zoomed{cursor:grab}.lbx-pic.grabbing{cursor:grabbing}
|
||||
.lbx-pic.swap{opacity:0}
|
||||
|
||||
/* chrome 层 1:关闭/箭头/计数(+140ms 淡入) */
|
||||
.c1{opacity:0;transition:opacity 220ms var(--ez)}
|
||||
.lbx.on .c1{opacity:1;transition-delay:140ms}
|
||||
.lbx-btn{position:fixed;width:44px;height:44px;border-radius:10px;cursor:pointer;
|
||||
border:1px solid rgba(255,255,255,.22);background:rgba(20,24,30,.55);color:#e6e9ee;
|
||||
backdrop-filter:blur(4px);box-shadow:0 2px 10px rgba(0,0,0,.35);display:grid;place-items:center}
|
||||
.lbx-btn:hover{background:rgba(255,255,255,.12)}
|
||||
.lbx-close{top:16px;right:16px}
|
||||
.lbx-prev{left:14px;top:50%;translate:0 -50%}
|
||||
.lbx-next{right:14px;top:50%;translate:0 -50%}
|
||||
.lbx-count{position:fixed;top:26px;left:20px;font-family:var(--mono);font-size:12.5px;color:#98a2b3}
|
||||
|
||||
/* chrome 层 2:caption / title+EXIF / 动作(+300ms 淡入) */
|
||||
.c2{opacity:0;transition:opacity 260ms var(--ez)}
|
||||
.lbx.on .c2{opacity:1;transition-delay:300ms}
|
||||
.lbx-meta{position:fixed;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;
|
||||
gap:3px;padding:14px 72px 20px;text-align:center;text-shadow:0 1px 4px rgba(0,0,0,.6)}
|
||||
.lbx-caption{margin:0;font-family:var(--sans);font-size:13.5px;line-height:1.6;color:#dfe6f3;max-width:640px}
|
||||
.lbx-row{display:flex;align-items:center;flex-wrap:wrap;justify-content:center;gap:14px;
|
||||
font-family:var(--mono);font-size:12.5px}
|
||||
.lbx-title{color:#fff;font-family:var(--sans);font-size:13px;font-weight:600}
|
||||
.lbx-exif{color:#98a2b3}
|
||||
.lbx-acts{display:inline-flex;gap:10px}
|
||||
.lbx-act{display:inline-flex;align-items:center;gap:5px;padding:6px 11px;border-radius:10px;
|
||||
border:1px solid rgba(255,255,255,.22);background:rgba(20,24,30,.55);color:#dfe6f3;
|
||||
font-family:var(--mono);font-size:12px;text-decoration:none;backdrop-filter:blur(4px)}
|
||||
.lbx-act:hover{background:rgba(40,48,60,.75);color:#fff}
|
||||
|
||||
/* 沉浸态:zoom>1 或按 i —— chrome 全隐,舞台纯净 */
|
||||
.lbx.clean .c1,.lbx.clean .c2,.lbx.noui .c1,.lbx.noui .c2{opacity:0;transition-delay:0ms}
|
||||
|
||||
/* 首开提示 + 缩放指示 */
|
||||
.lbx-hint{position:fixed;left:50%;bottom:128px;translate:-50% 0;padding:7px 13px;border-radius:10px;
|
||||
border:1px solid rgba(255,255,255,.22);background:rgba(20,24,30,.55);backdrop-filter:blur(4px);
|
||||
font-family:var(--mono);font-size:12px;color:#dfe6f3;opacity:0;transition:opacity 400ms var(--ez);
|
||||
pointer-events:none}
|
||||
.lbx-hint.show{opacity:1}
|
||||
.lbx-zind{position:fixed;right:20px;bottom:128px;font-family:var(--mono);font-size:12px;color:#98a2b3;
|
||||
opacity:0;transition:opacity 300ms var(--ez);pointer-events:none}
|
||||
.lbx-zind.show{opacity:1}
|
||||
|
||||
@media (max-width:560px){
|
||||
.lbx-prev{left:6px}.lbx-next{right:6px}
|
||||
.lbx-meta{padding:12px 16px 16px}
|
||||
.lbx-hint{bottom:150px}
|
||||
}
|
||||
@media (prefers-reduced-motion:reduce){
|
||||
.lbx-bg,.c1,.c2,.lbx-pic,.lbx-win,.lbx-hint,.lbx-zind{transition:none!important}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>图库 · 灯箱 v2.1 提案预览</h1>
|
||||
<p class="sub">预览稿 —— 交互为提案演示(非线上行为)· 图片为 content/photos 实拍,EXIF 为示例数据</p>
|
||||
<div class="note">
|
||||
<strong>这是给浏览器直接打开的交互演示</strong>,对应《09-灯箱美化探索》提案 #1/#2/#3/#6/#7:
|
||||
<ul>
|
||||
<li><strong>原位起飞开合</strong>:点击缩略图,照片从原位放大入舞台;关闭回落原位(<kbd>Esc</kbd> / 背景点击 / 关闭钮)</li>
|
||||
<li><strong>chrome 双层淡入</strong>:开箱后层 1(箭头/计数)先入、层 2(caption/EXIF)随入;<kbd>i</kbd> 隐藏全部;放大后自动隐层</li>
|
||||
<li><strong>连拍导航</strong>:<kbd>←</kbd> <kbd>→</kbd> / 边缘箭头 / 触屏快滑;计数 n / N</li>
|
||||
<li><strong>缩放</strong>:滚轮锚点缩放 1–4x、<kbd>双击</kbd> 1x↔2.5x、放大后拖拽、双指缩放;右下角缩放指示</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="stage"><ul class="wall" id="wall"></ul></div>
|
||||
</div>
|
||||
|
||||
<!-- 灯箱:fixed 覆盖层(生产等价搬回原生 <dialog>,focus-trap 由 dialog 免费提供) -->
|
||||
<div class="lbx" id="lbx" hidden>
|
||||
<div class="lbx-bg"></div>
|
||||
<div class="lbx-win" id="win"><img class="lbx-pic" id="pic" alt=""></div>
|
||||
<button class="lbx-btn lbx-close c1" id="bClose" aria-label="关闭(Esc)">
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="m5 5 14 14M19 5 5 19"/></svg></button>
|
||||
<button class="lbx-btn lbx-prev c1" id="bPrev" aria-label="上一张(←)">
|
||||
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m14.5 5.5-6.5 6.5 6.5 6.5"/></svg></button>
|
||||
<button class="lbx-btn lbx-next c1" id="bNext" aria-label="下一张(→)">
|
||||
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9.5 5.5 6.5 6.5-6.5 6.5"/></svg></button>
|
||||
<div class="lbx-count c1" id="count">1 / 5</div>
|
||||
<div class="lbx-meta c2">
|
||||
<p class="lbx-caption" id="cap"></p>
|
||||
<div class="lbx-row"><strong class="lbx-title" id="ttl"></strong><span class="lbx-exif" id="exif"></span>
|
||||
<span class="lbx-acts"><a class="lbx-act" id="aOrig" href="#" target="_blank" rel="noopener">原图 ↗</a><a class="lbx-act" id="aDl" href="#" download>下载 ↓</a></span></div>
|
||||
</div>
|
||||
<div class="lbx-hint" id="hint">滚轮缩放 · 双击放大 · Esc 关闭 · i 隐藏界面</div>
|
||||
<div class="lbx-zind" id="zind">100%</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ===== 数据:仓内实拍(含 2.36:1 长横幅与 2:3 竖图两种 letterbox 形态) ===== */
|
||||
const PHOTOS=[
|
||||
{src:'../content/photos/1766156983093.jpg',w:2400,h:1600,sp:13,title:'防波堤暮色',
|
||||
cap:'落日把海与城一齐烫成金箔,海鸟的剪影恰好掠过日轮',exif:'X-T5 · XF35mmF1.4 · f/5.6 · 1/500s · ISO 200'},
|
||||
{src:'../content/photos/mmexport1771816382115.jpg',w:2400,h:1018,sp:6,title:'长滩低云',
|
||||
cap:'涨潮前的长滩,云层压得很低',exif:'X100VI · f/8 · 1/1000s · ISO 160'},
|
||||
{src:'../content/photos/1766156998344.jpg',w:1600,h:2400,sp:20,title:'旗与塔',
|
||||
cap:'旗在风里翻卷,塔身亮得像一支随时点火的火箭',exif:'GR III · f/2.8 · 1/250s · ISO 400'},
|
||||
{src:'../content/photos/1766157004116.jpg',w:2400,h:1536,sp:9,title:'暮色撤离',
|
||||
cap:'灯柱次第亮起,暮色像退潮一样撤离',exif:'X-T5 · XF56mmF1.2 · f/1.4 · 1/120s · ISO 800'},
|
||||
{src:'../content/photos/mmexport1776564092433.jpg',w:2400,h:1600,sp:13,title:'夜航',
|
||||
cap:'货轮的灯在雾里连成一条迟疑的线',exif:'X-T5 · XF70-300mm · f/4 · 1/60s · ISO 1250'}];
|
||||
const N=PHOTOS.length;
|
||||
|
||||
const $=id=>document.getElementById(id);
|
||||
const lbx=$('lbx'),win=$('win'),pic=$('pic'),wall=$('wall');
|
||||
const reduced=matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
let idx=0,open_=false,hintShown=false;
|
||||
let zoom={s:1,tx:0,ty:0};
|
||||
|
||||
/* ===== 影像墙 ===== */
|
||||
PHOTOS.forEach((p,i)=>{
|
||||
const li=document.createElement('li');
|
||||
li.className='shot'; li.style.setProperty('--sp',p.sp);
|
||||
const b=document.createElement('button'); b.type='button'; b.setAttribute('aria-label',p.title);
|
||||
b.dataset.i=i;
|
||||
const im=document.createElement('img'); im.src=p.src; im.alt=p.title; im.loading=i<3?'eager':'lazy';
|
||||
b.appendChild(im); li.appendChild(b); wall.appendChild(li);
|
||||
b.addEventListener('click',()=>openLbx(i));
|
||||
});
|
||||
const thumbImg=i=>wall.querySelector(`button[data-i="${i}"] img`);
|
||||
|
||||
/* ===== 几何:舞台 fit 矩形 / cover-contain 双盒(PhotoSwipe data-cropped 路线) ===== */
|
||||
function stageBox(){const W=innerWidth,H=innerHeight;
|
||||
const side=W>760?56:12,top=64,bot=innerWidth>560?124:150;
|
||||
return{x:side,y:top,w:W-side*2,h:H-top-bot};}
|
||||
function fitRect(p,box){const r=p.w/p.h,br=box.w/box.h;let w,h;
|
||||
if(r>br){w=box.w;h=w/r;}else{h=box.h;w=h*r;}
|
||||
return{x:box.x+(box.w-w)/2,y:box.y+(box.h-h)/2,w,h};}
|
||||
function coverIn(p,winR){const r=p.w/p.h,wr=winR.w/winR.h;let w,h;
|
||||
if(wr>r){h=winR.h;w=h*r;}else{w=winR.w;h=w/r;}
|
||||
return{x:(winR.w-w)/2,y:(winR.h-h)/2,w,h};} /* 相对 win 的坐标 */
|
||||
const setBox=(el,g)=>{el.style.left=g.x+'px';el.style.top=g.y+'px';
|
||||
el.style.width=g.w+'px';el.style.height=g.h+'px';};
|
||||
|
||||
/* ===== 开箱:从缩略图原位起飞 ===== */
|
||||
function fillMeta(p){$('cap').textContent=p.cap;$('ttl').textContent=p.title;
|
||||
$('exif').textContent=p.exif;$('count').textContent=`${idx+1} / ${N}`;
|
||||
$('aOrig').href=p.src;$('aDl').href=p.src;$('aDl').setAttribute('download',p.title+'.jpg');}
|
||||
function openLbx(i){
|
||||
idx=i;const p=PHOTOS[idx];pic.src=p.src;pic.alt=p.title;fillMeta(p);
|
||||
const t=thumbImg(i).getBoundingClientRect();
|
||||
const tr=fitRect(p,stageBox());
|
||||
zoom={s:1,tx:0,ty:0};pic.style.transform='none';pic.classList.remove('zoomed','grabbing','swap');
|
||||
lbx.hidden=false;open_=true;document.body.classList.add('locked');
|
||||
lbx.classList.add('landing');
|
||||
setBox(win,t);setBox(pic,coverIn(p,t)); /* 起点:与缩略图逐像素同框(cover 裁切) */
|
||||
if(reduced){lbx.classList.add('on');lbx.classList.remove('landing');setBox(win,tr);setBox(pic,{x:0,y:0,w:tr.w,h:tr.h});}
|
||||
else{
|
||||
void win.offsetWidth; /* 强制 reflow,起点先落定 */
|
||||
lbx.classList.add('on');
|
||||
setBox(win,tr);setBox(pic,{x:0,y:0,w:tr.w,h:tr.h}); /* 终点:舞台 contain */
|
||||
setTimeout(()=>lbx.classList.remove('landing'),330);
|
||||
}
|
||||
if(!hintShown){hintShown=true;const h=$('hint');h.classList.add('show');
|
||||
setTimeout(()=>h.classList.remove('show'),2600);}
|
||||
}
|
||||
|
||||
/* ===== 关闭:回落到缩略图原位 ===== */
|
||||
function closeLbx(){
|
||||
if(!open_)return;
|
||||
const p=PHOTOS[idx],t=thumbImg(idx)?.getBoundingClientRect();
|
||||
instantResetZoom();
|
||||
if(reduced||!t){lbx.hidden=true;lbx.classList.remove('on','clean','noui');open_=false;
|
||||
document.body.classList.remove('locked');return;}
|
||||
lbx.classList.add('landing');
|
||||
setBox(win,t);setBox(pic,coverIn(p,t));
|
||||
lbx.classList.remove('on','clean','noui');
|
||||
setTimeout(()=>{lbx.hidden=true;lbx.classList.remove('landing');open_=false;
|
||||
document.body.classList.remove('locked');},320);
|
||||
}
|
||||
|
||||
/* ===== 换片:旧图淡出、新图自侧向 10% 淡入(spacing 节奏) ===== */
|
||||
function show(i,dir){
|
||||
idx=((i%N)+N)%N;const p=PHOTOS[idx];instantResetZoom();fillMeta(p);
|
||||
pic.src=p.src;pic.alt=p.title;
|
||||
const tr=fitRect(p,stageBox());setBox(win,tr);setBox(pic,{x:0,y:0,w:tr.w,h:tr.h});
|
||||
if(!reduced){pic.classList.add('swap');
|
||||
pic.style.transform=`translateX(${dir*-10}%)`;
|
||||
void pic.offsetWidth;pic.classList.remove('swap');
|
||||
pic.style.transform='none';}
|
||||
}
|
||||
|
||||
/* ===== 缩放:滚轮锚点 / 双击 / 双指 / 拖拽(复用线上 v2 数学) ===== */
|
||||
function clampPan(){const mx=Math.max(0,pic.clientWidth*(zoom.s-1)/2),
|
||||
my=Math.max(0,pic.clientHeight*(zoom.s-1)/2);
|
||||
zoom.tx=Math.min(mx,Math.max(-mx,zoom.tx));zoom.ty=Math.min(my,Math.max(-my,zoom.ty));}
|
||||
function applyZoom(){clampPan();
|
||||
pic.style.transform=zoom.s===1?'none':`translate(${zoom.tx}px,${zoom.ty}px) scale(${zoom.s})`;
|
||||
pic.classList.toggle('zoomed',zoom.s!==1);
|
||||
lbx.classList.toggle('clean',zoom.s!==1);}
|
||||
function instantResetZoom(){const keep=pic.style.transition;pic.style.transition='none';
|
||||
zoom={s:1,tx:0,ty:0};pic.style.transform='none';pic.classList.remove('zoomed');
|
||||
lbx.classList.remove('clean');void pic.offsetWidth;pic.style.transition=keep||'';}
|
||||
function zoomAt(next,cx,cy){const r=pic.getBoundingClientRect();
|
||||
const ax=cx-r.left-r.width/2-zoom.tx,ay=cy-r.top-r.height/2-zoom.ty;
|
||||
if(next<=1){zoom={s:1,tx:0,ty:0};applyZoom();zindShow();return;}const k=next/zoom.s;zoom.tx-=ax*(k-1);zoom.ty-=ay*(k-1);zoom.s=next;applyZoom();zindShow();}
|
||||
let zt;function zindShow(){const z=$('zind');z.textContent=Math.round(zoom.s*100)+'%';
|
||||
z.classList.add('show');clearTimeout(zt);zt=setTimeout(()=>z.classList.remove('show'),900);}
|
||||
lbx.addEventListener('wheel',e=>{if(!open_)return;e.preventDefault();
|
||||
const f=Math.exp(-e.deltaY*0.0015);
|
||||
const next=Math.min(4,Math.max(1,zoom.s*f));
|
||||
if(next!==zoom.s)zoomAt(next,e.clientX,e.clientY);},{passive:false});
|
||||
win.addEventListener('dblclick',e=>{if(zoom.s===1)zoomAt(2.5,e.clientX,e.clientY);
|
||||
else instantResetZoom();}); /* 挂 win:放大态 setPointerCapture 会把 click 派发给 win,pic 收不到 */
|
||||
|
||||
/* 拖拽平移(放大态)+ 双指缩放:pointer 事件统一 */
|
||||
const pts=new Map();let drag=null,pinch=null;
|
||||
win.addEventListener('pointerdown',e=>{
|
||||
pts.set(e.pointerId,{x:e.clientX,y:e.clientY});
|
||||
if(pts.size===2){const a=[...pts.values()];
|
||||
pinch={d0:Math.hypot(a[0].x-a[1].x,a[0].y-a[1].y),s0:zoom.s,
|
||||
mx:(a[0].x+a[1].x)/2,my:(a[0].y+a[1].y)/2};drag=null;return;}
|
||||
if(zoom.s>1){win.setPointerCapture(e.pointerId);
|
||||
drag={x:e.clientX,y:e.clientY,tx:zoom.tx,ty:zoom.ty};pic.classList.add('grabbing');}
|
||||
});
|
||||
win.addEventListener('pointermove',e=>{
|
||||
if(pts.has(e.pointerId))pts.set(e.pointerId,{x:e.clientX,y:e.clientY});
|
||||
if(pinch&&pts.size===2){const a=[...pts.values()];
|
||||
const d=Math.hypot(a[0].x-a[1].x,a[0].y-a[1].y);
|
||||
const next=Math.min(4,Math.max(1,pinch.s0*d/pinch.d0));
|
||||
if(next!==zoom.s)zoomAt(next,pinch.mx,pinch.my);return;}
|
||||
if(drag){zoom.tx=drag.tx+e.clientX-drag.x;zoom.ty=drag.ty+e.clientY-drag.y;applyZoom();}
|
||||
});
|
||||
const endPt=e=>{pts.delete(e.pointerId);if(pts.size<2)pinch=null;
|
||||
if(drag){drag=null;pic.classList.remove('grabbing');}};
|
||||
win.addEventListener('pointerup',endPt);win.addEventListener('pointercancel',endPt);
|
||||
|
||||
/* 触屏快滑翻页(zoom=1 时) */
|
||||
let x0=null;lbx.addEventListener('touchstart',e=>{x0=e.touches[0].clientX;},{passive:true});
|
||||
lbx.addEventListener('touchend',e=>{if(x0===null||zoom.s!==1){x0=null;return;}
|
||||
const dx=e.changedTouches[0].clientX-x0;
|
||||
if(Math.abs(dx)>40)show(idx+(dx<0?1:-1),dx<0?1:-1);x0=null;},{passive:true});
|
||||
|
||||
/* ===== 键盘 / 背景点击 / chrome 召唤 ===== */
|
||||
document.addEventListener('keydown',e=>{
|
||||
if(!open_)return;
|
||||
if(e.key==='Escape')closeLbx();
|
||||
else if(e.key==='ArrowLeft')show(idx-1,-1);
|
||||
else if(e.key==='ArrowRight')show(idx+1,1);
|
||||
else if(e.key==='i'||e.key==='I')lbx.classList.toggle('noui');
|
||||
});
|
||||
lbx.addEventListener('click',e=>{
|
||||
if(e.target.closest('.lbx-win')||e.target.closest('.lbx-btn')||
|
||||
e.target.closest('.lbx-meta')||e.target.closest('a'))return;
|
||||
closeLbx(); /* 背景点击关闭(bgClickAction:'close' 同款) */
|
||||
});
|
||||
$('bClose').addEventListener('click',closeLbx);
|
||||
$('bPrev').addEventListener('click',()=>show(idx-1,-1));
|
||||
$('bNext').addEventListener('click',()=>show(idx+1,1));
|
||||
|
||||
/* 开箱中窗口变化:舞台即时重排(不做动画,避免与缩放打架) */
|
||||
addEventListener('resize',()=>{if(!open_||lbx.classList.contains('landing'))return;
|
||||
const p=PHOTOS[idx];const tr=fitRect(p,stageBox());
|
||||
setBox(win,tr);setBox(pic,{x:0,y:0,w:tr.w,h:tr.h});});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,125 @@
|
||||
# 博客体验研究与优化方案调研(六站)
|
||||
|
||||
> 任务产出。数据来源:2026-10-26 直连抓取六站 HTML(文本提取后人工分析)。抓取失败的站已如实标注。
|
||||
> 站点快照存于 /tmp/uxstudy/*.html(临时,会话级)。目标站背景:Astro SSG 双语、灰阶极客风、命令面板/键盘弦/终端彩蛋/热力图/PWA/Artalk、自建 gitea+Sveltia 照片管线。
|
||||
|
||||
## 一、六站逐站笔记
|
||||
|
||||
### 1. qh1009.net(清凉药)✅ 已抓取(66KB,WordPress + WiThemes)
|
||||
- **首页 IA**:纯内容流——俳句/诗全文直接铺在首页(非标题列表),编号制(#219…#2008),每条极短 + 标签。首页即作品。
|
||||
- **排版**:文学向极简,正文居中窄栏,无目录/无进度条/无评论组件可见(0 评论数展示)。
|
||||
- **特色交互**:几乎零交互,靠"编号+日期+分类词(俳/诗/译/博物)"的仪式感取胜。
|
||||
- **导航/移动端**:顶部 Toggle Menu,Home/关于/作品/杂记/博物/俳/诗/译/Podcast,移动折叠。
|
||||
- **内容组织**:标签云非常细(50+ 自然意象词),年归档分页。
|
||||
- **管理**:WordPress(页脚 "Designed by WiThemes"),推断经典 WP 后台。
|
||||
- **可借鉴**:瞬间(moments)可学"编号制"给每条瞬间一个序号,制造连续感;标签的"意象化命名"比功能化命名更有味道。
|
||||
|
||||
### 2. nbfox.com(麦田艺术)✅ 已抓取(187KB,WordPress 画廊站)
|
||||
- **首页 IA**:题材/流派双维下拉菜单(风景画/肖像画… × 印象派/巴洛克…),首屏专题卡片(手机壁纸/梵高向日葵/浮岳三十六景)+ 大图瀑布。
|
||||
- **排版**:图为主体,每图带结构化元数据卡:创作者/年代/图片原始尺寸(像素)/风格——元数据即说明文字。
|
||||
- **特色**:①双维分类导航(题材×流派)②图片标注精确像素尺寸,管理下载预期 ③"超高清/热门收藏/精选"策展位。
|
||||
- **导航/移动端**:粘性双搜索框 + 汉堡菜单,功能/会员/收藏夹分离。
|
||||
- **内容组织**:博物馆聚合、公域版权资源库、网盘分发——内容型产品打法。
|
||||
- **管理**:WordPress + WooCommerce(会员/网盘),重后台。
|
||||
- **可借鉴**:**图库(gallery)加"双维筛选"**(地点×器材 或 主题×年份);每张照片展示 EXIF 元数据卡(机型/焦段/光圈/地点)——我们已有 exifr 管线,只差展示层。
|
||||
|
||||
### 3. yongyuan.name ✅ 已抓取(56KB,静态技术博客)
|
||||
- **首页 IA**:一句个人格言 + "All Posts" 按年份倒序全量清单(2015→2024,一页全列),每条:日期/标题/分类。
|
||||
- **排版**:无摘要、无图,纯列表——十年技术文一目了然,靠"全量可见"建立信任。
|
||||
- **特色交互**:无。反例价值:极简到牺牲发现性(无搜索/无热门/无相关文)。
|
||||
- **内容组织**:分类词前置在每行(CV/推荐系统/C++/ANN),每年末一篇"年终总结"锚点文。
|
||||
- **可借鉴**:**归档页全量清单模式**(我们有 archives/,确认是否一页可览十年);"年终总结"惯例值得立为个人传统。
|
||||
|
||||
### 4. ruanyifeng.com/blog ✅ 已抓取(首页 20KB + 周刊分类页 176KB,自建 MT 系统)
|
||||
- **首页 IA**:最新一篇全文摘要 + "继续阅读全文 »" + 右栏:最新文章 5 条 / 最新留言 / 关于 / 邮件订阅 / 授权声明 / 社交账号——**首页即门户**,信息密度极高但层级清晰。
|
||||
- **文章列表**:周刊独立分类页,416 篇按年分组倒序,纯标题+日期列表。首页摘要带"分类: 周刊"标签 + 留言数(30)——**留言数前置是社交证明**。
|
||||
- **内容组织(标杆点)**:①周刊作为**固定节奏产品**(周五发布,416 期,编号化)②完整档案页(分类/按月/2004 至今)③邮件订阅+RSS 双通道 ④留言 79533 条展示在侧栏(社区感)⑤版权/授权声明独立成块。
|
||||
- **排版详情**:正文窄栏衬线、目录在长教程文中手写小节链接、代码块朴素无高亮(极客反潮流)。
|
||||
- **可借鉴**:①**固定栏目化**——我们的"瞬间"可升格为编号制轻周刊(每周 3-5 条瞬间聚合 + 一句话评);②侧栏"最新留言"(Artalk API 可拉);③留言数展示在列表页。
|
||||
|
||||
### 5. antfu.me ✅ 已抓取(首页/列表页/文章页,Astro + UnoCSS,同栈)
|
||||
- **首页 IA**:一段自我介绍正文 + "Recent work" 项目动态(直接拉 GitHub 数据)+ 一图流头部。无文章列表——博客藏在 /posts。
|
||||
- **文章列表**:按年份分组,每条:日期 · 阅读时长("Sep 16 · 15min")· 语言徽标(中文/日本語)——**双语不是双站,是同一列表内并列两行互链**("Hello Tokyo!" / 「おはよう、東京!」相邻)。
|
||||
- **排版/交互(值得抄)**:①`slide-enter-content` 入场动画(我们已有同类)②magic-link:GitHub 链接自动渲染成卡片、图片自动预加载(grep 实锤 markdown-magic-link 类)③单篇文章页有 "Demo of …" 双语切换按钮 + **Plaintext 纯文本模式**(去样式阅读)④暗色以 class 切换,全站色调极克制(text-gray-700/200)。
|
||||
- **导航**:极简 5 项(Blog/Projects/Talks/Sponsors),移动端同构。
|
||||
- **管理**:纯 git 仓 + 自己写的构建,无 CMS——证明"无后台"路线在顶级生产者处成立。
|
||||
- **可借鉴**:①**双语互链并列**(比我们 /en/ 双轨更轻,可作文章页"读中文版"强化);②**阅读时长估算**列表页展示;③**纯文本/阅读模式**(灰阶站天然适配,一行 toggle + CSS);④首页 "Recent work" 拉 gitea commit 流(我们已有"最近心跳",可扩展为开源项目动态)。
|
||||
|
||||
### 6. innei.in ✅ 已抓取(141KB,Mix Space + Shiro 主题,React SSR)——中文现代个人站代表
|
||||
- **首页 IA**:问候 + 一言(随机引言)+ 统计条(X 篇 · X 万字 · X 天)+ "近期笔墨"5 条(类型徽标:笔记/文章/技术)+ 页脚多宫格(关于/照片廊/联系/订阅/版式开关)。
|
||||
- **特色(值得抄)**:①**站内搜索/主题/背景音乐/背景效果/余白开关全在设置面板**("版式"区:浅色·随系统·深色 / 背景音乐 关·开 / 背景效果 关·开)——把实验性开关收纳成一个面板;②多内容类型(文稿/手记/时光/思考)+ 订阅通信(Newsletter 入口);③留印记(留言板)、朋友们(友链)、一言。
|
||||
- **导航**:顶部 4 项 + "更多",移动端宫格化页脚导航。
|
||||
- **管理**:Mix Space 自带 admin 后台(草稿/评论/监控),有 API——对比样本。
|
||||
- **可借鉴**:①**设置面板收纳实验开关**(我们的终端彩蛋/辉光/动效可聚成一个"偏好"面板并 localStorage 持久化——比散落各处更克制);②Newsletter 订阅入口;③友链页。
|
||||
|
||||
### 抓取情况汇总
|
||||
六站全部抓取成功(qh1009/nbfox/yongyuan/ruanyifeng/antfu/innei),无备选站需补。注:本机代理(7897) TLS 握手故障,全部改直连抓取;innei.in 首屏计数显示 0(数据懒加载),已从 DOM 结构推断功能。
|
||||
|
||||
## 二、共性模式清单(优质站普遍做、我们没做/未确认)
|
||||
|
||||
| # | 是什么 | 价值 | 灰阶极客风适配建议 |
|
||||
|---|---|---|---|
|
||||
| 1 | **阅读时长估算**(antfu "15min") | 列表页决策成本↓ | PostList 每条加 `~8 min` 等宽字体角标,构建期按字数/分钟计算,零运行时 |
|
||||
| 2 | **双语条目互链并列**(antfu 同列表 zh/en 相邻互指) | 双语发现性↑,用户不会漏另一语版 | 文章页标题下加一行 `→ EN` 静态链接即可(已有 /en/ 路由,缺的是入口可见性) |
|
||||
| 3 | **纯文本/阅读模式**(antfu Plaintext) | 极客好评、灰阶天然契合 | 单条 CSS class 剥离装饰(辉光/动效/侧栏),命令面板加 `reader` 命令 + 快捷键 |
|
||||
| 4 | **留言数/评论数前置到列表**(阮一峰"留言(30)") | 社交证明,激活 Artalk 存量数据 | Artalk API 构建期拉取各页评论数,写进 frontmatter 化的 JSON,列表页静态渲染 |
|
||||
| 5 | **侧栏/首页"最新留言"**(阮一峰) | 站点"活着"的信号 | 首页"最近心跳"旁并列"最新评论"一条流(构建期快照,接受 24h 陈旧) |
|
||||
| 6 | **固定节奏栏目化**(阮一峰周刊 416 期编号) | 读者形成期待,更新压力可控 | "瞬间"页顶加"本周瞬间 #N"聚合块,编号来自构建期统计 |
|
||||
| 7 | **图片元数据卡**(nbfox 尺寸/创作者/年代) | 摄影轨的专业感 | gallery 详情展示 exifr 已提取的 EXIF(机型/焦段/光圈/ISO/时间/地点)等宽字体表 |
|
||||
| 8 | **归档全量清单**(yongyuan 十年一页) | 纵深感与可扫读 | 确认 archives/ 是全量单页;不够则按年分组渲染 |
|
||||
| 9 | **偏好/实验开关集中面板**(innei 版式区) | 克制与自由兼得,避免装饰散弹 | 命令面板加 `settings` 命令:辉光/动效/热力图/背景音乐各自 toggle,localStorage 持久化 |
|
||||
| 10 | **Newsletter 订阅入口**(innei/antfu sponsor+订阅、阮邮件订阅) | 回访通道不被算法绑架 | P2→P1 观望:无邮件服务前先放 RSS 说明页强化;接入自托管 listmonk 是远期项 |
|
||||
| 11 | **友链页**(innei 朋友们) | 个人站社区礼节,互推 | /about 或独立 /links,静态 YAML 驱动 |
|
||||
| 12 | **首页项目动态**(antfu Recent work 拉 GitHub) | 开源轨的活性证明 | "最近心跳"已含 commit 脉冲,可加"开源动态"一列(gitea API,构建期) |
|
||||
|
||||
## 三、差异化机会(我们独有,保持强化)
|
||||
|
||||
1. **命令面板 Ctrl+K + vim 键位弦(g g / g s 双击 chord)**——六站无一做到;持续补齐 chord E2E 矩阵即可,是护城河。
|
||||
2. **终端彩蛋页 + 页脚 commit hash**——antfu 级极客也只做了 slide 动画;我们的终端隐喻是身份标识,新功能优先以"命令"形态暴露(如 reader、settings 走命令面板而非按钮)。
|
||||
3. **构建期零 JS 摄影 masonry(exifr+sharp)**——innei 照片廊是 React 重运行时;我们的构建期管线是自托管价值观的最佳注脚,配上 EXIF 元数据卡(共性#7)即成完整叙事。
|
||||
4. **构建期 LLM 预翻译 + .en.md 入仓 + AI 标注**——antfu 双语靠手写;我们的管线差异化在"过程可审计"(入仓),强化 AI 译徽标的诚实性。
|
||||
5. **PWA 离线全量 + 安装 CTA 三态**——六站均无 PWA;已有,勿再投入,保持零回归即可。
|
||||
6. **灰阶克制本身**——阮一峰证明了"朴素不等于廉价";我们与 antfu 的 gray-700/200 同谱系,方向正确。
|
||||
|
||||
## 四、管理后台便利性专题
|
||||
|
||||
### Sveltia vs 主流后台(WordPress/Typecho/Halo)差距面
|
||||
| 能力 | WP/Halo/Typecho | Sveltia(git 仓) | 补齐方案 |
|
||||
|---|---|---|---|
|
||||
| 草稿预览 | 即时预览 | 编辑侧预览,但布局/组件 ≠ 成品 | **/draft/ 构建路由**:draft frontmatter 的文章构建进 `/draft/<slug>/`,nginx 层加 BasicAuth 或长随机路径;发布=移除 draft 标志 |
|
||||
| 媒体库 | 中央媒体库、检索、批量 | 文件夹浏览、上传即入 git | 我们照片走 gitea web 上传已等价;补 **构建期图片 lint**(exifr 校验尺寸/格式/命名规范,违规即构建警告清单页) |
|
||||
| 定时发布 | 原生 | 无 | **构建期 publishDate 判定**:CI 触发频率内生效(webhook+定时 cron 双触发 gitea actions / receiver),文章带未来日期默认不出现在正式列表 |
|
||||
| 批量操作 | 后台多选 | git 原生(批量改文件+一次 commit) | 无需补;可在 scripts/ 加批量 frontmatter 工具(改日期/标签) |
|
||||
| 版本历史 | 修订版 | git 全量历史 | 已胜出;页脚 commit hash 已把它可视化了 |
|
||||
|
||||
### "自托管+git 仓"架构补齐清单(按性价比排序)
|
||||
1. `/draft/` 预览路由(上面方案)——写作体验最大缺口。
|
||||
2. 构建期内容 lint:断链检查、图片存在性、frontmatter schema(typebox 已在栈内)、双语文件配对校验(zh 无 en 时警告)。
|
||||
3. 构建期质量报告页(仅 draft 可见):字数/阅读时长/图片体积/未翻译词条清单,写作完成度仪表。
|
||||
4. Sveltia config.yml 优化:为 moments/gallery 集合配 i18n 双语字段映射、媒体文件夹预排序(按日期倒序,避免长列表翻找)。
|
||||
5. 发布检查单自动化:构建尾跑 LH 冒烟(gz-server.py 已在仓)+ 死链扫描,fail 即 webhook 通知 gitea。
|
||||
|
||||
## 五、分级建议表
|
||||
|
||||
| 级 | 项 | 理由 |
|
||||
|---|---|---|
|
||||
| **P0** | 阅读时长角标(#1) | 一次构建期计算,全站收益 |
|
||||
| **P0** | 文章页"→ EN/中文版"互链(#2) | 双语投入的回报被入口缺失吃掉 |
|
||||
| **P0** | /draft/ 预览路由(四-1) | 写作流最大缺口,git 架构易实现 |
|
||||
| **P0** | 构建期内容 lint(四-2) | 防线前移,typebox 已在栈内 |
|
||||
| **P1** | 命令面板 reader 纯文本模式(#3) | 极客好评且实现轻,走命令面板零 UI 负担 |
|
||||
| **P1** | 偏好面板收纳实验开关(#9) | 辉光/动效已有但散落,收纳更克制 |
|
||||
| **P1** | gallery EXIF 元数据卡(#7) | exifr 数据已在手,纯展示层 |
|
||||
| **P1** | 列表评论数 + 首页最新评论流(#4/#5) | 激活 Artalk 存量;接受构建期快照陈旧 |
|
||||
| **P1** | 瞬间编号化"本周瞬间 #N"(#6) | 低成本仪式感 |
|
||||
| **P1** | 归档全量清单复核(#8) | 只需确认,可能已达标 |
|
||||
| **P1** | 友链页(#11) | 静态 YAML,一晚工作量 |
|
||||
| **P2** | Newsletter/邮件订阅(#10) | 需自托管邮件栈(listmonk+发信域名),维护成本先于价值;先强化 RSS |
|
||||
| **P2** | 背景音乐/一言/背景特效(innei 同款) | 与灰阶克制冲突:多感官装饰稀释"终端"隐喻的一致性 |
|
||||
| **P2** | 首页大图专题卡、瀑布流重化(nbfox 同款视觉) | nbfox 是内容电商型画廊;我们零 JS masonry 已是更优解,加重视觉与"灰阶骨架×分轨正文"定稿相悖 |
|
||||
| **P2** | 阅读进度条/浮动 TOC 大改 | 六站中仅少数有 TOC;我们长文少,现有目录足够,勿为趋势加常驻 UI |
|
||||
|
||||
## 风险与备注
|
||||
- antfu.me 文章页部分组件(Plaintext 按钮)为客户端渲染,快照中由类名/结构推断,实现前建议人工核对 antfu 仓(antfu.me 开源)。
|
||||
- innei.in 首屏计数 0 为懒加载,统计功能存在但数值未取到。
|
||||
- 评论数/最新评论若走 Artalk 需注意 ESA 缓存(此前已有 /artalk/api 绕缓存议题),构建期拉取可绕开运行时问题。
|
||||
@@ -0,0 +1,146 @@
|
||||
# 博客优化方案 v2(深度版)
|
||||
|
||||
> 最终落位:`docs/research/07-博客优化方案v2.md`(本文件为 scout 产出原稿,内容即完整方案)。
|
||||
> 数据来源:2026-10-26 12:05-12:30 生产实测(curl 直抓 wait4x.online + 本机 nginx/Artalk 容器 + 仓库源码)与境外站直连抓取(本机 clash 7897 节点故障,`--noproxy '*'` 直连成功,复现 v1 教训)。抓取失败项已如实标注。
|
||||
> 前置:v1 六站研究见 docs/research/06;本项目历史调研 01-05。
|
||||
|
||||
## 1. 执行摘要(推荐立即做的 8 件事)
|
||||
|
||||
1. **解除全站 noindex 封印**(0.5d):nginx 共享 snippet 给博客 vhost 注入 `X-Robots-Tag: noindex`,全站被标记不可索引——**这是生产 SEO 60-63 的第一主因,所有内容工作在这之前等于闭门写**。
|
||||
2. **补 robots.txt + ESA 全站 purge 一次**(0.5d):public/ 从未有 robots.txt(生产 404);noindex 响应头已被 ESA 边缘缓存 30 天,修完必须 purge。
|
||||
3. **修 localePath 双 en bug + 加自指 canonical**(0.5d):en 页 hreflang 自指 `/en/en/archives/…`(404);同页带/不带尾斜杠双 200。
|
||||
4. **构建期内容 lint**(1d):断链/图片存在性/frontmatter schema/双语配对,build.sh 守卫挂钩(防线前移,v1 P0 坚持项)。
|
||||
5. **/draft/ 预览路由**(1d):写作流最大缺口;长随机路径 + robots noindex + 不入 sitemap。
|
||||
6. **reader 纯文本模式**(0.5d):`html.reader` 单 class 方案,落点已全部排明(§4.4);命令面板 `reader` 命令 + `r` 键。
|
||||
7. **列表页评论数前置**(1d):Artalk API 已实测(`GET /api/v2/comments?…&site_name=wait4x.online` 返回 `count`),构建期拉取静态渲染,零运行时。
|
||||
8. **首页 title/description 语义化**(0.5d):现全站共用 `wait4x` + 一句通用 description,列表/文章页 title 已对,home 与 moments/gallery 等仍弱。
|
||||
|
||||
## 2. v1 P0/P1 批判性复审
|
||||
|
||||
| v1 项 | v1 级 | 复审结论 | 理由 |
|
||||
|---|---|---|---|
|
||||
| 阅读时长角标 | P0 | **降 P1**:文章页已有(`[...slug].astro:39` 汉字数/400 取整,`:60` 已渲染"阅读时长 N 分钟");v1 说的其实是列表页角标,页内已有 → 列表是锦上添花 |
|
||||
| →EN 互链 | P0 | **关闭**:Header `nav-lang` EN 链接已每页常驻(车道 G 出舱后任何宽度可见)。但复查揪出真 bug:en 页 hreflang 双 en(见 §4.1-3)——**从"加互链"转化为"修 localePath"** |
|
||||
| /draft/ 预览路由 | P0 | **坚持**:git 架构最大写作缺口。实现降级:长随机路径优先于 BasicAuth(nginx 现无 auth 依赖,别为 draft 引入) |
|
||||
| 构建期 lint | P0 | **坚持并升级**:SEO 三连漏检证明"只在本地测"会漏生产层故障——lint 应加"生产冒烟"(curl robots/sitemap/头)一档 |
|
||||
| reader 纯文本 | P1 | **坚持升 P0 批**(与 SEO 同批做):antfu 同款 + 灰阶天然适配,本轮落点清单已备好,半天可收 |
|
||||
| 偏好面板 | P1 | **降级收窄**:先只做 reader + 辉光/动效开关两个 toggle,防"设置中心"过度设计(innei 面板是 React 重站的路数) |
|
||||
| gallery EXIF 卡 | P1 | **坚持**:photos_ingest.py 构建期 exifr 数据已在手,纯展示层 |
|
||||
| 评论数前置+最新评论 | P1 | **坚持,风险清零**:v1 担心的"API 形状/ESA 缓存"本轮实测解决——构建期从 127.0.0.1:13030 内网直拉(构建机=Artalk 宿主),完全不碰 ESA |
|
||||
| 瞬间编号化 | P1 | **保留 P1 尾部**:仪式感成立(diygod `#年终总结` 6 篇、阮一峰 416 期都验证了节奏感),但数据量小(21 图+1 瞬间),收益后置 |
|
||||
| 归档全量复核 | P1 | **关闭(已达标)**:实测 /archives/ 全量单页,zh 6 条 + en 6 条(7 个 md 减 media-sample 的 hidden 标记),无分页,一页可扫 |
|
||||
| 友链页 | P1 | **保留**:diygod `/friends` 友邻页再次验证(中文个人站礼节),YAML 驱动一晚 |
|
||||
| Newsletter | P2 | **维持观望**:xeiaso 用 Patreon/signalboost 但那是有 400 文+社群的体量;我们 RSS 刚起步,先 RSS 说明页 |
|
||||
|
||||
**v1 最大盲区(自我批判)**:v1 调研全部基于"站点代码应该没问题"的假设,只测了视觉/交互,没测生产响应头与爬虫视角——本轮 SEO 60-63 的三个根因(noindex 头、robots 缺失、hreflang 404)全部存在于 v1 调研期间,一个都没被 v1 发现。教训:**体验研究必须包含"爬虫/机器人视角"的生产实测**,已补进 §4。
|
||||
|
||||
## 3. 新探索站增量发现(v2 补面,8 站)
|
||||
|
||||
> 本机 clash 7897 节点当日故障(google/github 经代理均 000),全部改 `--noproxy '*'` 直连成功——与 v1"代理 TLS 故障改直连"同款环境病,抓境外站先直连再代理。
|
||||
|
||||
1. **rauno.me**(9.7KB 瘦 HTML,实测)——交互工艺标杆,但真正的标杆在子页:`/craft` 是**按月编年的工艺案例库**(2020→2026,30+ 条:Toolbar Morph/X-Ray Interaction/⌘K Breadcrumbs…,每条三种出口 View Production/View Prototype/Read Essay)。首页即一句信条循环("Make it fast. Make it beautiful… Make it.")+ **Email 点击即复制**("Copied" 内联反馈)+ **自我版本化**(2023/2022 年度快照链接)。可借鉴:①把 terminal 页或独立页做成"工艺/极客案例编年体";②页脚 email 用复制微交互替代 mailto;③站内 About 写"信条列表"比自我介绍段更有身份感。
|
||||
2. **diygod.cc**(RSSHub 作者站,实测)——中文极客个人站代表:首页格言 + **分类计数前置**(创作集26/分享境18/研习坊16/日记本13——中文意象化命名,与 v1 qh1009 结论互证);/posts **90 篇前端关键词搜索**(零后端);**双语 /en 切换**;`#年终总结` 6 篇的节奏传统。**彩蛋级发现:页面内嵌一段给 AI 爬虫的 persona prompt**("你是一个擅于模仿真实人类写博客的 AI,名字叫 DIYgod…")——对 AI 爬虫的幽默防御/人格注入,我们 terminal 页可同款玩。RSS 姿态:作者即 RSSHub 之父,站内 RSS 极简入口 + 完整摘要输出(15/15 全文)。
|
||||
3. **brianlovin.com**——**抓取失败(如实标注)**:Vercel Security Checkpoint JS 挑战挡 curl(HTTP 429),本机 c4ai 无头浏览器因代理故障不可用。其"写作/项目/梯次"结构引用 v1 常识不采信,标注为待人工复核。
|
||||
4. **vincentwill.com**(实测,选它弃 sabrinacodes)——sabrinacodes 实抓后确认是 PHP 教师 portfolio(Resume/Education/Mini Games),与我们三轨内容零交集,弃;vincentwill 为柏林 web 开发者:**微产品墙一行一产品**(名+一句话+链接,[Sold] 标记),"latest projects" 精选 6 + "More Projects" 全量折叠——与开源项目墙"双源"同构,可借鉴其**精选/全量两档**而非平铺。
|
||||
5. **xeiaso.net**(实测)——400+ 文的终端极客活化石:blog 列表页顶部**RSS+JSONFeed 双提示语**("If you have a compatible reader…");**系列文仪式**("No way to prevent this" 同标题连发=美国枪击梗的技术圈复刻,系列性本身就是品牌);页脚**最新 commit hash 链回 GitHub**(与我们 T7 同款,互证方向);多身份自我定义(technical educator/vtuber/philosopher)。可借鉴:①系列文(CS 配置/书评都可系列化命名);②blog 顶部 RSS 友好提示语;③signalboost(转发求职/求助信息)是社群位远期选项。
|
||||
6. **shud.in**(自选①,理由:中文 design engineer、Vercel 核心贡献者的极简样本,与"灰阶骨架"同谱系)——实测四页 IA(About/Thoughts/Projects/Guestbook),首页一段自我叙述+社交链接,零装饰零动效。**Guestbook 留言板**=轻量社区位(Artalk 现成能力);"Thoughts"命名比"博客"更有随笔感。启示:**我们的极简已达标,不需再减;缺的是 diygod/xeiaso 级的内容节奏与仪式**。
|
||||
7. **Artalk 生态站**——**artalk.org 域名已易主**(实测标题"𝕜✺𝕫 𝕤𝕥𝕦𝕕𝕚𝕠 // healing arts therapy",Squarespace 疗愈站)⚠️;但官方实为 **artalk.js.org**,npm 包 `artalk` **2.10.1 于 2026-10-25(昨天)发布**,上游活跃。结论:我们自托管 artalk-go 2.10.0 容器不受域名易主影响;升级通道=Docker Hub;官方 demo 站本轮未访(js.org 子域未测),评论体验最佳实践改以"本机 2.10.0 API 实测"为准(§4.3)。**行动项:文档/书签里的 artalk.org 引用全部改 artalk.js.org**。
|
||||
8. 增量共性(补 v1 十二条之外):①**分类/标签中文意象命名+计数前置**(diygod×qh1009 双验证)→ 我们 PostList 的 reading/tech/photo 三轨 zh 词条已是意象词,可加计数;②**系列文/编号传统**(xeiaso×diygod×阮一峰三方验证)→ "瞬间"编号化(v1 #6)证据更强;③**RSS 可见性**(diygod 全文输出+xeiaso 列表页提示语)→ 我们 rss.xml.ts 存在但入口只在命令面板+页脚,/articles/ 页顶可加一行订阅提示。
|
||||
|
||||
## 4. 自查实证(生产 × 仓库,全部本轮实测)
|
||||
|
||||
### 4.1 SEO 60-63 根因(三连,全实锤)
|
||||
|
||||
| # | 事实 | 证据 | 影响 |
|
||||
|---|---|---|---|
|
||||
| 1 | **全站 `X-Robots-Tag: noindex`** | `curl -I https://wait4x.online/` 与文章页均带该头;源=`/etc/nginx/snippets/esa-origin-ssl.conf:11`(`add_header X-Robots-Tag noindex always;`),博客 server 块(`sites-enabled/default:92` wait4x.online→18090)include 了这个给内部服务(api/oj/token/fc/latex/cpa)设计的 snippet | Lighthouse `is-crawlable` fail(重权重);**搜索引擎视角全站不可索引** |
|
||||
| 2 | **robots.txt 不存在** | 生产 404(nginx);`public/` 与 `dist/` 均无 | Lighthouse robots 审计扣分;无 Sitemap 入口 |
|
||||
| 3 | **en 页 hreflang 自指双 en** | 生产 HTML:`hreflang="en" href="…/en/en/archives/mian-sha/"`(实测 404);源=`src/lib/locale.ts` 的 `localePath('/en/…','en')` 未剥旧前缀,`BaseLayout.astro:91` 直接调用 | Lighthouse hreflang fail;每页必扣 |
|
||||
|
||||
次级问题:④无自指 canonical,且 `/en/archives/mian-sha`(Header nav-lang 的 href)与 `/en/archives/mian-sha/`(sitemap/hreflang 形状)**双 200 不重定向**(nginx try_files 直出),重复内容风险;⑤home `<title>wait4x</title>` 过薄;⑥**ESA 对 text/html 缓存 2592000s(30 天)**(`X-Swift-CacheTime`),当前靠 Last-Modified/ETag 回源重验证拿新内容(实测 Last-Modified=当日部署时刻),但响应头(含 noindex)固化在缓存对象里——**修复后不 purge 则一个月内爬虫仍见 noindex**。本地曾 100 的原因:测试床无 nginx snippet、无 ESA、robots.txt 404 在旧版 Lighthouse 不判 fail。
|
||||
|
||||
**修法(A 卡片)**:① snippet 剥离 noindex 行,6 个内部服务 vhost 各自 server 块补回(它们确实该 noindex),博客 vhost 不加;博客私有路径(/admin/、/draft/)单独 location 加 noindex;② `public/robots.txt`(Allow 全站,Disallow /admin/ /draft/,Sitemap 指向 sitemap-index.xml);③ localePath 改为先 normalize 到 zh 再拼目标前缀;④ BaseLayout 加自指 canonical(Astro.site + withSlash(rawPath)),Header nav-lang href 补尾斜杠;⑤ ESA 控制台对 wait4x.online 执行一次全站 purge,并评估 HTML 边缘 TTL 调至 ≤1h 或接 purge API(发布可见性保险)。
|
||||
|
||||
### 4.2 /archives/ 归档现状(v1 #8 复核)
|
||||
|
||||
实测:`/archives/` 全量单页,zh 6 条 en 6 条,年份分组+三轨分节(PostList.astro),无分页;`content/posts` 7 文,差 1 = media-sample(`hidden` tag,`archives/index.astro` 过滤条件 `!data.draft && !data.tags.includes('hidden')`,注释声明"视觉测试完成后应删除")。**结论:达标,零改动**;唯一尾单:视觉验收完成后删 media-sample(它仍占 sitemap 一条 URL)。
|
||||
|
||||
### 4.3 Artalk 评论数 API(列表页前置数据源,已实测)
|
||||
|
||||
- 形状:`GET /api/v2/comments?page_key=/archives/<slug>/&type=page&limit=0&site_name=wait4x.online` → `{"comments":[],"count":0,"roots_count":0,"page":{…,"pv":4,…}}`。**注意参数是 `site_name` 不是 `site`**(`site` 报 err_no_site)。匿名可读,无需管理员。
|
||||
- site 值来源:`~/docker_apps/artalk/data/artalk.yml` `site_default: wait4x.online`;前端 ArtalkComments.astro 的 pv POST 已带 `site_name`,pageKey 口径一致(`/archives/<id>/`,中英共用不分裂)。
|
||||
- **构建期直拉可行**:构建机=Artalk 宿主(同机 127.0.0.1:13030),绕开 ESA/nginx,零缓存语义问题;构建产物静态渲染数字,接受"构建时刻快照"陈旧(v1 已定此取舍)。
|
||||
- 最新评论流(首页"最新心跳"旁):同端点 `type=all` 时仍要求 page_key,全站维度改用 `GET /api/v2/comments?type=all&site_name=…&limit=5` 需带任一 page_key 的坑已遇("Page_key必须填写")——**实施时以管理员 token 走 `/api/v2/pages`(需登录,`need_login:true` 实测)或退回逐页轮询 count 取 top-N**,两路都写在卡片验收里。
|
||||
- 上游健康:artalk-go 2.10.0(在跑)→ npm artalk 2.10.1(2026-10-25 发);artalk.org 域名易主(§3-7),文档引用改 artalk.js.org。
|
||||
|
||||
### 4.4 reader 纯文本模式落点(html.reader 单 class 方案)
|
||||
|
||||
Body 结构(BaseLayout.astro):`.bg-glow`(背景辉光)→ `.skip-link` → `header.site-header.safe-area`(含 nav-actions 三钮)→ `main#main.container`(正文+`.toc`侧栏+`.pn`上下篇+`#artalk-container`)→ `footer.site-footer`。会被覆盖/剥离的选择器清单(global.css + 组件 scoped):
|
||||
- 隐藏:`.bg-glow`、`.read-progress`(顶进度条)、Header `.nav-actions`(保留品牌+导航词)或整个 `.site-header` 收为一行、Footer 收为备案单行(ICP 陕ICP备2026014860号-1 **不可隐**——合规底线);
|
||||
- 中性化:正文入场动画类(global.css 动效层 `prefers-reduced-motion` 已有的降级规则可复用同款写法)、列表微浮 hover、`.atk-pv-line`/`#artalk-container` 隐藏或保留(建议保留评论——reader 是去装饰不是去内容,antfu Plaintext 同款语义);
|
||||
- 增强:`.prose-reading/.prose-tech` 字号微升一档、行宽略收(tokens.css 变量覆写)。
|
||||
- 入口:CommandPalette `data-cmd="reader"`(照 `data-cmd="theme"` 模式,`src/components/CommandPalette.astro:42`)+ 键盘 `r`(Keyboard.astro 弦位表)+ localStorage 持久化;VT 软导航下 class 挂 `documentElement` 天然存活。
|
||||
|
||||
## 5. 实施方案卡片
|
||||
|
||||
**A|SEO 解封三件套 + ESA purge**(工作量 1d;风险:nginx 重载失误→保留 .bak 回滚,ESA purge 不可逆但无害)
|
||||
- 目标:搜索引擎可索引,Lighthouse SEO ≥95。
|
||||
- 做法:`/etc/nginx/snippets/esa-origin-ssl.conf` 删 noindex 行;`api/oj/token/fc/latex/cpa/git` 各 vhost server 块补 `add_header X-Robots-Tag noindex always;`;博客 vhost 加 `location /admin/ { add_header X-Robots-Tag noindex always; }`;`public/robots.txt` 新建;ESA 全站 purge + HTML TTL 评估 ≤1h。
|
||||
- 验收:`curl -sI https://wait4x.online/ | grep -ci x-robots-tag` = 0;`curl -s https://wait4x.online/robots.txt` 含 `Sitemap: https://wait4x.online/sitemap-index.xml`;`curl -sI https://wait4x.online/admin/ | grep -ci noindex` = 1;Lighthouse 生产 home SEO ≥95。
|
||||
|
||||
**B|localePath 双 en + canonical + 尾斜杠**(0.5d;风险低,纯前端)
|
||||
- 做法:`src/lib/locale.ts` localePath 先归一到 zh 路径再拼;`BaseLayout.astro` 加 `<link rel="canonical" href={new URL(withSlash(rawPath), Astro.site)}>`;Header nav-lang href 与所有站内链接统一带尾斜杠。
|
||||
- 验收:`grep -c '/en/en/' dist/en/archives/mian-sha/index.html` = 0;`dist/` 内每页有自指 canonical;playwright `link[hreflang="en"]` 的 href 与自身 URL 相等;`curl -sI https://wait4x.online/en/archives/mian-sha`(无斜杠)→ 301 或 canonical 唯一。
|
||||
|
||||
**C|构建期内容 lint + 生产冒烟**(1d;风险:lint 误报阻断发布→先 warn 一周再 fail)
|
||||
- 做法:`scripts/build-perf/content-lint.mjs`(断链:a href 站内前缀逐一对 dist 产物;图片存在性;frontmatter typebox schema;zh/en 配对警告);`build.sh` 守卫挂钩 `[ -f scripts/build-perf/content-lint.mjs ] && node … || echo skip`(铁律:存在性守卫必须是第一笔改动);生产冒烟脚本 curl robots/sitemap/首页头三断言。
|
||||
- 验收:人为引入断链 md → build 输出 FAIL 清单;正常仓 build 通过;`sh infra/deploy/build.sh` 全链绿。
|
||||
|
||||
**D|/draft/ 预览路由**(1d;风险:草稿 URL 泄露→长随机段+noindex+不入 sitemap)
|
||||
- 做法:posts 集合 frontmatter 加 `draft: boolean`;`src/pages/draft/[slug].astro`(draft:true 文章构建进 `/draft/<token>/<slug>/`,token=构建期环境变量注入的随机串);getStaticPaths 正式路由已过滤 draft(`!data.draft` 现成);robots.txt Disallow /draft/;nginx 无需改(静态目录直出)。
|
||||
- 验收:draft 文章不出现在 `/archives/` 与 rss.xml/sitemap;`/draft/<token>/<slug>/` 200;`curl -sI …/draft/ | grep -ci noindex` = 1;playwright 断言 draft 页无 nav 中"文章"aria-current。
|
||||
|
||||
**E|reader 纯文本模式**(0.5d;风险:覆盖不全→按 §4.4 清单逐条断言)
|
||||
- 做法:`html.reader` 单 class(global.css 尾部集中一段,全部 `html.reader xxx` 前缀);CommandPalette 加 `data-cmd="reader"` 词条(i18n/ui 双语);Keyboard `r` 单键;localStorage `reader` 持久化。
|
||||
- 验收:playwright 触发命令后 `.bg-glow`/`.read-progress` 不可见、备案号可见、正文段落数不变;`html` 有 `reader` class 且刷新(reload)后仍在;E2E zh/en 各跑一遍。
|
||||
|
||||
**F|列表评论数 + 阅读时长角标**(1d;风险:Artalk 不可用时构建卡死→超时 3s 降级 `–`,lint warn 不 fail)
|
||||
- 做法:`scripts/build-perf/artalk-counts.mjs` 构建期对每篇 `GET 127.0.0.1:13030/api/v2/comments?…&site_name=wait4x.online&limit=0` 取 count 写 `dist` 内联进 props(或 src/data/ 快照 JSON,构建前生成);PostList.astro li 加 `<span class="cnt">N</span>` 与阅读时长角标(复用 `[...slug].astro:39` 的汉字/400 算法抽成 lib);首页"最近心跳"旁"最新评论"流(§4.3 两路择一)。
|
||||
- 验收:构建日志含每篇 count 拉取;dist 列表 HTML 含 `class="cnt"`;断 Artalk(停容器 30s)build 仍成功且角标 `–`;playwright 列表页可见"N 条"与"~8 min"。
|
||||
|
||||
**G|gallery EXIF 元数据卡**(1d;风险:老图无 EXIF→字段缺省隐藏不渲染空行)
|
||||
- 做法:photos_ingest.py 产物已含 exifr 数据,确认 JSON 字段(机型/焦段/光圈/ISO/时间/地点)→ gallery 详情/灯箱下加等宽字体表(mono,ink-3);nbfox 式"元数据即说明"。
|
||||
- 验收:任一有 EXIF 的图渲染 6 字段中 ≥4;无 EXIF 图无空表壳;E2E 断言 mono 类名。
|
||||
|
||||
**H|home title/description 语义化 + RSS 可见性**(0.5d)
|
||||
- 做法:home `<title>wait4x · 阅读 技术 摄影</title>` 级别语义;`/archives/` 顶部加一行 RSS 提示(xeiaso 式,链 /rss.xml 与 /en/rss.xml);description 按页级注入(BaseLayout 已有 prop,逐页补实)。
|
||||
- 验收:playwright 断言每类页 title 唯一;archives 页含 rss 链接且 hreflang 正确。
|
||||
|
||||
**I|瞬间编号化 + 友链页**(各 0.5d)
|
||||
- I1 moments 每条构建期序号 `#N`(diygod/qh1009/阮一峰三方验证的节奏感)+ 页顶"本周瞬间"聚合块(可选)。
|
||||
- I2 `src/pages/links.astro` + `content/links.yaml`(name/url/desc/avatar),导航不加项(克制),入口放 about 与页脚。验收:links 页渲染全部条目且 a[href] 全 200(lint 断链覆盖)。
|
||||
|
||||
**J|偏好雏形 + craft 编年页(观望)**:reader/辉光/动效三 toggle 收进命令面板 `settings` 分组(不做独立面板 UI);terminal 页扩"工艺案例编年体"(rauno /craft 式)列为远期。邮件复制微交互(rauno "Copied")0.2d 可顺手做进 Footer。
|
||||
|
||||
## 6. 排期建议(三批)
|
||||
|
||||
- **立即(本周,SEO 解封是硬闸门)**:A → B → C(C 的生产冒烟依赖 A/B 上线)→ E;ESA purge 随 A 同日执行。依赖:A 需 homelab nginx 操作权限(用户或运维车道);ESA 控制台 purge 需用户操作单。
|
||||
- **本月**:D(draft 路由)→ F(评论数,依赖 Artalk 在线策略确认)→ G(EXIF)→ H;尾单:删 media-sample。
|
||||
- **观望(触发条件)**:I1/I2(内容节奏成型后)、J(terminal 页改版时合并)、Artalk 2.10.1 升级(下个维护窗口)、Newsletter(RSS 订阅量证明需求后)、GitHub PAT(热力图真实数据,用户素材依赖,scratchpad 在案)。
|
||||
- 用户操作单(非代码):① ESA 全站 purge + HTML TTL 调整 + /artalk/api 绕缓存规则复核;② GitHub PAT(热力图);③ nginx 重载窗口确认。
|
||||
|
||||
## 7. 明确不做的(与灰阶克制冲突,v1 P2 续封 + 新增)
|
||||
|
||||
- 背景音乐/一言/背景特效(v1 P2,innei 同款):稀释终端隐喻一致性。
|
||||
- 首页大图专题卡/瀑布流重化(v1 P2,nbfox 同款):内容电商型视觉,与"灰阶骨架×分轨正文"定稿相悖。
|
||||
- 常驻浮动 TOC/阅读进度大改(v1 P2):现 .toc 桌面 sticky 已够,长文少。
|
||||
- **新增:评论数实时化/SWR**(构建期快照已够,实时=引入运行时查询+ESA 缓存语义复杂度)。
|
||||
- **新增:AI persona 注入彩蛋进正式页**(diygod 同款虽有趣,但正式内容层注 prompt 有被搜索引擎误读风险——只进 terminal 彩蛋页)。
|
||||
- **新增:独立设置面板 UI**(innei 式面板):命令面板分组已覆盖收纳需求,独立面板是 UI 负担。
|
||||
|
||||
## 附:本轮实测命令与产物索引
|
||||
|
||||
- 生产:`curl -sI https://wait4x.online/{,archives/mian-sha/,robots.txt,admin/}`(noindex 头、404、30 天缓存头);`/sitemap-0.xml` 34 URL 全 200。
|
||||
- 本机:`/etc/nginx/snippets/esa-origin-ssl.conf`、`sites-enabled/default:92`、`docker_apps/artalk/data/artalk.yml`、`127.0.0.1:13030/api/v2/comments?…` 响应原文。
|
||||
- 外站快照:/tmp/v2/*.html(会话级):rauno.me(含 /craft /projects)、diygod.cc(含 /posts)、xeiaso.net(含 /blog)、shud.in、vincentwill.com、sabrinacodes.com、artalk.org(易主证据);brianlovin.com=Vercel 挑战页(失败标注)。
|
||||
- 仓库:BaseLayout.astro / locale.ts / PostList.astro / CommandPalette.astro / ArtalkComments.astro / [slug] 与 [...slug].astro / archives/index.astro / build.sh / global.css / tokens.css。
|
||||
@@ -0,0 +1,204 @@
|
||||
# 博客美化专项方案(视觉打磨批次)
|
||||
|
||||
> 侦察员 @agent-591c3f | 2026-10-26
|
||||
> 证据链:仓库源码逐文件审计(tokens/global.css + 9 组件 + 2 文章页)× design/mockup.html 丙路线对照 × 本地重建 34 页实测(dist 内 Shiki 内联样式实抓)× WCAG 对比度数学计算 × 5 站补充抓取。
|
||||
> 截图证据床 /tmp/beauty-shots/(22 张,1280×900 fullPage,SW 阻断,gz-server 18800 测试床):现状 12 张 `{home,reading,tech,books,gallery,moments}-{light,dark}.png`;mockup 基线 10 张 `mock-{home,reading,tech,gallery,moments}-{light,dark}.png`。
|
||||
> 纪律分工:token 值/选择器/对比度数字=本报告结构性结论;"观感好坏"=⚠视觉待验,交视觉模型按 §5 问题清单复核,卡片产出不受阻。
|
||||
|
||||
## 0. 补充探索(每站聚焦视觉细节)
|
||||
|
||||
**抓取失败如实标注**:veselinov.pro、xivudong.me、photos.antfu.me 三域本机 DNS 无解析(getent 空记录,代理 7897 亦 000),已按任务预案换站并注明理由。
|
||||
|
||||
1. **rauno.me** ✅(HTML+2 CSS 实抓)——灰阶密度才是质感:stitches 体系 12 级灰(gray1–12 双主题 hsl 全套);间距阶严格 8px 步进(--space-1..11 = 8→88px);圆角三档 4/8/16+round;阴影三档全部 12% 黑透明(阴影当层级语言而非装饰);全站唯一动效曲线 `cubic-bezier(.2,.8,.2,1)`;focus-ring 变量化。字体=自研 X + JetBrains Mono + Georgia 点缀。
|
||||
2. **paco.me** ✅(3 CSS 实抓)——灰阶走**黑/白 alpha 阶梯**(--black-a1..a12 = 5%–95% 共 12 档):灰由前景色透明度派生,暗色主题零重复维护;Newsreader 斜体做强调词是签名手法;字号全站仅 13/14/16/1.25rem/4rem 五档;边框唯一 `--border-thin:1px`;页边距 `max(24px, env(safe-area-inset-*))` 安全区与留白合一。
|
||||
3. **mattdesl.com** ✅(换站理由见上)——全站等宽身份(BerkleyMono→Courier 回退)+ 97% 灰底/10% 墨字;标题 font-weight **normal**、统一 margin-top 1.5em——层级全靠字号与节奏不靠字重;标题小写化;标题锚 `#` 前缀常显;a:active 才变灰。与我们的 mono 轨同源:等宽当身份时,字重必须让位给间距。
|
||||
4. **tw93.fun** ✅(替代 xivudong.me,DNS 失败)——body 15px,身份字体藏锋金楷 TsangerJinKai02(标题正文混排),中文个人站"手写体温"代表;内联 prefers-reduced-motion 全量豁免(与我们同款纪律);header 图标化导航。启示:中文身份字体走金楷/文楷一脉成立,我们 `--font-kai` 已在栈内,金句正是它的岗位。
|
||||
5. **antfu.me/photos** ✅(photos 子域 DNS 失败,主域 /photos 200)——照片 img 带 inline `radial-gradient` **blur-up 占位**:图未加载先出主色近似色块,加载即有构图而非白洞;导航相机图标常驻;照片**无圆角无边框无阴影**直贴(摄影=内容本体不加框)。启示:图库质感优先做加载占位,而非边框阴影装饰。
|
||||
|
||||
## 1. 现状视觉审计(元素/现状/差距/目标)
|
||||
|
||||
| # | 元素 | 现状(源码事实) | 差距 | 目标 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 间距阶 | `--space-1..6`=4/8/16/24/40/64,跳档不均(缺 12/32) | rauno 8px 步进全阶 | 主阶不动,补正文流距 token(K) |
|
||||
| 2 | 标题节奏 | `.prose-* h2/h3` 仅 margin-top 1.8/1.5em,**无下距**(global.css:69-79) | 标题贴死正文;mockup h2 为 `1.8em 0 .6em` | h2/h3 补下距(K) |
|
||||
| 3 | 正文段距 | `p+p{margin-top:.9em}` 双轨 6 处字面量 | 不可一处调 | `--flow-*` token 化(K) |
|
||||
| 4 | 灰阶层级 | ink-1..4+line 五档;**ink-4(2.52:1) 用于 13px 日期/图注正文级文字**(PostList row-date、tl-date、g-note)不达 AA;无 border-strong 档 | ink-3 5.09:1 达标;ink-4 应退纯装饰位 | ink-4 停用于文字,新增 `--line-strong`(L) |
|
||||
| 5 | 圆角 | 4/6/8/10/12/999 字面量散落 7+ 处组件 | 未 token | `--radius-s/m/l` 三档(O) |
|
||||
| 6 | 动效时长 | 0.15s/160ms/0.4s/0.6s/2.4s 与 `--dur:350ms` 并存;**且 `.postlist .row` hover 1px 上浮是死规则**(global.css:196 类名应为 `post-list`,dist 实证从未生效) | 时长纪律散 + 微交互断线 | 修死规则 + `--dur-fast/slow` 补阶(O) |
|
||||
| 7 | 书评纸面板 | `.prose-reading` 仅上下 hairline + bg(global.css:52-63) | mockup 丙=全边框圆角卡(pad 44/radius 14)形态差 | 两案裁定(Q)⚠视觉待验 |
|
||||
| 8 | 代码块 | Astro 默认 Shiki github-dark,dist 实测内联 `background:#24292e` **压过 token `--code-bg:#0d1117`**;无语言标签;`.term-dots` 为死选择器 | token 与实际分裂 | Shiki css-variables 主题接管 + 灰阶窗条 + 语言标签(P) |
|
||||
| 9 | 表格 | global.css **零表格规则**;内容 3 篇含 markdown 表(dist 实证 `<table>` 裸渲染) | UA 默认裸表 | 分隔线制表格样式(R) |
|
||||
| 10 | 列表 | reset `*{margin:0}` 后无 prose 列表规则;内容 5 篇含列表 | li 间距仅靠行高,紧贴 | li+li 间距 + marker 色(R) |
|
||||
| 11 | 金句 | `.pull-quote` 全仓无使用(死选择器);blockquote 只有 A 墨线模式 | D 金句模式从未落地 | 写作指南模板 + mood 注入(Q) |
|
||||
| 12 | 引文规则 | global.css 与 [...slug].astro:172-177 **镜像双份** | 漂移风险 | 单源化(Q) |
|
||||
| 13 | 图库衬底 | `.stage` 仅 container-type,无背景 | mockup `--stage:#f4f4f2` 面板+radius 14 | 两案裁定(N)⚠视觉待验 |
|
||||
| 14 | 页面标题 | books/gallery/moments 用 `--text-h1` 27px | mockup g-title 20px/700 | 降一档 22px 或保持(K)⚠ |
|
||||
| 15 | 首屏 | IdCard=60px 渐变头像(紫→accent 彩色占位)+21px 名字,slot 被 socials 占用,**无身份陈述行** | mockup hero 有 bio 15px 行 | M 两案 |
|
||||
| 16 | 热力图 | ink 灰四档(组件注释明示决策) | mockup 原型为绿四档 | S 裁定 ⚠视觉待验 |
|
||||
| 17 | 达标项(不动) | 灯箱 backdrop 95%、focus ring 全局 2px、::selection 半透明、明暗双套 tokens、reduced-motion 全豁免(row hover 上浮除外——行 6 实锤为死规则) | — | 保持 |
|
||||
|
||||
## 2. 美化卡片(K–S,续 v2 A–J)
|
||||
|
||||
### K|正文流距 token 化与标题下距
|
||||
- **目标**:标题-正文、段落-段落的呼吸成为可调参数;治愈 h2 贴正文。
|
||||
- **改法**(tokens.css + global.css):新增 `--flow-p: 0.9em; --flow-h2-pre: 1.8em; --flow-h2-post: 0.6em; --flow-h3-pre: 1.5em; --flow-h3-post: 0.45em;`;global.css 双轨 h2/h3 改用变量并补 margin-bottom;`p+p` 改 `var(--flow-p)`。页面标题(Books/Gallery/Moments h1.page-title)27px→`--text-h2`(22px) 降档对齐 mockup g-title(视觉待验§5-6)。
|
||||
- **涉及**:`src/styles/tokens.css`、`src/styles/global.css`、三页组件 `page-title` 规则。
|
||||
- **验收**:playwright 断言 `.prose-tech h2` computed marginBottom ≈13.2px(22×0.6)、marginTop≈39.6px;`:root` `--flow-p`===' 0.9em';books 页 `h1.page-title` fontSize==='22px'。
|
||||
- **工作量**:0.5d。
|
||||
|
||||
### L|灰阶达标与 border-strong
|
||||
- **目标**:全部正文级文字 ≥4.5:1(WCAG AA 实测数:ink-3 亮 5.09/暗 5.22 ✓,ink-4 亮 2.52/暗 2.67 ✗);分隔线获得强弱两档。
|
||||
- **改法**:PostList `.row-date`、MomentsPage `.tl-date`、GalleryPage `.g-note`、index `.lab` 的 `color: var(--ink-4)`→`var(--ink-3)`;tokens 新增 `--line-strong: #d4d6db` / dark `#3d434f`,供 kbd 边框、chip 选中态(现用 ink-3 充当)、TOC 选中线。ink-4 仅保留纯装饰(track 前方块、beat-dot)。
|
||||
- **涉及**:tokens.css、PostList.astro、MomentsPage.astro、GalleryPage.astro、index.astro。
|
||||
- **验收**:`.row-date` computed color==='rgb(107, 110, 120)';对比度数学 5.09:1;`--line-strong` 存在且 chip:checked border-color===rgb(212,214,219)。
|
||||
- **工作量**:0.5d。
|
||||
|
||||
### M|首屏叙事两案(⚠视觉待验 §5-1 定案)
|
||||
- **案1 极简维持+身份陈述**(推荐先行):IdCard 结构改两行——名字 21px 下加 `.idcard-bio`(15px/ink-3,一句身份陈述,文案用户定);socials 移到 bio 行下;头像渐变占位灰阶化(`--mood-3/accent` 渐变→`ink-3→ink-1` 灰渐变,真头像接入前不破纪律)。`margin-block` 40→56px 加呼吸。
|
||||
- **案2 hero 强化**:头像 72px、名字 `--text-h1` 27px、bio 一行 mono 14px tagline(如 `阅读 · 技术 · 摄影`),首屏区块底加 `--line` 分隔。视觉重量↑,与"骨架克制"张力↑。
|
||||
- **涉及**:IdCard.astro、Avatar.astro、index.astro。
|
||||
- **验收**:案1 `.idcard-bio` 存在且 fontSize==='15px'、color===ink-3;头像 svg 首个 stop 颜色为灰阶(无 hue)。
|
||||
- **工作量**:案1 0.5d / 案2 1d。
|
||||
|
||||
### N|图片质感与灯箱纯度
|
||||
- **目标**:照片墙获得"策展台"层次而非散图直贴;灯箱进入"暗房"纯度。
|
||||
- **改法**:tokens 新增 `--stage: #f4f4f2` / dark `#101318`(mockup 丙同位);GalleryPage `.stage` 加 `background: var(--stage); padding: var(--space-3); border-radius: var(--radius-l, 14px)`(⚠两案:有衬底 vs 无衬底,§5-4);新增 `--radius-img: 10px` 统一 .shot/.thumb/.mom-fig/prose img 圆角(现 8/8/8/10 混用);灯箱 backdrop `rgba(8,10,14,.95)`→`.97` + 底条 mono 字距 `0.02em`;blur-up 占位列观望(antfu 手法需管线配合,非纯 CSS)。
|
||||
- **涉及**:tokens.css、GalleryPage.astro、MomentsPage.astro、index.astro、global.css。
|
||||
- **验收**:`.stage` computed backgroundColor==='rgb(244, 244, 242)';`:root` `--radius-img` 存在且 `.thumb` borderRadius==='10px';dialog#lb backdrop 不便断言→断言 CSS 源含 0.97。
|
||||
- **工作量**:1d。
|
||||
|
||||
### O|微交互 token 化与 focus ring 收尾
|
||||
- **改法**:先修死规则——global.css `.postlist .row`→`.post-list .row`(hover 1px 上浮即刻复活);tokens 新增 `--dur-fast: 150ms; --dur-slow: 600ms;`;替换字面量——row hover `0.15s ease`→`var(--dur-fast) var(--ease)`、GalleryPage `#lb-img` 160ms→`var(--dur-fast)`、`.bg-glow` 0.6s→`var(--dur-slow)`、rise-in 0.4s→`calc(var(--dur) + 50ms)`;圆角 token:`--radius-s:6px; --radius-m:10px; --radius-l:14px`,全局字面量归位;`:focus-visible` 补 `border-radius: 2px`(mockup:29,防直角 outline 扎圆角钮)。
|
||||
- **涉及**:tokens.css、global.css、Header.astro、GalleryPage.astro、各组件 radius 字面量。
|
||||
- **验收**:`.post-list .row` computed transitionDuration==='0.15s' 且 hover 后 transform==='matrix(1, 0, 0, 1, 0, -1)' 且 timingFunction 含 'cubic-bezier(0.19, 1, 0.22, 1)';`:focus-visible` computed borderRadius==='2px';`grep -c 'px' radius 字面量` 下降(构建审计)。
|
||||
- **工作量**:0.5d。
|
||||
|
||||
### P|代码块窗口化(Shiki 接管 + 语言标签 + 灰阶窗条)
|
||||
- **目标**:`--code-bg/--code-ink` token 恢复统治(现被 Shiki 内联 #24292e 压制);代码块获得窗口身份。
|
||||
- **改法**:astro.config.mjs `markdown.shikiConfig.theme: 'css-variables'`(Shiki 官方主题,颜色全走 `--shiki-*` 变量);global.css 定义 `--shiki-default/--shiki-key/...` 对齐现有 github-dark 色系但 bg/ink 用 `var(--code-bg)/var(--code-ink)`,亮暗同底纪律恢复;`pre.astro-code[data-language]::after{content:attr(data-language); …mono 11px; color:#8b949e; text-align:right; display:block}` 语言标签(零 JS,dist 实证 data-language 属性已在);窗条三灰点用 `pre::before{content:'';display:block;width:10px;height:10px;border-radius:50%;background:#3f4650;box-shadow:16px 0 0 #3f4650,32px 0 0 #3f4650;margin-bottom:8px}`(灰阶三档 `#3f4650/#4a515b/#565f6a` 递进,macOS 彩点不采——红线);pre padding 顶加 `6px`。删除 `.term-dots` 死规则。**行号不做**(短命令块为主,320px 移动端宽度宝贵,见 §4)。
|
||||
- **涉及**:astro.config.mjs、global.css。
|
||||
- **验收**:dist 文章页 `pre.astro-code` 无内联 background-color(`getAttribute('style')` 不含 'background-color');pre computed backgroundColor==='rgb(13, 17, 23)';`getComputedStyle(pre,'::after').content` 非 'none' 且含 attr;`getComputedStyle(pre,'::before')` width==='10px'。
|
||||
- **工作量**:1d。
|
||||
|
||||
### Q|书评轨:纸面板形态 + 金句实装 + 引文单源
|
||||
- **目标**:书评页的"纸感"从色块升级为形态;D 金句模式真正可用。
|
||||
- **改法**:
|
||||
- 纸面板两案:**A 卡化**=`.prose-reading{border:1px solid var(--paper-line);border-radius:var(--radius-l);padding:var(--space-5) clamp(20px,4vw,32px)}`(mockup sheet 形态);**B 维持通栏** hairline 仅把上下线升级 `--line-strong`。(⚠§5-2 裁定)
|
||||
- 金句:内容约定 HTML 直书 `<blockquote class="pull-quote"><p>…</p><span class="quote-cite">——作者《书名》</span></blockquote>`(写作指南已有 raw HTML 先例);pull-quote 样式修 62px→`3.2em` 相对化 + `--mood` 从文章 frontmatter 经 [...slug].astro `article style="--mood:…"` 注入(现 PostList 已用同源 mood,接线一致);docs/写作指南.md 加模板一节。
|
||||
- 引文单源:删除 [...slug].astro:172-177 镜像规则,markdown 裸 blockquote 全靠 global.css。
|
||||
- **涉及**:global.css、pages/archives/[...slug].astro、en 同文件、docs/写作指南.md。
|
||||
- **验收**:`grep -c 'blockquote' src/pages/archives/[...slug].astro`===0;含 pull-quote 的页面 computed fontFamily 含 'WenKai'、`::before` fontSize==='64px'(20×3.2);A 案 `.prose-reading` computed borderRadius==='14px'。
|
||||
- **工作量**:0.5d。
|
||||
|
||||
### R|表格与列表基建(从零到精)
|
||||
- **改法**(global.css,作用于 `.prose-reading/.prose-tech`):表格 `table{border-collapse:collapse;width:100%;font-size:.92em;margin-block:1.2em} th{text-align:left;font-weight:600;color:var(--ink-1)?→书评轨用 paper-ink} th,td{padding:6px 12px 6px 0;border-bottom:1px solid var(--line)} tbody tr:last-child>*{border-bottom:0} td{font-variant-numeric:tabular-nums}`——分隔线制,**不做 Zebra**(灰阶密度已够,见 §4);列表 `ul,ol{padding-left:1.5em;margin-block:1.2em} li+li{margin-top:.35em} li::marker{color:var(--ink-3)}`;tech 轨 kbd 已有样式保持。
|
||||
- **涉及**:global.css(含 3 篇含表内容无需改动)。
|
||||
- **验收**:tech 文章页 `th` computed borderBottomWidth==='1px';`li+li` marginTop≈6.65px(.35em@19px);`li::marker` color==='rgb(107, 110, 120)'。
|
||||
- **工作量**:0.5d。
|
||||
|
||||
### S|热力图色系裁定(⚠视觉待验 §5-5)
|
||||
- **现状**:ink 灰四档(hm-l1..4=ink-4..1),组件注释明示"灰阶纪律"决策;mockup 原型为 GitHub 绿四档。绿=贡献量语义直觉(外部认知零成本),灰=骨架纯度。
|
||||
- **改法(若裁定换绿)**:tokens 新增 `--hm-0:#ebedf0/--hm-1:#9be9a8/--hm-2:#40c463/--hm-3:#30a14e/--hm-4:#216e39` + dark 套(#22272e/#0e4429/#006d32/#26a641/#39d353),Heatmap.astro 四行替换。默认建议**维持灰阶**(热力图是骨架层不是内容层;绿色块会成为首页唯一大面积彩色,重心被劫持)。
|
||||
- **验收**:若换绿 `--hm-4`===' #216e39' 且 `.hm-l4` backgroundColor 对应;维持则零改动,S 卡关闭。
|
||||
- **工作量**:0.2d。
|
||||
|
||||
## 3. 优先级(视觉收益 × 工作量)
|
||||
|
||||
| 卡 | 视觉收益 | 工作量 | 象限 |
|
||||
|---|---|---|---|
|
||||
| L 灰阶达标 | 高(可读性硬指标) | 0.5d | ★高收益低成本 |
|
||||
| R 表格列表 | 高(3+5 篇内容直接受益,现裸样式) | 0.5d | ★ |
|
||||
| K 流距/标题下距 | 高(全文页节奏) | 0.5d | ★ |
|
||||
| M 案1 首屏陈述 | 高(第一屏人格) | 0.5d | ★ |
|
||||
| P 代码块窗口化 | 高(技术轨视觉主件) | 1d | ★(成本略高但收益覆盖全部技术文) |
|
||||
| Q 纸面板+金句 | 中高 | 0.5d | 次批 |
|
||||
| O 微交互 token | 中(一致性负债清偿) | 0.5d | 次批 |
|
||||
| N 图库质感 | 中 | 1d | 次批(含⚠两案) |
|
||||
| S 热力图 | 低(大概率维持现状) | 0.2d | 随批 |
|
||||
|
||||
**前 5(高收益低成本)**:L → R → K → M案1 → P。合计 ≈3d,其中 L/R/K 纯 global+tokens 层,一次提交可收。
|
||||
|
||||
## 4. 明确不做(与灰阶克制红线冲突,含理由)
|
||||
|
||||
1. **玻璃拟态/大面积毛玻璃**:backdrop-filter 大面积使用与"纸墨实体感"冲突;微信内置浏览器兼容与滚动性能翻车史;Header 8px blur 轻量例外保留。
|
||||
2. **渐变色文字/渐变大块**:全站唯一强调色(--accent 单色)是丙路线根基;渐变=多色并存,01 报告诊断的多色病复发。
|
||||
3. **大圆角卡片风(≥16px)**:圆阶上限钉 `--radius-l:14px`;大圆角是消费 App 友好风,与终端/制版感相反。
|
||||
4. **macOS 三彩圆点/彩色窗条**:代码窗条采灰阶三档(P 卡);彩点是全站唯一彩点的地位争夺者。
|
||||
5. **噪点/纸纹覆盖层**(02 报告 Tier1 曾列,本批否决):纸感已由 `--paper` 米白承担;噪点在低分屏/微信内染摩尔纹,reduced-motion 需额外豁免,收益/成本比不成立。
|
||||
6. **表格 Zebra 斑马纹**:隔行灰块密度过高,与分隔线骨感二选一取线(R 卡)。
|
||||
7. **代码行号**:内容以短命令块为主,行号吃掉 320px 移动端宽度;Shiki attrs 可后补不锁死。
|
||||
8. **行尾点号悬挂/首行缩进**:03 报告已证伪(无出版惯例/避头尾风险)。
|
||||
9. **弹跳缓动/大面积视差/入场加码**:红线"不弹跳宁轻勿闹";现有 12px rise-in + 1px hover 是上限。
|
||||
10. **自定义光标**:02 Tier3 已判,正文必须标准 I-beam。
|
||||
|
||||
## 5. 截图问题清单(⚠视觉待验——交视觉模型,附证据文件)
|
||||
|
||||
| # | 问题 | 证据对 |
|
||||
|---|---|---|
|
||||
| 1 | 首屏 M 案裁定:现状 IdCard+列表直连的密度 vs mockup hero 的呼吸,哪个更"被设计过"?案2 hero 是否过度? | home-{light,dark}.png ↔ mock-home-{light,dark}.png |
|
||||
| 2 | 纸面板 Q-A/B:通栏 hairline(现状)vs 全边框圆角卡(mockup),哪个更"书页"?暗色下 paper #f8f6f1↔#201d18 转换是否自然? | reading-{light,dark}.png ↔ mock-reading-{light,dark}.png |
|
||||
| 3 | 代码块 P:加灰阶窗条+语言标签后是否加分?(可先看 mockup term 窗对比现裸块) | tech-{light,dark}.png ↔ mock-tech-{light,dark}.png |
|
||||
| 4 | 图库 N:stage 米灰衬底 vs 直贴,哪个衬托照片更好? | gallery-{light,dark}.png ↔ mock-gallery-{light,dark}.png |
|
||||
| 5 | 热力图 S:灰四档 vs 绿四档的量级辨识与首页色彩重心 | home-{light,dark}.png(热力图区) |
|
||||
| 6 | 页面标题降档(27→22px):books/gallery/moments 三页标题现值是否过重? | books-{light,dark}.png(无 mock 对照,独立判断) |
|
||||
| 7 | 现状整体灰阶层级是否"够用"(rauno 12 档 vs 我们 5 档):列表行 hairline+日期档位是否单薄? | home-*.png 列表区 ↔ mock-home-*.png |
|
||||
|
||||
## 附:测试床与复现
|
||||
|
||||
- 截图环境:`npm run build`(34 页)→ `python3 scripts/build-perf/gz-server.py 18800 dist`(写稿时仍在跑,用后可杀:`pgrep -f gz-server` 取 PID kill);脚本 `/tmp/beauty-shots/shoot.cjs` 与 `shoot-mockup.cjs`(`NODE_PATH=$(pwd)/node_modules node …`)。
|
||||
- 对比度计算:sRGB 相对亮度公式手算(§1-4 数值即此口径),可直接进验收注释。
|
||||
|
||||
|
||||
## 6. 视觉裁定结果(glm-5.3-flash:max 视觉轨,2026-10-26 定案)
|
||||
|
||||
> §5 七问全部裁定完毕,⚠视觉待验标记以此为准失效;卡片 M/N/S 的两案之争已定。
|
||||
|
||||
裁定基准:灰阶克制(不彩色不弹跳宁轻勿闹)· 专业质感(被设计过≠被装饰过)· 双模式同判。
|
||||
证据:/tmp/beauty-shots/ 现状 12 张 + mock 10 张全部读图;另对书评页头部(亮/暗)与热力图区(亮/暗)做 2× 特写裁片 4 张复核细节。
|
||||
|
||||
---
|
||||
|
||||
## #1 首屏:现状 IdCard 直连 vs mock hero
|
||||
**裁定:两案融合**(取 mock 的 tagline + 标题升一档;保留现状的信息密度与紧凑行距,不 banner 化)| 信心:高
|
||||
理由:现状 IdCard(头像+名+3 图标)到 2026 列表之间无一句身份语,读感偏"档案页";mock hero 仅一行「写作者 · 读者 · 快门按压者,拥抱开源。」+社交图标,无插画无 CTA,克制成立——不过度、不空洞,且"被设计过"确实更强。
|
||||
落地注意:tagline 控制在 ≤20 字级,移动端随 hero 收紧行高;禁加背景辉光、大字标语等 banner 元素。
|
||||
|
||||
## #2 书评纸面板:通栏 hairline vs 圆角纸卡
|
||||
**裁定:选 mockup**(保留现状"标题区外置"结构;正文列由 hairline+微纸底升级为圆角米灰纸卡)| 信心:高
|
||||
理由:现状正文列只有极淡暖底 + 顶部通栏 hairline,"一页纸"的物体感弱;mock 的细边框+圆角+米灰底让纸卡成立。暗色不冲突:现状暗列与 mock 暗卡都已转暖棕纸色(夜灯感),转换自然。
|
||||
落地注意:本文实测 7000px 长文——卡片左右边框在视口内就是两根竖线,边框用 1px hairline 级、圆角收敛;移动端收 padding 保字宽。
|
||||
|
||||
## #3 代码块:裸块 vs 终端窗条
|
||||
**裁定:选 mockup(窗条加分),但只给多行块** | 信心:高
|
||||
理由:现状暗色技术页一排单行裸块退化为"纹理条",块与底仅靠细边框区分;mock 窗条提供 `bash · deploy@vps` 标题(是信息不是装饰)+ 一条灰阶色带,暗色下块轮廓明显更立。
|
||||
落地注意:≥4 行的块才挂窗条,单行命令保持裸 pill;红黄绿三点可换灰阶三点——窗条+mono 标题已足够传达终端梗,守住红线更稳。
|
||||
|
||||
## #4 图库衬底:直贴 vs stage 米灰衬底
|
||||
**裁定:两案融合**(亮色维持白底直贴;暗色给照片加轻衬底/描边)| 信心:中
|
||||
理由:亮色现状就是干净的白盒画廊(21 张彩照自供色),灰衬板会成全页最大 UI 块、抢照片的戏;暗色实拍夜景照(明珠塔/暗楼群/暗日落)边缘融底,轻衬底恰好能救。mock 图库两图均截在动画中途(扇形堆叠+页脚透字),终态布局证据不足。
|
||||
落地注意:暗色衬底用 ~4% 白底或 1px hairline,衬底圆角不得大于照片圆角,避免"框中框"。
|
||||
|
||||
## #5 热力图:灰档量级辨识 & 绿系利弊(只评不站队)
|
||||
**裁定:双模式分别判——亮色灰档够用;暗色低两档实缺。绿系利弊各半** | 信心:高
|
||||
理由:2× 特写下亮色灰档可辨、密集黑簇(周峰值列)一目了然;暗色最低两档与底色粘连、接近丢失。绿系=明度+色相双编码、暗色读数大增、geek 熟悉感;代价是成为全页最大色块,与照片条/彩点连成"颜色带",压灰阶骨架重心、违"宁轻勿闹"。
|
||||
落地注意:若动,只动暗色或用去饱和苔绿(sage);亮色不建议换绿。
|
||||
|
||||
## #6 书架页标题 27px 是否过重
|
||||
**裁定:维持现状(不过重,不降到 22px)** | 信心:中
|
||||
理由:书架页唯一层级锚点,27px 与 17px 级书名卡的差距正是层级本身;降到 22 与卡题贴脸,稀疏页面会更散。暗色下白字光晕(halation)放大视觉重量,是"显得重"的主因。
|
||||
落地注意:与图库/文章页标题保持同轨字号(一致性优先于单页微调);要轻就减字重(700→600)不减号。
|
||||
|
||||
## #7 灰阶层级:hairline+日期单薄?5 档够不够
|
||||
**裁定:两案融合**(hairline 保留但更淡,行距向 mock 靠拢;5 档灰够用,不追 12 档)| 信心:高
|
||||
理由:现状 hairline+右对齐日期是"档案感"资产,并不单薄;mock 全靠行距分组更透气,但长列表会失锚。页面可感知灰阶本就 4-6 阶,rauno 的 12 档是设计体系储备,页面级硬凑只会出"渐变灰"噪感。
|
||||
落地注意:暗色 hairline 再降一档亮度(现状略亮、切分感偏强);行距 +2~4px 优先于删线。
|
||||
|
||||
---
|
||||
|
||||
## 三行总评
|
||||
1. **当前视觉成熟度:约 7.5/10**——已是完整自洽的灰阶克制系统(暖纸正文列/彩色书脊卡/mono 元数据/双模式纸色转换),缺口集中在 hero 身份语、暗色低灰阶辨识度、代码块"仪器感"。
|
||||
2. **最值的一张卡:代码块终端窗条**——信息(上下文标题)+质感双收益、暗色收益最大、实现风险最低;书评纸卡次之。
|
||||
3. **最危险的过度设计倾向:把画廊"道具化"**(stage 衬底+扇形堆叠动效是 mock 截图里已现形的方向)——与 hero banner 化、绿热力图同属"为设计感加戏",画廊应是白盒,动效层越薄越贵。
|
||||
|
||||
## 证据瑕疵备注
|
||||
mock-home-dark 列表缺《海风中失落的血色馈赠》一行(截图态);mock 图库亮/暗两图均抓在动画中途。以上不影响各问主证据链,仅 #4 信心降为中。
|
||||