Compare commits
14
Commits
975cf5e58b
...
19b2390cbf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19b2390cbf | ||
|
|
983527a79d | ||
|
|
23ea8e5a55 | ||
|
|
57f19d855b | ||
|
|
a2395be379 | ||
|
|
90b8d0fbc0 | ||
|
|
4e24fe510f | ||
|
|
2bfe2ef3db | ||
|
|
814b138f89 | ||
|
|
c307285a25 | ||
|
|
8ec1712135 | ||
|
|
22930cfffd | ||
|
|
5d18591967 | ||
|
|
c1e3d30954 |
No files matched your search
@@ -0,0 +1,95 @@
|
|||||||
|
# Phase 5f:SEO 解封与功能优化批 + 美化首批 - 完成总结
|
||||||
|
|
||||||
|
## 交付结论
|
||||||
|
- 结果:完成
|
||||||
|
- 完成时间:2026-11-01 09:51
|
||||||
|
|
||||||
|
## 假设回顾
|
||||||
|
### 已验证假设
|
||||||
|
- nginx sudo 与 ESA 控制台由用户操作(本包只备好仓库侧与操作单)
|
||||||
|
- 视觉裁定为终态依据;实现与裁定冲突时以裁定为准并在完成总结记录
|
||||||
|
|
||||||
|
### 仍未完全验证的假设
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 交付范围
|
||||||
|
### 已交付
|
||||||
|
- T1 SEO 仓库侧三件(robots.txt + admin noindex 预案 + 用户操作单)
|
||||||
|
- T2 localePath 双 en 修复 + 自指 canonical + 尾斜杠归一
|
||||||
|
- T3 构建期内容 lint + 生产冒烟 + build.sh 守卫挂载
|
||||||
|
- T4 reader 纯文本模式(html.reader + 命令面板 + r 键 + 持久化)
|
||||||
|
- T5 代码块窗条(P 卡:≥4 行灰阶窗+mono 标题+灰阶三点)
|
||||||
|
- T6 书评纸面板(Q 卡:圆角米灰纸卡+暗色暖棕+hairline 边框)
|
||||||
|
- T7 版式基底(K/L/O 卡:流距 token+灰阶补档+暗色 hairline 降档+微交互曲线+focus ring)
|
||||||
|
- T8 首屏 tagline + 图库质感(M/N 卡:身份语+圆角统一+暗色衬底)
|
||||||
|
|
||||||
|
### 未交付
|
||||||
|
- 无
|
||||||
|
|
||||||
|
### 偏差说明
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 简化决策
|
||||||
|
### 保持简单的关键选择
|
||||||
|
- 最简单可行方案:token 值级 CSS 改动为主 + locale.ts 单函数修 + 两个构建脚本;无新依赖
|
||||||
|
- 暂不引入:blur-up 占位(需管线)、独立设置面板
|
||||||
|
- 不做的抽象:主题引擎化/样式体系重构(tokens 增量演化)
|
||||||
|
|
||||||
|
### 本轮明确不做的内容
|
||||||
|
- nginx/ESA 实际操作(用户侧);draft 路由 D、评论数 F、EXIF 卡 G、title H(本月批后续包)
|
||||||
|
|
||||||
|
## 变更边界
|
||||||
|
### 本轮主要改动模块
|
||||||
|
- 见 tasks.md 的 boundary 记录
|
||||||
|
|
||||||
|
### 明确未触碰的区域
|
||||||
|
- nginx/ESA 实际操作(用户侧);draft 路由 D、评论数 F、EXIF 卡 G、title H(本月批后续包)
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
### 备注
|
||||||
|
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 级别:package
|
||||||
|
- 机器验证率:8/8
|
||||||
|
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||||
|
|
||||||
|
## 哲学生效证据
|
||||||
|
### 行为成效回填
|
||||||
|
- Development Record 行为门禁通过
|
||||||
|
|
||||||
|
### 一致性门禁回顾
|
||||||
|
- 验收面(checklist 或成功标准)已通过
|
||||||
|
|
||||||
|
## Git 记录
|
||||||
|
- 日期时间:2026-11-01 09:51
|
||||||
|
- 范围:Phase 5f:SEO 解封与功能优化批 + 美化首批
|
||||||
|
- 功能:T1 SEO 仓库侧三件(robots.txt + admin noindex 预案 + 用户操作单);T2 localePath 双 en 修复 + 自指 canonical + 尾斜杠归一;T3 构建期内容 lint + 生产冒烟 + build.sh 守卫挂载;T4 reader 纯文本模式(html.reader + 命令面板 + r 键 + 持久化);T5 代码块窗条(P 卡:≥4 行灰阶窗+mono 标题+灰阶三点);T6 书评纸面板(Q 卡:圆角米灰纸卡+暗色暖棕+hairline 边框);T7 版式基底(K/L/O 卡:流距 token+灰阶补档+暗色 hairline 降档+微交互曲线+focus ring);T8 首屏 tagline + 图库质感(M/N 卡:身份语+圆角统一+暗色衬底)
|
||||||
|
- 操作:提交 / 推送
|
||||||
|
- 成效:Development Record 行为门禁通过
|
||||||
|
- 提交:pending local commit
|
||||||
|
- 提交区间:975cf5e58bf2846fc0c1e34a600725bcfe915f0e..23ea8e5a555a984cf0e8be46efecb718d26d3cdc
|
||||||
|
- 推送:not-run (push stage follows commit)
|
||||||
|
|
||||||
|
## AGENTS.md 增量提议
|
||||||
|
```diff
|
||||||
|
Phase 5f 教训与资产:①收官门禁代码化(spec_gate.py 首秀连环拦下五宗——E2E 口径错配/竞态雷/verify 管道笔误/5.1 双字段措辞/字段改名丢失,每次给出可执行下一步);②E2E_BASE 覆写为 spec 验收标准姿势;③视觉双轨闭环(token 方案→flash:max 像素验收);④? 浮层焦点竞态根治;⑤车道 verify 落点偏差随回执报告;⑥add -A 卷入在途车道第四次变体秒拆;⑦verify 断言禁用 -q 接管道;⑧spec 模板字段名不可创意改写(升级触发≠升级条件)。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 被拒绝的扩展提议
|
||||||
|
- 无
|
||||||
|
|
||||||
|
## 门禁证据
|
||||||
|
- check_spec_package.py exit 0
|
||||||
|
|
||||||
|
## 遗留事项
|
||||||
|
- 无;问题处置见下方结构化区块
|
||||||
|
|
||||||
|
## 问题处置
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"issues": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:23+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 1, "duration_s": 0.669, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:23+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.06, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:23+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 1, "duration_s": 0.637, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:24+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 1, "duration_s": 0.675, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:25+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/ | grep -v node_modules", "exit": 1, "duration_s": 0.638, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:25+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 1, "duration_s": 0.656, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:26+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 1, "duration_s": 0.66, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b8b3033256d8436e1994365e0a4d843de899a400abcd303c4a3f8703bf7236ec", "at": "2026-11-01T01:31:27+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:31:48+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 15.103, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:03+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.073, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:04+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 1, "duration_s": 14.313, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:18+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.413, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:32+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/ | grep -v node_modules", "exit": 1, "duration_s": 14.48, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:32:47+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 1, "duration_s": 14.192, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:33:01+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.446, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "7c6557e6c8bfbe5baff62bc1881f03fd8d53750b60ad5fd843888b0db2932995", "at": "2026-11-01T01:33:15+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:34:48+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 14.521, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:02+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.056, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:02+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 0, "duration_s": 14.398, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:17+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.495, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:31+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/ | grep -v node_modules", "exit": 1, "duration_s": 14.653, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:35:46+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 0, "duration_s": 14.506, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:36:00+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.668, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a7774fa671522fb695d781169a132c4dfe108f5de2a8fd911e8e2993f16f10e6", "at": "2026-11-01T01:36:15+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:05+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 14.603, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:19+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.071, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:19+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 0, "duration_s": 14.487, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:34+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.324, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:40:48+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/", "exit": 0, "duration_s": 14.598, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:41:03+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 0, "duration_s": 15.156, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:41:18+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.487, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "59aaab217ae63cfe6ec2718684c60490ecb25a65c0f1a3dd89920e9b7a6795a7", "at": "2026-11-01T01:41:32+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:01+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 14.593, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:16+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.058, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:16+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 0, "duration_s": 14.429, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:31+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.396, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:45+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/", "exit": 0, "duration_s": 14.486, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:42:59+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 0, "duration_s": 14.479, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:43:14+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.333, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "5e3805453ecab02ecf7d7db336ac16e05fa30d2b497ba655209e410c8c527dfd", "at": "2026-11-01T01:43:28+00:00"}
|
||||||
|
{"task": "task_0001", "cmd": "test -f public/robots.txt && grep -q \"Sitemap: https://wait4x.online/sitemap-index.xml\" public/robots.txt && grep -q \"Disallow: /admin\" public/robots.txt && grep -q \"SEO 解封操作单\" docs/上线检查单.md", "exit": 0, "duration_s": 0.005, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:49:52+00:00"}
|
||||||
|
{"task": "task_0002", "cmd": "! grep -r \"/en/en/\" dist/ && grep -q 'rel=\"canonical\"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\"", "exit": 0, "duration_s": 14.485, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:06+00:00"}
|
||||||
|
{"task": "task_0003", "cmd": "node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh", "exit": 0, "duration_s": 0.062, "stdout_tail_sha256": "209fba8e31f47e3c0984b06e53b6dec06a74dcc59311dae22cca8323af4ad09f", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:06+00:00"}
|
||||||
|
{"task": "task_0004", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q 'data-cmd=\"reader\"' src/components/CommandPalette.astro && grep -q \"html.reader\" src/styles/global.css", "exit": 0, "duration_s": 14.474, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:21+00:00"}
|
||||||
|
{"task": "task_0005", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rqE \"code-win|codebar|窗条\" src/pages/archives/", "exit": 0, "duration_s": 14.495, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:35+00:00"}
|
||||||
|
{"task": "task_0006", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"paper\" src/components/", "exit": 0, "duration_s": 14.482, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:50:50+00:00"}
|
||||||
|
{"task": "task_0007", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -q \"flow-h2-pre\" src/styles/tokens.css && grep -q \"focus-visible\" src/styles/global.css", "exit": 0, "duration_s": 14.59, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:51:04+00:00"}
|
||||||
|
{"task": "task_0008", "cmd": "E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q \"34/34 PASS\" && grep -rq \"tagline\" src/pages/index.astro src/components/ && grep -q \"radius-img\" src/styles/tokens.css", "exit": 0, "duration_s": 14.452, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "a0b992acff0e1c843f7cb94470f4c1304b5e8747a0fa18dea2a93e69200f3de7", "at": "2026-11-01T01:51:19+00:00"}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Phase 5f:SEO 解封与功能优化批 + 美化首批 - 项目范围
|
||||||
|
|
||||||
|
<!-- spec-format: 2 -->
|
||||||
|
|
||||||
|
## 1. 问题定义
|
||||||
|
- **项目目标**:①解除生产全站 noindex 封印并补齐 SEO 基建(事故级:上线至今搜索引擎不可见)②修双语暗伤(localePath 双 en/canonical/尾斜杠)③构建期内容 lint+生产冒烟防线 ④reader 纯文本模式 ⑤美化首批 K-Q(流距 token 化/灰阶补档/首屏 tagline/图库质感/微交互/代码块窗条/书评纸面板,全部经视觉模型裁定定案)
|
||||||
|
- **目标用户**:站长(写作/发布/被搜索到)与读者(阅读质感/双语导航正确性)
|
||||||
|
- **核心价值**:站点从"隐身可用"到"可被发现+有质感";发布链自带质检
|
||||||
|
|
||||||
|
## 2. 假设与待确认
|
||||||
|
|
||||||
|
### 2.1 已确认事实
|
||||||
|
- noindex 来源=/etc/nginx/snippets/esa-origin-ssl.conf:11(我已 curl+grep 实锤);robots.txt 生产 404;ESA 头缓存 30 天
|
||||||
|
- localePath 在 en→en 场景产出 /en/en/...(404)
|
||||||
|
- Artalk 评论数端点已实测(GET /api/v2/comments?site_name=wait4x.online&limit=0 → count)
|
||||||
|
- 美化七问已由 glm-5.3-flash:max 逐图裁定(docs/research/08 §6)
|
||||||
|
|
||||||
|
### 2.2 关键假设
|
||||||
|
- nginx sudo 与 ESA 控制台由用户操作(本包只备好仓库侧与操作单)
|
||||||
|
- 视觉裁定为终态依据;实现与裁定冲突时以裁定为准并在完成总结记录
|
||||||
|
|
||||||
|
### 2.3 待确认问题
|
||||||
|
- [x] 美化两案之争 → 视觉模型裁定完成(08 文档 §6),无悬案
|
||||||
|
- [x] 本批范围 → 用户 2026-10-26 全批(A/B/C/E+K-Q;R 热力图暗色与 S 并入 N 后的下一批)
|
||||||
|
|
||||||
|
### 2.4 可选解释与取舍
|
||||||
|
- 纸面板=全边框圆角卡(裁定)优于通栏 hairline——保留标题区外置结构
|
||||||
|
- 代码块窗条仅 ≥4 行块;三点换灰阶(红线优先于 macOS 梗)
|
||||||
|
- 图库亮色直贴/暗色 4% 衬底(双模式分判)
|
||||||
|
|
||||||
|
### 2.5 已证伪路径
|
||||||
|
- 首屏 banner 化/大字标语(视觉裁定否决——克制红线)
|
||||||
|
- 热力图亮色换绿(裁定否决——全页最大色块压灰阶骨架)
|
||||||
|
- 页面标题 27→22px(裁定否决——层级锚点,要轻减字重不减号)
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 核心功能(MVP)
|
||||||
|
- [x] T1 SEO 仓库侧三件:robots.txt 新建 + /admin 局部 noindex 预案 + 用户操作单(nginx/ESA)
|
||||||
|
- [x] T2 localePath 双 en 修复 + 自指 canonical + 尾斜杠归一
|
||||||
|
- [x] T3 构建期内容 lint(断链/图片/frontmatter/双语配对)+ 生产冒烟脚本 + build.sh 守卫挂载
|
||||||
|
- [x] T4 reader 纯文本模式(html.reader 单 class + 命令面板 reader + r 键 + localStorage)
|
||||||
|
- [x] T5 代码块窗条(P 卡:≥4 行灰阶窗+mono 标题+灰阶三点,单行裸 pill)
|
||||||
|
- [x] T6 书评纸面板(Q 卡:圆角米灰纸卡+暗色暖棕+hairline 边框防长文竖线感)
|
||||||
|
- [x] T7 版式基底(K/L/O 卡:正文流距 token 化+灰阶补档+暗色 hairline 降档+微交互曲线统一+focus ring 收尾)
|
||||||
|
- [x] T8 首屏 tagline + 图库质感(M/N 卡:IdCard 身份语≤20 字+图库圆角统一+暗色 4% 衬底)
|
||||||
|
|
||||||
|
### 3.2 扩展功能
|
||||||
|
- 无(R 热力图暗色补救列下一批)
|
||||||
|
|
||||||
|
### 3.3 不在范围内
|
||||||
|
- nginx/ESA 实际操作(用户侧);draft 路由 D、评论数 F、EXIF 卡 G、title H(本月批后续包)
|
||||||
|
|
||||||
|
## 4. 最小实现路径
|
||||||
|
- 最简单可行方案:token 值级 CSS 改动为主 + locale.ts 单函数修 + 两个构建脚本;无新依赖
|
||||||
|
- 暂不引入:blur-up 占位(需管线)、独立设置面板
|
||||||
|
- 不做的抽象:主题引擎化/样式体系重构(tokens 增量演化)
|
||||||
|
|
||||||
|
## 5. 技术决策
|
||||||
|
- 技术栈:既有 Astro+tokens 体系;lint 用 node 脚本(复用 build-perf 目录与守卫模式)
|
||||||
|
- 证据协议:evidence: runs
|
||||||
|
- 本轮允许改动:src/lib/locale.ts、src/layouts/BaseLayout.astro、src/styles/{tokens,global}.css、src/components/{CommandPalette,IdCard*}.astro、src/components/geek/Keyboard.astro、GalleryPage.astro、prose 相关组件、public/robots.txt、scripts/build-perf/、infra/deploy/build.sh、tests/e2e.mjs
|
||||||
|
- 本轮不应触碰:nginx/ESA(用户)、Header/Footer 结构、sw.js(无涉)、热力图组件
|
||||||
|
- Git integration branch:`spec/2026-10-26_blog-5f`
|
||||||
|
|
||||||
|
### 5.1 验证策略
|
||||||
|
- 范围级别:package
|
||||||
|
- 变更对象:SEO 基建文件、locale 工具、构建链 lint 挂载、reader/美化涉及的样式与组件
|
||||||
|
- 快速检查:`pnpm astro build`
|
||||||
|
- 集成检查:`node tests/e2e.mjs`(33 项)+ 本包新增断言(canonical/localePath/reader/窗条/纸面板 computed-style)
|
||||||
|
- 全项目检查:生产 Lighthouse SEO≥95 复测(依赖用户 nginx/ESA 操作单完成)+ 移动溢出密集扫描;仅在 nginx/ESA 面、构建链或 Header 布局再变更时升级范围
|
||||||
|
- 升级触发:T7 灰阶补档若波及 Header/导航布局,或用户操作单完成后生产复测不达标,升级为全站密集扫描与 LH 多轮中位并记录理由
|
||||||
|
|
||||||
|
## 6. 成功标准与验证方式
|
||||||
|
- [x] dist 每页含自指 canonical 且 en 页无 /en/en/ -> verify: task_0002
|
||||||
|
- [x] robots.txt 产物存在且含 Sitemap 行 -> verify: task_0001
|
||||||
|
- [x] 人为断链能让 build 输出 FAIL 清单(warn 一周策略首版即 fail 可议) -> verify: task_0003
|
||||||
|
- [x] reader 触发后 .bg-glow 隐藏/ICP 可见/刷新保持 -> verify: task_0004
|
||||||
|
- [x] 多行代码块有窗条与灰阶三点、单行无 -> verify: task_0005
|
||||||
|
- [x] 书评正文为圆角纸卡(computed border-radius+背景) -> verify: task_0006
|
||||||
|
- [x] 流距 token 生效(h2 前后距为变量值)+focus ring 统一 -> verify: task_0007
|
||||||
|
- [x] 首屏 tagline 渲染≤20 字+图库圆角统一 -> verify: task_0008
|
||||||
|
|
||||||
|
## 7. 风险与约束
|
||||||
|
- 灰阶红线:所有美化以 08 §6 裁定为终态依据
|
||||||
|
- build.sh 引用新脚本守卫先行(车道纪律);lint 首版 fail 可降 warn 一周(发布链安全优先)
|
||||||
|
- 多车道并行:T2/T3(op-gitea)、T5/T6(op-artalk)、T1/T4/T7/T8(orchestrator);共享文件按 boundary 划界,global.css/tokens 仅 orchestrator 动
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Phase 5f:SEO 解封与功能优化批 + 美化首批 - 任务拆解
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
|
||||||
|
|
||||||
|
## 阶段一:实现
|
||||||
|
|
||||||
|
- [x] T1 SEO 仓库侧三件(robots.txt + admin noindex 预案 + 用户操作单)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: 新建 public/robots.txt(Allow 全站;Disallow /admin/ /draft/;Sitemap 行);docs/上线检查单.md 追加「SEO 解封操作单」节(nginx snippet 行号/内部 vhost 补回清单/ESA purge+TTL+绕缓存三动作);nginx/ESA 实操归用户
|
||||||
|
- verify: `test -f public/robots.txt && grep -q "Sitemap: https://wait4x.online/sitemap-index.xml" public/robots.txt && grep -q "Disallow: /admin" public/robots.txt && grep -q "SEO 解封操作单" docs/上线检查单.md`
|
||||||
|
|
||||||
|
- [x] T2 localePath 双 en 修复 + 自指 canonical + 尾斜杠归一
|
||||||
|
- claim: op-gitea(mesh 车道)
|
||||||
|
- boundary: src/lib/locale.ts(先归一 zh 再拼前缀);BaseLayout.astro 加自指 canonical(Astro.site+withSlash);Header.astro nav-lang href 尾斜杠补齐;E2E 可加断言
|
||||||
|
- verify: `! grep -r "/en/en/" dist/ && grep -q 'rel="canonical"' dist/en/archives/mian-sha/index.html && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS"`
|
||||||
|
|
||||||
|
- [x] T3 构建期内容 lint + 生产冒烟 + build.sh 守卫挂载
|
||||||
|
- claim: op-gitea(mesh 车道)
|
||||||
|
- boundary: 新建 scripts/build-perf/content-lint.mjs(dist 断链:站内 a[href] 逐一对产物;图片存在性;zh/en 配对警告)+ scripts/build-perf/prod-smoke.sh(curl robots/sitemap/首页头三断言);build.sh 守卫行(存在性守卫铁律);首版 fail 但提供 SKIP 环境变量逃生门
|
||||||
|
- verify: `node scripts/build-perf/content-lint.mjs dist && bash -n infra/deploy/build.sh && test -x scripts/build-perf/prod-smoke.sh`
|
||||||
|
|
||||||
|
- [x] T4 reader 纯文本模式(html.reader + 命令面板 + r 键 + 持久化)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: global.css 尾部集中 html.reader 段(按 08 文档 §4.4 清单:隐 bg-glow/进度条/收 Header 尾部钮/Footer 收单行 ICP 必留;正文字号微升行宽收窄);CommandPalette data-cmd="reader" + ui.ts cmd_reader 双语键;Keyboard r 单键;localStorage 持久化;reduced-motion/无 JS 天然兼容(纯 class)
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -q 'data-cmd="reader"' src/components/CommandPalette.astro && grep -q "html.reader" src/styles/global.css`
|
||||||
|
|
||||||
|
- [x] T5 代码块窗条(P 卡:≥4 行灰阶窗+mono 标题+灰阶三点)
|
||||||
|
- claim: op-artalk(mesh 车道)
|
||||||
|
- boundary: 技术轨代码块渲染层(自查源码定落点:prose-tech 容器/全局 pre 样式);≥4 行块加窗条(灰阶三点非红黄绿+mono 语言/文件标签),单行块裸 pill;亮暗双态;scoped 样式优先不动 global.css(与 T7 划界)
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -rqE "code-win|codebar|窗条" src/pages/archives/`
|
||||||
|
|
||||||
|
- [x] T6 书评纸面板(Q 卡:圆角米灰纸卡+暗色暖棕+hairline 边框)
|
||||||
|
- claim: op-artalk(mesh 车道)
|
||||||
|
- boundary: 书评正文容器(prose-reading 所在组件 scoped 样式):圆角纸卡+米灰 #f4f4f2 系亮/暖棕暗态;边框 hairline 级防长文竖线感(08 裁定 §6-2);标题区外置结构不动;--paper 变量需求提给 orchestrator 入 tokens(T7 协同),不直接改 tokens.css
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -rq "paper" src/components/`
|
||||||
|
|
||||||
|
- [x] T7 版式基底(K/L/O 卡:流距 token+灰阶补档+暗色 hairline 降档+微交互曲线+focus ring)
|
||||||
|
- claim: orchestrator(tokens/global 专属车道)
|
||||||
|
- boundary: tokens.css(--flow-* 流距五变量+灰阶补档评估+--paper 纸色变量);global.css(h2/h3/p+p 改用流距变量、暗色 hairline 降一档、过渡曲线统一 token :where 一段、focus-visible ring 统一);标题字重 700→600(裁定 #6);行距 +2~4px(裁定 #7);移动端密集扫描复测(涉布局面)
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -q "flow-h2-pre" src/styles/tokens.css && grep -q "focus-visible" src/styles/global.css`
|
||||||
|
|
||||||
|
- [x] T8 首屏 tagline + 图库质感(M/N 卡:身份语+圆角统一+暗色衬底)
|
||||||
|
- claim: orchestrator
|
||||||
|
- boundary: 首页身份区(IdCard 或等位组件)加 tagline ≤20 字(措辞备两案入完成总结供用户选,默认「写作者 · 读者 · 快门按压者」);GalleryPage 圆角统一 --radius-img 暗色 4% 衬底(裁定 §6-4 双模式分判;衬底圆角≤照片圆角防框中框);亮色直贴不动
|
||||||
|
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "34/34 PASS" && grep -rq "tagline" src/pages/index.astro src/components/ && grep -q "radius-img" src/styles/tokens.css`
|
||||||
@@ -36,3 +36,28 @@ sudo bash infra/deploy/rollback.sh --exec # 秒级回 Halo
|
|||||||
- 新文章工作流:本地或 gitea web 写 md → push main → webhook 自动构建发布
|
- 新文章工作流:本地或 gitea web 写 md → push main → webhook 自动构建发布
|
||||||
- 新照片:上传 content/photos/ 后跑 `python3 scripts/photos_ingest.py` 再 commit
|
- 新照片:上传 content/photos/ 后跑 `python3 scripts/photos_ingest.py` 再 commit
|
||||||
- 翻译:新文章 push 后跑 `python3 scripts/translate/translate_posts.py`(幂等)
|
- 翻译:新文章 push 后跑 `python3 scripts/translate/translate_posts.py`(幂等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SEO 解封操作单(Phase 5f T1,2026-10-26 立案)
|
||||||
|
|
||||||
|
**背景**:生产全站自上线起带 `X-Robots-Tag: noindex`(/etc/nginx/snippets/esa-origin-ssl.conf:11 共享片段误伤博客 vhost)+ robots.txt 404 + ESA 将带 noindex 的响应头缓存 30 天——搜索引擎全站不可见。
|
||||||
|
|
||||||
|
**第一步:nginx(需 sudo)**
|
||||||
|
1. 编辑 `/etc/nginx/snippets/esa-origin-ssl.conf`:删除第 11 行 `add_header X-Robots-Tag noindex always;`(改前备份 .bak)
|
||||||
|
2. 给确实需要 noindex 的内部服务 vhost(api/oj/token/fc/latex/cpa/git)各 server 块补回该行
|
||||||
|
3. 博客 vhost 补局部:`location /admin/ { add_header X-Robots-Tag noindex always; }`
|
||||||
|
4. `sudo nginx -t && sudo systemctl reload nginx`
|
||||||
|
|
||||||
|
**第二步:ESA 控制台(不做则 noindex 再活 30 天)**
|
||||||
|
1. 全站 purge `wait4x.online`
|
||||||
|
2. HTML 边缘 TTL 调 ≤1h(当前 2592000s,靠回源重验证更新——头会固化)
|
||||||
|
3. 加规则:`/artalk/api/*` 绕过缓存(GET stats 有 Age 头实锤,pv 数字会冻)
|
||||||
|
|
||||||
|
**第三步:验证**
|
||||||
|
```bash
|
||||||
|
curl -sI https://wait4x.online/ | grep -ci x-robots-tag # 期望 0
|
||||||
|
curl -s https://wait4x.online/robots.txt | head -3 # 期望 User-agent + Sitemap
|
||||||
|
curl -sI https://wait4x.online/admin/ | grep -ci noindex # 期望 1(局部保留)
|
||||||
|
```
|
||||||
|
仓库侧已就绪:public/robots.txt(本提交);生产 Lighthouse SEO≥95 复测随后入档。
|
||||||
@@ -31,6 +31,9 @@ pnpm astro build
|
|||||||
[ -f scripts/build-perf/gen-sw-precache.mjs ] && node scripts/build-perf/gen-sw-precache.mjs dist || echo "[build] sw-precache 生成器未就绪,跳过"
|
[ -f scripts/build-perf/gen-sw-precache.mjs ] && node scripts/build-perf/gen-sw-precache.mjs dist || echo "[build] sw-precache 生成器未就绪,跳过"
|
||||||
[ -f scripts/build-perf/inline-home-css.mjs ] && node scripts/build-perf/inline-home-css.mjs || echo "[build] inline-home 脚本未就绪,跳过"
|
[ -f scripts/build-perf/inline-home-css.mjs ] && node scripts/build-perf/inline-home-css.mjs || echo "[build] inline-home 脚本未就绪,跳过"
|
||||||
|
|
||||||
|
# 内容 lint(T3):断链/缺图在部署前拦截;CONTENT_LINT=skip|warn 逃生门见脚本头注
|
||||||
|
[ -f scripts/build-perf/content-lint.mjs ] && node scripts/build-perf/content-lint.mjs dist || echo "[build] content-lint 未就绪,跳过"
|
||||||
|
|
||||||
echo "[build] rsync -> $STAGING"
|
echo "[build] rsync -> $STAGING"
|
||||||
mkdir -p "$STAGING"
|
mkdir -p "$STAGING"
|
||||||
rsync -a --delete "$BUILD_REPO/dist/" "$STAGING/"
|
rsync -a --delete "$BUILD_REPO/dist/" "$STAGING/"
|
||||||
@@ -38,3 +41,9 @@ ORIG="$HOME/docker_apps/blog/photos-originals"
|
|||||||
if [ -d "$ORIG" ]; then rsync -a "$ORIG/" "$STAGING/photos-originals/"; fi
|
if [ -d "$ORIG" ]; then rsync -a "$ORIG/" "$STAGING/photos-originals/"; fi
|
||||||
printf 'built %s at %s\n' "$SHA" "$(date -Is)" > "$STAGING/.build-stamp"
|
printf 'built %s at %s\n' "$SHA" "$(date -Is)" > "$STAGING/.build-stamp"
|
||||||
echo "[build] done: $(find "$STAGING" -name '*.html' | wc -l) html files @ $SHA"
|
echo "[build] done: $(find "$STAGING" -name '*.html' | wc -l) html files @ $SHA"
|
||||||
|
|
||||||
|
# 生产冒烟(T3):staging 三断言。首版容忍失败(robots 待 T1 落地,smoke FAIL 属预期),
|
||||||
|
# T1 收编后改硬门:去掉 `|| echo` 让断言失败直接炸构建
|
||||||
|
if [ -f scripts/build-perf/prod-smoke.sh ] && [ "${SMOKE_BASE:-http://127.0.0.1:18743}" != "off" ]; then
|
||||||
|
bash scripts/build-perf/prod-smoke.sh "${SMOKE_BASE:-http://127.0.0.1:18743}" || echo "[build] prod-smoke 未过(见上;T1 robots 落地后转硬门)"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# wait4x.online —— 搜索引擎指引(Phase 5f T1)
|
||||||
|
# 背景:上线期 nginx 共享 snippet 误注全站 noindex,本文件随 SEO 解封三件套落地
|
||||||
|
# (docs/上线检查单.md「SEO 解封操作单」;robots 曾长期 404)
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
Disallow: /admin/
|
||||||
|
Disallow: /draft/
|
||||||
|
|
||||||
|
Sitemap: https://wait4x.online/sitemap-index.xml
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// 构建期内容 lint(T3,零依赖):扫 dist 产物三检——站内断链 / 图片存在 / zh↔en 配对
|
||||||
|
// 用法:node scripts/build-perf/content-lint.mjs [dist目录=dist]
|
||||||
|
// 逃生门:CONTENT_LINT=skip 整体跳过;CONTENT_LINT=warn 只警告不炸构建(首版兼容期)
|
||||||
|
// 口径:断链/缺图 = FAIL(exit 1);配对缺失 = WARN(en 缺页可能是有意的,人工裁决)
|
||||||
|
import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
|
||||||
|
import { join, resolve as pathResolve } from 'node:path';
|
||||||
|
|
||||||
|
const DIST = pathResolve(process.argv[2] ?? 'dist');
|
||||||
|
const MODE = process.env.CONTENT_LINT ?? 'fail';
|
||||||
|
if (MODE === 'skip') {
|
||||||
|
console.log('[content-lint] CONTENT_LINT=skip,整体跳过');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SKIP_PREFIX = ['/admin/', '/photos-originals/']; // admin=CMS 路由;photos-originals=部署期 rsync 叠加层(不在 dist,另有部署链保障)
|
||||||
|
const SKIP_FILE = new Set(['/404.html']); // Astro 404 页不参与配对
|
||||||
|
|
||||||
|
function walk(dir, out = []) {
|
||||||
|
for (const e of readdirSync(dir)) {
|
||||||
|
const p = join(dir, e);
|
||||||
|
if (statSync(p).isDirectory()) walk(p, out);
|
||||||
|
else if (e.endsWith('.html')) out.push(p);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** /x/ → dist/x/index.html;/a.svg → dist/a.svg;找不到即 null */
|
||||||
|
function targetExists(url) {
|
||||||
|
if (SKIP_PREFIX.some((s) => url.startsWith(s))) return true;
|
||||||
|
const p = url.split('#')[0].split('?')[0];
|
||||||
|
if (!p || p === '/') return existsSync(join(DIST, 'index.html'));
|
||||||
|
if (p.endsWith('/')) return existsSync(join(DIST, p, 'index.html'));
|
||||||
|
return existsSync(join(DIST, p)) || existsSync(join(DIST, `${p}.html`)) || existsSync(join(DIST, p, 'index.html'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = walk(DIST);
|
||||||
|
const fails = [];
|
||||||
|
const warns = [];
|
||||||
|
|
||||||
|
for (const f of files) {
|
||||||
|
const html = readFileSync(f, 'utf8');
|
||||||
|
const rel = f.slice(DIST.length) || '/';
|
||||||
|
|
||||||
|
// 站内 <a href>:仅根相对路径;#锚点/mailto/tel/外部/data: 全跳过
|
||||||
|
for (const m of html.matchAll(/<a\s[^>]*href="([^"]*)"/g)) {
|
||||||
|
const href = m[1];
|
||||||
|
if (!href.startsWith('/') || href.startsWith('//')) continue;
|
||||||
|
if (!targetExists(href)) fails.push(`断链 ${rel} → ${href}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// <img src> 与 srcset:产物必须存在(构建期副本检查,防部署后 404 图)
|
||||||
|
for (const m of html.matchAll(/<img\s[^>]*>/g)) {
|
||||||
|
const tag = m[0];
|
||||||
|
const src = /(?:\ssrc="([^"]*)")/.exec(tag)?.[1];
|
||||||
|
const srcset = /(?:\ssrcset="([^"]*)")/.exec(tag)?.[1];
|
||||||
|
for (const u of [src, ...(srcset ?? '').split(',').map((s) => s.trim().split(/\s+/)[0])]) {
|
||||||
|
if (!u || !u.startsWith('/') || u.startsWith('//')) continue;
|
||||||
|
if (!targetExists(u)) fails.push(`缺图 ${rel} → ${u}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// zh↔en 配对:dist/en/x/index.html ↔ dist/x/index.html,双向 WARN
|
||||||
|
if (!SKIP_FILE.has(rel) && !SKIP_PREFIX.some((s) => rel.startsWith(s))) {
|
||||||
|
const other = rel.startsWith('/en/') ? rel.slice(3) : `/en${rel === '/index.html' ? '/index.html' : rel}`;
|
||||||
|
if (!existsSync(join(DIST, other))) warns.push(`配对缺 ${rel} ↔ ${other}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const w of warns) console.warn(`[content-lint] WARN ${w}`);
|
||||||
|
if (fails.length) {
|
||||||
|
for (const x of fails) console.error(`[content-lint] FAIL ${x}`);
|
||||||
|
if (MODE === 'warn') {
|
||||||
|
console.warn(`[content-lint] WARN 模式:${fails.length} 个失败降级为警告`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
console.error(`[content-lint] ${fails.length} 失败 / ${warns.length} 警告`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`[content-lint] 通过:${files.length} 页扫描,0 断链 0 缺图,${warns.length} 配对警告`);
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 生产冒烟(T3):部署后三断言——robots 带 Sitemap / sitemap-index 可达 / 首页无 x-robots-tag
|
||||||
|
# 用法:./prod-smoke.sh [BASE_URL](默认 https://wait4x.online;构建机对 staging 传 http://127.0.0.1:18743)
|
||||||
|
# 任一断言失败 exit 1;robots 缺失常见于 SEO 解封前(T1 未落),会明确报 FAIL 而非静默
|
||||||
|
set -u
|
||||||
|
BASE="${1:-${SMOKE_BASE:-https://wait4x.online}}"
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
robots="$(curl -fsS --max-time 15 "${BASE}/robots.txt" 2>/dev/null)" \
|
||||||
|
&& grep -q 'Sitemap:' <<<"$robots" \
|
||||||
|
&& echo "[smoke] robots.txt 200 含 Sitemap ✓" \
|
||||||
|
|| { echo "[smoke] FAIL robots.txt 缺失/无 Sitemap 行:${BASE}/robots.txt"; fail=1; }
|
||||||
|
|
||||||
|
curl -fsS --max-time 15 -o /dev/null "${BASE}/sitemap-index.xml" \
|
||||||
|
&& echo "[smoke] sitemap-index 200 ✓" \
|
||||||
|
|| { echo "[smoke] FAIL sitemap-index 不可达:${BASE}/sitemap-index.xml"; fail=1; }
|
||||||
|
|
||||||
|
# x-robots-tag 出现即 FAIL(noindex 头会废掉整站收录,SEO 解封的反面)
|
||||||
|
if curl -fsS --max-time 15 -I "${BASE}/" | grep -qi 'x-robots-tag'; then
|
||||||
|
echo "[smoke] FAIL 首页响应头含 x-robots-tag"; fail=1
|
||||||
|
else
|
||||||
|
echo "[smoke] 首页无 x-robots-tag ✓"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $fail
|
||||||
@@ -39,6 +39,10 @@ const navItems = [
|
|||||||
<div id="cmd-commands" class="cmd-commands">
|
<div id="cmd-commands" class="cmd-commands">
|
||||||
<p class="cmd-group" role="presentation" id="cmd-h-actions">{t('cmd_group_actions', locale)}</p>
|
<p class="cmd-group" role="presentation" id="cmd-h-actions">{t('cmd_group_actions', locale)}</p>
|
||||||
<ul class="cmd-ul" role="group" aria-labelledby="cmd-h-actions">
|
<ul class="cmd-ul" role="group" aria-labelledby="cmd-h-actions">
|
||||||
|
<li class="cmd-item" role="option" aria-selected="false" data-cmd="reader">
|
||||||
|
<span class="cmd-label">{t('cmd_reader', locale)}</span>
|
||||||
|
<span class="cmd-kbd" aria-hidden="true">r</span>
|
||||||
|
</li>
|
||||||
<li class="cmd-item" role="option" aria-selected="false" data-cmd="theme">
|
<li class="cmd-item" role="option" aria-selected="false" data-cmd="theme">
|
||||||
<span class="cmd-label">{t('cmd_toggle_theme', locale)}</span>
|
<span class="cmd-label">{t('cmd_toggle_theme', locale)}</span>
|
||||||
<span class="cmd-kbd" aria-hidden="true">t</span>
|
<span class="cmd-kbd" aria-hidden="true">t</span>
|
||||||
@@ -134,6 +138,12 @@ const navItems = [
|
|||||||
}
|
}
|
||||||
switch (item.dataset.cmd) {
|
switch (item.dataset.cmd) {
|
||||||
// 复用 Header 主题钮的现成切换(同一 localStorage key),不另起第二套实现
|
// 复用 Header 主题钮的现成切换(同一 localStorage key),不另起第二套实现
|
||||||
|
case 'reader': {
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.classList.toggle('reader');
|
||||||
|
localStorage.setItem('reader', root.classList.contains('reader') ? '1' : '0');
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'theme': {
|
case 'theme': {
|
||||||
const btn = document.querySelector('[data-theme-toggle]') as HTMLElement | null;
|
const btn = document.querySelector('[data-theme-toggle]') as HTMLElement | null;
|
||||||
if (btn) {
|
if (btn) {
|
||||||
|
|||||||
@@ -14,4 +14,6 @@
|
|||||||
: (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
: (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
document.documentElement.dataset.theme = theme;
|
document.documentElement.dataset.theme = theme;
|
||||||
})();
|
})();
|
||||||
|
// reader 模式预绘(5f T4):与主题同点位读取,避免可交互前闪装饰
|
||||||
|
if (localStorage.getItem('reader') === '1') document.documentElement.classList.add('reader');
|
||||||
</script>
|
</script>
|
||||||
@@ -65,6 +65,13 @@ const routes: Record<string, string> = {
|
|||||||
if (inField() || e.metaKey || e.ctrlKey || e.altKey) return;
|
if (inField() || e.metaKey || e.ctrlKey || e.altKey) return;
|
||||||
|
|
||||||
if (e.key === 't') { toggleTheme(); return; }
|
if (e.key === 't') { toggleTheme(); return; }
|
||||||
|
if (e.key === 'r') {
|
||||||
|
// reader 纯文本模式(5f T4):与命令面板/持久化同源
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.classList.toggle('reader');
|
||||||
|
localStorage.setItem('reader', root.classList.contains('reader') ? '1' : '0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (e.key === '?') {
|
if (e.key === '?') {
|
||||||
(document.getElementById('kbd-help') as HTMLDialogElement | null)?.showModal();
|
(document.getElementById('kbd-help') as HTMLDialogElement | null)?.showModal();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -429,3 +429,12 @@ const first = cards[0];
|
|||||||
.lb-meta { order: 3; flex-basis: 100%; }
|
.lb-meta { order: 3; flex-basis: 100%; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 5f T8(视觉裁定 §6-4):亮色直贴不动;暗色 4% 轻衬底救夜景融底;衬底圆角≤照片防框中框 */
|
||||||
|
:global([data-theme='dark']) .stage {
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.shot img, .thumb img { border-radius: var(--radius-img); }
|
||||||
|
</style>
|
||||||
@@ -10,6 +10,7 @@ const dict = {
|
|||||||
nav_posts: { zh: '文章', en: 'Posts' },
|
nav_posts: { zh: '文章', en: 'Posts' },
|
||||||
nav_gallery: { zh: '图库', en: 'Gallery' },
|
nav_gallery: { zh: '图库', en: 'Gallery' },
|
||||||
nav_about: { zh: '关于', en: 'About' },
|
nav_about: { zh: '关于', en: 'About' },
|
||||||
|
cmd_reader: { zh: '阅读模式(纯文本)', en: 'Reader mode (plain text)' },
|
||||||
nav_opensource: { zh: '开源', en: 'Projects' }, // en 用短词:390-430px 尾部主题钮免内滚(用户拍板)
|
nav_opensource: { zh: '开源', en: 'Projects' }, // en 用短词:390-430px 尾部主题钮免内滚(用户拍板)
|
||||||
nav_books: { zh: '书架', en: 'Books' },
|
nav_books: { zh: '书架', en: 'Books' },
|
||||||
kbd_gs_books: { zh: '书架', en: 'Books' },
|
kbd_gs_books: { zh: '书架', en: 'Books' },
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const rawPath = Astro.url.pathname.replace(/\/index\.html$/, '');
|
|||||||
const zhPath = localeOf(rawPath) === 'en' ? localePath(rawPath, 'zh') : rawPath;
|
const zhPath = localeOf(rawPath) === 'en' ? localePath(rawPath, 'zh') : rawPath;
|
||||||
const enPath = localePath(rawPath, 'en');
|
const enPath = localePath(rawPath, 'en');
|
||||||
const withSlash = (p: string) => (p.endsWith('/') ? p : `${p}/`);
|
const withSlash = (p: string) => (p.endsWith('/') ? p : `${p}/`);
|
||||||
|
// 自指 canonical(T2):SEO 权重收敛到当前页自身,与 hreflang 组合覆盖双轨
|
||||||
|
const canonicalUrl = new URL(withSlash(rawPath), Astro.site).href;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -87,6 +89,7 @@ const withSlash = (p: string) => (p.endsWith('/') ? p : `${p}/`);
|
|||||||
addEventListener('load', () => navigator.serviceWorker.register('/sw.js'));
|
addEventListener('load', () => navigator.serviceWorker.register('/sw.js'));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<link rel="canonical" href={canonicalUrl} />
|
||||||
<link rel="alternate" hreflang="zh" href={new URL(withSlash(zhPath), Astro.site)} />
|
<link rel="alternate" hreflang="zh" href={new URL(withSlash(zhPath), Astro.site)} />
|
||||||
<link rel="alternate" hreflang="en" href={new URL(withSlash(enPath), Astro.site)} />
|
<link rel="alternate" hreflang="en" href={new URL(withSlash(enPath), Astro.site)} />
|
||||||
<link rel="alternate" hreflang="x-default" href={new URL(withSlash(zhPath), Astro.site)} />
|
<link rel="alternate" hreflang="x-default" href={new URL(withSlash(zhPath), Astro.site)} />
|
||||||
|
|||||||
+6
-3
@@ -7,9 +7,12 @@ export type Locale = 'zh' | 'en';
|
|||||||
export const localeOf = (pathname: string): Locale =>
|
export const localeOf = (pathname: string): Locale =>
|
||||||
pathname === '/en' || pathname.startsWith('/en/') ? 'en' : 'zh';
|
pathname === '/en' || pathname.startsWith('/en/') ? 'en' : 'zh';
|
||||||
|
|
||||||
/** 当前路径在另一语言下的对应路径(zh → /en/...,en → 去 /en 前缀)。 */
|
/** 当前路径在另一语言下的对应路径(zh → /en/...,en → 去 /en 前缀)。
|
||||||
|
* 尾斜杠归一:返回值一律带尾斜杠(与全站/sitemap 口径一致)。
|
||||||
|
* 先归一 zh 再拼前缀——en→en 场景若直接 /en+clean 会产出 /en/en/(T2 病根) */
|
||||||
export function localePath(pathname: string, to: Locale): string {
|
export function localePath(pathname: string, to: Locale): string {
|
||||||
const clean = pathname.replace(/\/index\.html$/, '').replace(/\/$/, '') || '/';
|
const clean = pathname.replace(/\/index\.html$/, '').replace(/\/$/, '') || '/';
|
||||||
if (to === 'en') return clean === '/' ? '/en/' : `/en${clean}`;
|
const zh = clean.replace(/^\/en(?=\/|$)/, '') || '/';
|
||||||
return clean.replace(/^\/en(?=\/|$)/, '') || '/';
|
const slash = (p: string) => (p === '/' ? '/' : `${p}/`);
|
||||||
|
return to === 'en' ? slash(zh === '/' ? '/en' : `/en${zh}`) : slash(zh);
|
||||||
}
|
}
|
||||||
@@ -133,6 +133,24 @@ const toc = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||||||
addEventListener('scroll', paint, { passive: true });
|
addEventListener('scroll', paint, { passive: true });
|
||||||
document.addEventListener('astro:page-load', paint);
|
document.addEventListener('astro:page-load', paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 代码块窗条标记(P 卡 §6-3):纯 CSS 无法按内容行数条件挂伪元素,
|
||||||
|
// 此处数行加类;语言标签取 shiki 的 language-* 类名写入 data-lang 供 CSS attr() 渲染
|
||||||
|
const markCodeWins = () => {
|
||||||
|
document.querySelectorAll('.prose-tech pre:not(.code-win)').forEach((pre) => {
|
||||||
|
const code = pre.querySelector('code');
|
||||||
|
if (!code) return;
|
||||||
|
const lines = (code.textContent ?? '').split('\n').length;
|
||||||
|
if (lines >= 4) {
|
||||||
|
// astro shiki 语言落 pre[data-language](code 类名为空)
|
||||||
|
const lang = pre.dataset.language ?? /language-([\w-]+)/.exec(code.className)?.[1] ?? 'text';
|
||||||
|
pre.dataset.lang = lang;
|
||||||
|
pre.classList.add('code-win');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
document.addEventListener('astro:page-load', markCodeWins);
|
||||||
|
markCodeWins();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -277,4 +295,47 @@ const toc = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||||||
}
|
}
|
||||||
.pn-item:hover strong { color: var(--accent); }
|
.pn-item:hover strong { color: var(--accent); }
|
||||||
#artalk-container { margin-top: var(--space-5); }
|
#artalk-container { margin-top: var(--space-5); }
|
||||||
|
/* ---- T5 代码块窗条(08 裁定 §6-3):≥4 行挂窗条,单行块维持现状裸块 ---- */
|
||||||
|
.prose-tech :global(pre.code-win) {
|
||||||
|
position: relative;
|
||||||
|
padding-top: calc(var(--space-3) + 30px);
|
||||||
|
}
|
||||||
|
.prose-tech :global(pre.code-win::before) {
|
||||||
|
content: attr(data-lang);
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 auto 0;
|
||||||
|
height: calc(var(--space-3) + 30px - 1px);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 0 14px 8px 66px;
|
||||||
|
background: #161b22; /* code-bg 上一档:恒深区亮暗同判(§6-3 暗色收益最大) */
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #6e7681;
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
/* 灰阶三点(§6-3 裁定弃红黄绿):terminal 页同款点色,伪元素本体一点+投影两点 */
|
||||||
|
.prose-tech :global(pre.code-win::after) {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 14px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #30363d;
|
||||||
|
box-shadow: 18px 0 0 #30363d, 36px 0 0 #30363d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- T6 书评纸卡(08 裁定 §6-2):通栏 hairline 升圆角纸卡 ----
|
||||||
|
--paper 三态已入 tokens(亮米白/暗暖棕夜灯),零新变量;7000px 长文
|
||||||
|
左右边框恒为 1px hairline 防两根竖线感(§6-2 落地注意) */
|
||||||
|
.prose-reading {
|
||||||
|
border: 1px solid var(--paper-line);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: var(--space-5) clamp(20px, 4vw, 32px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -78,4 +78,64 @@ const minutes = Math.max(1, Math.ceil(words / 220));
|
|||||||
.pn { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-5); }
|
.pn { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-5); }
|
||||||
.pn-item { font-size: 14px; color: var(--ink-2); transition: color var(--dur) var(--ease); }
|
.pn-item { font-size: 14px; color: var(--ink-2); transition: color var(--dur) var(--ease); }
|
||||||
.pn-item:hover { color: var(--accent); }
|
.pn-item:hover { color: var(--accent); }
|
||||||
|
|
||||||
|
/* ---- T5 code window bar (§6-3): ≥4-line blocks only ---- */
|
||||||
|
.prose-tech :global(pre.code-win) {
|
||||||
|
position: relative;
|
||||||
|
padding-top: calc(var(--space-3) + 30px);
|
||||||
|
}
|
||||||
|
.prose-tech :global(pre.code-win::before) {
|
||||||
|
content: attr(data-lang);
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 auto 0;
|
||||||
|
height: calc(var(--space-3) + 30px - 1px);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 0 14px 8px 66px;
|
||||||
|
background: #161b22;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #6e7681;
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
.prose-tech :global(pre.code-win::after) {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 14px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #30363d;
|
||||||
|
box-shadow: 18px 0 0 #30363d, 36px 0 0 #30363d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- T6 reading paper card (§6-2) ---- */
|
||||||
|
.prose-reading {
|
||||||
|
border: 1px solid var(--paper-line);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: var(--space-5) clamp(20px, 4vw, 32px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Code window bar marker (same as zh track): count lines, tag .code-win + data-lang
|
||||||
|
const markCodeWins = () => {
|
||||||
|
document.querySelectorAll('.prose-tech pre:not(.code-win)').forEach((pre) => {
|
||||||
|
const code = pre.querySelector('code');
|
||||||
|
if (!code) return;
|
||||||
|
const lines = (code.textContent ?? '').split('\n').length;
|
||||||
|
if (lines >= 4) {
|
||||||
|
// astro shiki 语言落 pre[data-language](code 类名为空)
|
||||||
|
const lang = pre.dataset.language ?? /language-([\w-]+)/.exec(code.className)?.[1] ?? 'text';
|
||||||
|
pre.dataset.lang = lang;
|
||||||
|
pre.classList.add('code-win');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
document.addEventListener('astro:page-load', markCodeWins);
|
||||||
|
markCodeWins();
|
||||||
|
</script>
|
||||||
@@ -19,7 +19,8 @@ const heartbeat = await fetchHeartbeat();
|
|||||||
|
|
||||||
<BaseLayout title="wait4x">
|
<BaseLayout title="wait4x">
|
||||||
<IdCard>
|
<IdCard>
|
||||||
<div class="socials">
|
<p class="tagline">写作者 · 读者 · 快门按压者,拥抱开源。</p>
|
||||||
|
<div class="socials">
|
||||||
<a
|
<a
|
||||||
class="tap social"
|
class="tap social"
|
||||||
href="https://github.com/wait4xx"
|
href="https://github.com/wait4xx"
|
||||||
@@ -110,6 +111,12 @@ const heartbeat = await fetchHeartbeat();
|
|||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.tagline {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--ink-3);
|
||||||
|
margin-top: 4px;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
.heartbeat {
|
.heartbeat {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 12.5px;
|
font-size: 12.5px;
|
||||||
|
|||||||
@@ -219,3 +219,29 @@ hr { border: none; border-top: 1px solid var(--line); margin-block: var(--space-
|
|||||||
transition-duration: 0.01ms !important;
|
transition-duration: 0.01ms !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== Phase 5f T7:流距变量应用(标题前后距/p+p 统一双轨) ===== */
|
||||||
|
.prose-reading h2, .prose-tech h2 { margin-top: var(--flow-h2-pre); margin-bottom: var(--flow-h2-post); }
|
||||||
|
.prose-reading h3, .prose-tech h3 { margin-top: var(--flow-h3-pre); margin-bottom: var(--flow-h3-post); }
|
||||||
|
.prose-reading p + p, .prose-tech p + p { margin-top: var(--flow-p); }
|
||||||
|
/* 页面标题字重 700→600(视觉裁定 #6:要轻减字重不减号;!important 压组件 scoped 默认) */
|
||||||
|
h1.page-title { font-weight: 600 !important; }
|
||||||
|
/* focus ring 收尾(O 卡:统一可键盘巡览性) */
|
||||||
|
a:focus-visible, button:focus-visible, summary:focus-visible {
|
||||||
|
outline: 2px solid var(--ink-3); outline-offset: 2px; border-radius: 4px;
|
||||||
|
}
|
||||||
|
/* 列表行距微增(裁定 #7:行距 +2~4px 优先于删线) */
|
||||||
|
.postlist .row { padding-block: calc(var(--space-2) + 2px); }
|
||||||
|
|
||||||
|
/* ===== Phase 5f T4:reader 纯文本模式(08 文档 §4.4 清单,视觉语义=去装饰非去内容) ===== */
|
||||||
|
html.reader .bg-glow { display: none !important; }
|
||||||
|
html.reader .site-header .nav-actions { display: none; } /* 收图标钮:保品牌+导航词 */
|
||||||
|
html.reader .site-footer .fine > *:not(a[href*="beian"]):not(.icp-sep) { display: none; }
|
||||||
|
html.reader .atk-pv-line { display: none; } /* 元信息让位正文 */
|
||||||
|
html.reader .prose-reading, html.reader .prose-tech {
|
||||||
|
font-size: calc(var(--prose-size-reading, 1.05rem) + 0.0625rem);
|
||||||
|
max-width: 56ch; /* 行宽收窄 */
|
||||||
|
}
|
||||||
|
html.reader #main > * { animation: none; } /* 入场动效关闭 */
|
||||||
|
/* ICP 备案链接永不隐藏(合规底线);分隔点号随之保留必要最小集 */
|
||||||
+16
-1
@@ -27,6 +27,20 @@
|
|||||||
|
|
||||||
/* ---- 书评情绪色(frontmatter mood 覆盖,此处为缺省五档) ---- */
|
/* ---- 书评情绪色(frontmatter mood 覆盖,此处为缺省五档) ---- */
|
||||||
--mood: var(--ink-3);
|
--mood: var(--ink-3);
|
||||||
|
|
||||||
|
/* ===== Phase 5f 版式基底(T7,08 文档 K/L 卡 + 视觉裁定) ===== */
|
||||||
|
/* 正文流距(rauno 式节奏:标题前后距变量化,双轨共用) */
|
||||||
|
--flow-p: 0.9em;
|
||||||
|
--flow-h2-pre: 1.8em;
|
||||||
|
--flow-h2-post: 0.6em;
|
||||||
|
--flow-h3-pre: 1.5em;
|
||||||
|
--flow-h3-post: 0.45em;
|
||||||
|
/* 纸面板(Q 卡/T6 书评轨:亮米灰/暗暖棕——视觉裁定 §6-2 夜灯感已验) */
|
||||||
|
--paper: #f4f4f2;
|
||||||
|
/* 图片圆角统一(N 卡:现 8/8/8/10 混用) */
|
||||||
|
--radius-img: 10px;
|
||||||
|
/* 微交互曲线统一(O 卡:rauno 全站唯一曲线) */
|
||||||
|
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
--mood-1: #b45309; /* 琥珀 */
|
--mood-1: #b45309; /* 琥珀 */
|
||||||
--mood-2: #0f766e; /* 松绿 */
|
--mood-2: #0f766e; /* 松绿 */
|
||||||
--mood-3: #7c3aed; /* 紫晶 */
|
--mood-3: #7c3aed; /* 紫晶 */
|
||||||
@@ -89,8 +103,9 @@
|
|||||||
--ink-1: #f5f5f7;
|
--ink-1: #f5f5f7;
|
||||||
--ink-2: #c9ccd4;
|
--ink-2: #c9ccd4;
|
||||||
--ink-3: #8b8f98;
|
--ink-3: #8b8f98;
|
||||||
|
--paper: #201d18; /* 暖棕夜灯(Q 卡视觉裁定) */
|
||||||
--ink-4: #5c6066;
|
--ink-4: #5c6066;
|
||||||
--line: #2c313a;
|
--line: #262b33; /* 5f 降一档:暗色切分感偏强(裁定 #7) */
|
||||||
|
|
||||||
--accent: #8ea2ff;
|
--accent: #8ea2ff;
|
||||||
--accent-soft: rgba(142, 162, 255, 0.18);
|
--accent-soft: rgba(142, 162, 255, 0.18);
|
||||||
|
|||||||
+15
-4
@@ -7,7 +7,7 @@
|
|||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
import { mkdirSync } from 'node:fs';
|
import { mkdirSync } from 'node:fs';
|
||||||
|
|
||||||
const BASE = 'http://127.0.0.1:18743';
|
const BASE = process.env.E2E_BASE ?? 'http://127.0.0.1:18743'; // 5f:本地 dist 口可覆写(staging=origin/main 纪律下验未推代码)
|
||||||
const SHOTS = '/tmp/e2e-shots';
|
const SHOTS = '/tmp/e2e-shots';
|
||||||
mkdirSync(SHOTS, { recursive: true });
|
mkdirSync(SHOTS, { recursive: true });
|
||||||
|
|
||||||
@@ -86,6 +86,8 @@ const enNavBad = await page.$$eval('nav a[href^="/"]:not(.nav-lang)', (as) => as
|
|||||||
ok('en 导航全轨 /en/ 前缀', enNavBad.length === 0 && enNavHrefs.length >= 6 && enNavHrefs.includes('/en/books/'), enNavBad.join(' ; ') || enNavHrefs.join(' '));
|
ok('en 导航全轨 /en/ 前缀', enNavBad.length === 0 && enNavHrefs.length >= 6 && enNavHrefs.includes('/en/books/'), enNavBad.join(' ; ') || enNavHrefs.join(' '));
|
||||||
const enBrand = await page.getAttribute('a.brand', 'href');
|
const enBrand = await page.getAttribute('a.brand', 'href');
|
||||||
ok('en 品牌链接指 /en/', enBrand === '/en/', String(enBrand));
|
ok('en 品牌链接指 /en/', enBrand === '/en/', String(enBrand));
|
||||||
|
const enCanon = await page.getAttribute('link[rel="canonical"]', 'href');
|
||||||
|
ok('en 页 canonical 自指', !!enCanon && new URL(enCanon).pathname === new URL(page.url()).pathname, enCanon ?? '无 canonical');
|
||||||
|
|
||||||
// 4) 命令面板 + 搜索
|
// 4) 命令面板 + 搜索
|
||||||
await page.keyboard.press('Control+k');
|
await page.keyboard.press('Control+k');
|
||||||
@@ -99,9 +101,18 @@ ok('命令面板·Pagefind 搜索', hasResult);
|
|||||||
await page.keyboard.press('Escape');
|
await page.keyboard.press('Escape');
|
||||||
|
|
||||||
// 5) 键盘帮助与 g-chords
|
// 5) 键盘帮助与 g-chords
|
||||||
await page.keyboard.press('?');
|
// 加固:面板测试后焦点可能残留输入域(? 被吞成字符)——Esc 收场+blur+重试三连
|
||||||
await page.waitForTimeout(300);
|
await page.keyboard.press('Escape');
|
||||||
ok('键盘 ? 帮助浮层', await page.locator('#kbd-help[open]').count() > 0);
|
await page.evaluate(() => document.activeElement?.blur?.());
|
||||||
|
await page.waitForTimeout(150);
|
||||||
|
let helpOpen = 0;
|
||||||
|
for (let i = 0; i < 3 && !helpOpen; i++) {
|
||||||
|
await page.keyboard.press('?');
|
||||||
|
await page.waitForTimeout(250);
|
||||||
|
helpOpen = await page.locator('#kbd-help[open]').count();
|
||||||
|
if (!helpOpen) { await page.keyboard.press('Escape'); await page.waitForTimeout(150); }
|
||||||
|
}
|
||||||
|
ok('键盘 ? 帮助浮层', helpOpen > 0);
|
||||||
await page.keyboard.press('Escape');
|
await page.keyboard.press('Escape');
|
||||||
await page.keyboard.press('g');
|
await page.keyboard.press('g');
|
||||||
await page.keyboard.press('g');
|
await page.keyboard.press('g');
|
||||||
|
|||||||
Reference in new issue
Block a user