Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6293fe25d5 | ||
|
|
8d82aab213 | ||
|
|
029633f00f | ||
|
|
0ff4ede91f | ||
|
|
23303bd39a | ||
|
|
31eb44caee | ||
|
|
5c015c6577 | ||
|
|
138d3d976b | ||
|
|
d30b5decbb | ||
|
|
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 | ||
|
|
5405e33f81 | ||
|
|
e185a292c8 | ||
|
|
4aa79fca06 | ||
|
|
c74815b516 | ||
|
|
cf42081614 |
No files matched your search
@@ -0,0 +1,92 @@
|
||||
# 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 待确认问题
|
||||
- [ ] 字体扩展款式(用户选:宋系 / 楷系 / 手写系 / 现代黑系各补几款)
|
||||
- [ ] 书架重做方向(A 色脊墙精修 / B 封面网格 / C 书脊展开卡)
|
||||
- [ ] README 动效图确认「构建期抓取 + 自托管」路线
|
||||
|
||||
### 2.4 可选解释与取舍
|
||||
- 字体:只按需加载,不做「全站全字族常开」(那会把单页体积拉到数 MB)
|
||||
- README 动效:自托管后仍保留原服务生成逻辑(GitHub Action / 第三方接口),只是把产物入仓并在构建期更新
|
||||
- 彩色限制取消后仍保留「灰阶外壳」红线(导航/页脚/按钮/背景不染色),只取消正文次数限制
|
||||
|
||||
### 2.5 已证伪路径
|
||||
- 「字族没做」判断错误:@font-face 与切片文件都在,问题在加载门禁(本轮实测推翻)
|
||||
- 外链 README 图直接保留:既有 502/超时与外链依赖问题(上一阶段已判弃),改自托管
|
||||
|
||||
## 3. 功能范围
|
||||
|
||||
### 3.1 核心功能(MVP)
|
||||
- [ ] T1 字族按需加载根治 + 字族扩展机制:BaseLayout 由「轨道门禁」改为「内容诉求驱动」,扫描正文与页面文案中的 `font-*` 类决定加载哪些字体 CSS;补字体扩展清单与接入流程
|
||||
- [ ] T2 Artalk 误报与配色同步:失败提示改为「真失败才提示」(脚本 onerror / 库未就绪超时且未挂载才提示),重置挂载状态机;主题变量覆盖全部 Artalk 实例容器(含瞬间逐条)
|
||||
- [ ] T3 灯箱:右上关闭恢复可点(层级与命中区域),右侧信息栏文字支持 markdown + 排版类(字体/颜色)
|
||||
- [ ] T4 取消彩色强调次数限制(文档 + 后台 hint + 示例文章同步)
|
||||
- [ ] T5 开源页项目墙恢复居中排版
|
||||
- [ ] T6 书架页重做(按用户选定方向)
|
||||
- [ ] 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
|
||||
- 全项目检查:生产实测五条(字体真加载 `document.fonts` 命中、灯箱点关闭真关、评论失败提示不再误报、瞬间评论区配色与页面一致、开源页动效图零外链);**升级条件**——仅当改动触及 BaseLayout 字体策略或 Artalk 挂载状态机时升级为全站视觉复核,本包因两者都动,已排一轮视觉终审
|
||||
|
||||
## 6. 成功标准与验证方式
|
||||
- [ ] 技术轨文章里的 `.font-kai/.font-song/.font-huiwen/.font-kuaile` 真加载对应字体(`document.fonts` 命中且渲染变化) -> verify: task_0001
|
||||
- [ ] 评论失败提示只在真失败时出现,正常挂载不提示 -> verify: task_0002
|
||||
- [ ] 瞬间评论区配色与页面主题一致(明暗双态) -> verify: task_0003
|
||||
- [ ] 灯箱右上关闭按钮点击必关(含缩放/信息栏展开态) -> verify: task_0004
|
||||
- [ ] 灯箱右侧文字支持字体与颜色类 -> verify: task_0005
|
||||
- [ ] 彩色强调不再有限次约束且文档同步 -> verify: task_0006
|
||||
- [ ] 开源页项目墙内容居中 -> verify: task_0007
|
||||
- [ ] 书架页信息密度与视觉达设计稿并过视觉终审 -> verify: task_0008
|
||||
- [ ] 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 "FONT_CSS\|neededFonts" 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/MomentComments.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`
|
||||
|
||||
- [ ] 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"`
|
||||
|
||||
- [ ] T9 字族扩展(款式待用户拍板)
|
||||
- claim: worker-one
|
||||
- boundary: src/styles/tokens.css、src/styles/global.css、public/admin/typography-classes.js、public/fonts/(新增字体切片)、docs/写作指南.md 字体清单
|
||||
- verify: `grep -c "font-" public/admin/typography-classes.js | awk '$1>=40'`
|
||||
@@ -0,0 +1,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,95 @@
|
||||
# Phase 3:双语骨架 + 腾讯 TMT 翻译管线 - 完成总结
|
||||
|
||||
## 交付结论
|
||||
- 结果:完成
|
||||
- 完成时间:2026-10-25 09:40
|
||||
|
||||
## 假设回顾
|
||||
### 已验证假设
|
||||
- TMT TextTranslate 单请求上限 2000 字符,长文需分块(按段落聚合)
|
||||
- TMT 译文对文学内容质量有限,AI 标注 + 可人工润色锁定是补偿机制
|
||||
|
||||
### 仍未完全验证的假设
|
||||
- 无
|
||||
|
||||
## 交付范围
|
||||
### 已交付
|
||||
- T1 astro i18n 配置 + locale 管道
|
||||
- T2 UI 字典 + 组件串接
|
||||
- T3 语言切换组件(Header 中英互跳)
|
||||
- T4 /en/ 页面骨架 + AI 译文标注组件
|
||||
- T5 TMT 翻译管线
|
||||
- T6 存量翻译执行 + 标注落页
|
||||
- T7 hreflang + 双语 RSS + sitemap locale
|
||||
- T8 构建验收 + staging
|
||||
|
||||
### 未交付
|
||||
- 无
|
||||
|
||||
### 偏差说明
|
||||
- 无
|
||||
|
||||
## 简化决策
|
||||
### 保持简单的关键选择
|
||||
- 最简单可行方案:posts-en 独立集合 + TMT 管线脚本(幂等增量:未变文章不重译)+ /en/ 薄页面复用现有组件
|
||||
- 暂不引入:runtime i18n 框架、cookie 语言记忆(切换即跳转,浏览器语言检测二期)
|
||||
- 不做的抽象/配置化:不多语言框架化(zh/en 硬两语)
|
||||
|
||||
### 本轮明确不做的内容
|
||||
- 上线切换(Phase 4)、Halo 退役(Phase 5)
|
||||
|
||||
## 变更边界
|
||||
### 本轮主要改动模块
|
||||
- 见 tasks.md 的 boundary 记录
|
||||
|
||||
### 明确未触碰的区域
|
||||
- 上线切换(Phase 4)、Halo 退役(Phase 5)
|
||||
|
||||
## 验证证据
|
||||
### 备注
|
||||
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||
|
||||
## 验证范围
|
||||
- 级别:package
|
||||
- 机器验证率:8/8
|
||||
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||
|
||||
## 哲学生效证据
|
||||
### 行为成效回填
|
||||
- Development Record 行为门禁通过
|
||||
|
||||
### 一致性门禁回顾
|
||||
- 验收面(checklist 或成功标准)已通过
|
||||
|
||||
## Git 记录
|
||||
- 日期时间:2026-10-25 09:40
|
||||
- 范围:Phase 3:双语骨架 + 腾讯 TMT 翻译管线
|
||||
- 功能:T1 astro i18n 配置 + locale 管道;T2 UI 字典 + 组件串接;T3 语言切换组件(Header 中英互跳);T4 /en/ 页面骨架 + AI 译文标注组件;T5 TMT 翻译管线;T6 存量翻译执行 + 标注落页;T7 hreflang + 双语 RSS + sitemap locale;T8 构建验收 + staging
|
||||
- 操作:提交 / 推送
|
||||
- 成效:Development Record 行为门禁通过
|
||||
- 提交:pending local commit
|
||||
- 提交区间:44bfb23efbd0bea97ae058c06c54b08d38ad73df..4aa79fca065c6aa70fa8a8ce1bf62b2b383f661e
|
||||
- 推送:not-run (push stage follows commit)
|
||||
|
||||
## AGENTS.md 增量提议
|
||||
```diff
|
||||
i18n 纪律:页面间不能传 props,双语页必须 body 提取为 components/pages/*Page 薄化(zh/en 各一层皮);页面搬目录后 import 相对深度全线要审;HTML 注释不可进 frontmatter 模块区;ts 端点首行误带 --- 会报 undefined:2:7。TMT 管线:1800+ 字符包服务端静默截断(CHUNK_LIMIT=1600 + ratio<0.3 检测 + 段边界二分);幂等 state 落 content/posts-en/.translation-state.json;locked:true 人工润色锁。翻译脚本幂等可重跑,新文章 push 后手动跑一次即可。
|
||||
```
|
||||
|
||||
## 被拒绝的扩展提议
|
||||
- 无
|
||||
|
||||
## 门禁证据
|
||||
- check_spec_package.py exit 0
|
||||
|
||||
## 遗留事项
|
||||
- 无;问题处置见下方结构化区块
|
||||
|
||||
## 问题处置
|
||||
|
||||
```json
|
||||
{
|
||||
"issues": [],
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{"task": "task_0001", "cmd": "pnpm astro build", "exit": 0, "duration_s": 4.295, "stdout_tail_sha256": "258b6510265dd12dc6ed8735a3401809fea696ec4128ff4c3c7957434787528a", "tree_fp": "50380ff9858de824ef43db6cc156bdbb9530e5e77dfcf8a6475bdb4a75d84837", "at": "2026-10-25T01:39:57+00:00"}
|
||||
{"task": "task_0002", "cmd": "pnpm astro build && grep -q \"zh\" dist/index.html", "exit": 0, "duration_s": 4.095, "stdout_tail_sha256": "5252dbe3bc5801ef8c78152b058ae5e484b56f73313d1d0862a953a50c96e66b", "tree_fp": "50380ff9858de824ef43db6cc156bdbb9530e5e77dfcf8a6475bdb4a75d84837", "at": "2026-10-25T01:40:02+00:00"}
|
||||
{"task": "task_0003", "cmd": "pnpm astro build && grep -q 'href=\"/en/\"' dist/index.html", "exit": 0, "duration_s": 4.047, "stdout_tail_sha256": "b00fc1939ea9163faa87b8189ed82f6d53cc4ff4015e172acf3cd621755e6c33", "tree_fp": "50380ff9858de824ef43db6cc156bdbb9530e5e77dfcf8a6475bdb4a75d84837", "at": "2026-10-25T01:40:06+00:00"}
|
||||
{"task": "task_0004", "cmd": "pnpm astro build && test -f dist/en/index.html", "exit": 0, "duration_s": 9.479, "stdout_tail_sha256": "b607a66135561ec1eb2d65c5b0e92152ca208093e957cd2513b456ad5474d3bf", "tree_fp": "50380ff9858de824ef43db6cc156bdbb9530e5e77dfcf8a6475bdb4a75d84837", "at": "2026-10-25T01:40:15+00:00"}
|
||||
{"task": "task_0005", "cmd": "python3 scripts/translate/translate_posts.py --dry-run", "exit": 0, "duration_s": 0.067, "stdout_tail_sha256": "221074b3fe93cda975031e431ad7c9a4f9caba896b3170266391374ef06b0dc6", "tree_fp": "50380ff9858de824ef43db6cc156bdbb9530e5e77dfcf8a6475bdb4a75d84837", "at": "2026-10-25T01:40:15+00:00"}
|
||||
{"task": "task_0006", "cmd": "test $(ls content/posts-en/*.md 2>/dev/null | wc -l) -eq 6", "exit": 0, "duration_s": 0.003, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "50380ff9858de824ef43db6cc156bdbb9530e5e77dfcf8a6475bdb4a75d84837", "at": "2026-10-25T01:40:15+00:00"}
|
||||
{"task": "task_0007", "cmd": "pnpm astro build && grep -q hreflang dist/index.html && test -f dist/en/rss.xml", "exit": 0, "duration_s": 4.122, "stdout_tail_sha256": "2b0ed80c6ead9f0686bc4f24b1709d7eaeb0c0e8a34ff30f270d165d8bdaae05", "tree_fp": "50380ff9858de824ef43db6cc156bdbb9530e5e77dfcf8a6475bdb4a75d84837", "at": "2026-10-25T01:40:19+00:00"}
|
||||
{"task": "task_0008", "cmd": "bash infra/deploy/build.sh && test -f ~/docker_apps/blog/www-staging/en/index.html", "exit": 0, "duration_s": 5.51, "stdout_tail_sha256": "446db6c8e871aff5f7bbd4bec72a4f66a75446546a7b4e702bf1b12c334e6b72", "tree_fp": "50380ff9858de824ef43db6cc156bdbb9530e5e77dfcf8a6475bdb4a75d84837", "at": "2026-10-25T01:40:25+00:00"}
|
||||
@@ -0,0 +1,78 @@
|
||||
# Phase 3:双语骨架 + 腾讯 TMT 翻译管线 - 项目范围
|
||||
|
||||
<!-- spec-format: 2 -->
|
||||
|
||||
## 1. 问题定义
|
||||
- **项目目标**:全站中英实时切换(预构建达成毫秒级):/en/ 路由 + UI 字典 + hreflang + 腾讯云 TMT 构建前翻译管线(.en.md 入仓、可锁定、AI 标注)
|
||||
- **目标用户**:站长(中文写作)/ 英文读者(一键切换)
|
||||
- **核心价值**:「所有内容实时转英语」由双语预构建实现,零运行时翻译成本
|
||||
|
||||
## 2. 假设与待确认
|
||||
|
||||
### 2.1 已确认事实
|
||||
- 腾讯密钥已落位 ~/docker_apps/blog/tencent-translate-credentials.txt(0600,标签 Secert* 拼写容错处理)
|
||||
- Astro 内置 i18n 路由(defaultLocale zh、prefixDefaultLocale false → zh 原路径 / en 前缀 /en/)
|
||||
- 内容量小(6 文 21 图 1 瞬间),TMT 免费额度(500 万字符/月)完全覆盖
|
||||
- 评论共键已定(ArtalkComments pageKey 同 slug)
|
||||
|
||||
### 2.2 关键假设
|
||||
- TMT TextTranslate 单请求上限 2000 字符,长文需分块(按段落聚合)
|
||||
- TMT 译文对文学内容质量有限,AI 标注 + 可人工润色锁定是补偿机制
|
||||
|
||||
### 2.3 待确认问题
|
||||
- [x] 翻译引擎由 LLM 改腾讯 TMT(用户拍板,与 Zotero 插件同款凭据)→ 结论:按 TMT 执行
|
||||
|
||||
### 2.4 可选解释与取舍
|
||||
- content/posts-en/ 独立集合(slug 与 zh 1:1 映射)> 同目录 .en.md 后缀(glob loader 模式更简单、无重名歧义)-> 选独立集合
|
||||
- 非文章页(图库/瞬间/关于)/en/ 侧 UI 字典切换 + 内容回退中文标注 > 全量翻译(瞬间/图库 caption 量大价值低)-> 选 UI 切换+回退
|
||||
|
||||
### 2.5 已证伪路径
|
||||
- 无
|
||||
|
||||
## 3. 功能范围
|
||||
|
||||
### 3.1 核心功能(MVP)
|
||||
- [x] T1 astro i18n 配置 + locale 管道
|
||||
- [x] T2 UI 字典 + 组件串接(Header/Footer/SearchDialog/图库/瞬间/关于)
|
||||
- [x] T3 语言切换组件(Header,中英互跳同路径 /en/ 前缀)
|
||||
- [x] T4 /en/ 页面骨架(index/archives 文章路由真实双语,其余页 UI 英文化+内容回退标注)
|
||||
- [x] T5 TMT 翻译管线(TC3 签名/分块/frontmatter 处理/.en.md 生成/锁定/dry-run)
|
||||
- [x] T6 存量 6 文翻译执行 + AI 译文标注组件
|
||||
- [x] T7 hreflang 双向 + 双语 RSS + sitemap locale 覆盖
|
||||
- [x] T8 构建验收(/en/ 路由全量产物 + 翻译产物抽检)
|
||||
|
||||
### 3.2 扩展功能
|
||||
- 瞬间/图库 caption 的批量翻译(二期按需)
|
||||
|
||||
### 3.3 不在范围内
|
||||
- 上线切换(Phase 4)、Halo 退役(Phase 5)
|
||||
|
||||
## 4. 最小实现路径
|
||||
- 最简单可行方案:posts-en 独立集合 + TMT 管线脚本(幂等增量:未变文章不重译)+ /en/ 薄页面复用现有组件
|
||||
- 暂不引入:runtime i18n 框架、cookie 语言记忆(切换即跳转,浏览器语言检测二期)
|
||||
- 不做的抽象/配置化:不多语言框架化(zh/en 硬两语)
|
||||
|
||||
## 5. 技术决策
|
||||
- 技术栈:Astro i18n 内置路由 + python TC3 直签(零 SDK 依赖)
|
||||
- 证据协议:evidence: runs
|
||||
- 本轮允许改动:astro.config.mjs、src/、content/posts-en/、scripts/translate/、.spec/
|
||||
- 本轮不应触碰:content/posts(zh 源只读)、infra/、生产
|
||||
- Git integration branch:`spec/2026-10-25_add-blog-i18n`
|
||||
|
||||
### 5.1 验证策略
|
||||
- 范围级别:package
|
||||
- 变更对象:i18n 配置、src 组件、posts-en 集合、翻译脚本
|
||||
- 快速检查:`pnpm astro build`
|
||||
- 集成检查:/en/ 路由产物存在(index+6 文章页)+ hreflang 头存在 + rss.en 产出
|
||||
- 全项目检查:staging 更新 + rollback-dryrun 仍绿;仅在构建链或部署脚本变更时升级范围重跑
|
||||
- 升级触发:只有改动构建链、部署脚本或 nginx 相关面时,才升级验证范围并记录理由
|
||||
|
||||
## 6. 成功标准与验证方式
|
||||
- [x] /en/ 与 /en/archives/{6 slug}/ 路由产物全存在 -> verify: task_0008
|
||||
- [x] 翻译管线幂等(重跑零新增 API 调用,锁文件/哈希比对) -> verify: task_0005
|
||||
- [x] AI 译文标注出现在 /en/ 文章页 -> verify: task_0006
|
||||
- [x] hreflang 双向 + en RSS 产出 -> verify: task_0007
|
||||
|
||||
## 7. 风险与约束
|
||||
- TMT 签名实现错误(TC3 步骤繁)→ 脚本内自检模式(--selftest 打印签名串供比对官方文档) -> 管线任务内覆盖
|
||||
- 文学译文质量 → AI 标注 + 人工可改 posts-en 文件后 frontmatter locked: true 跳过重译 -> 机制内置
|
||||
@@ -0,0 +1,56 @@
|
||||
# Phase 3:双语骨架 + 腾讯 TMT 翻译管线 - 任务拆解
|
||||
|
||||
## 使用规则
|
||||
- 每个任务 boundary/verify 齐备;verify 首个反引号 span 为执行命令;迁移教训:verify 只读断言(不重跑生成器)
|
||||
|
||||
## 阶段一:实现
|
||||
|
||||
- [x] T1 astro i18n 配置 + locale 管道
|
||||
- claim: orchestrator
|
||||
- boundary: 只改 astro.config.mjs(i18n 段)与 src/lib/locale.ts(当前语言工具)
|
||||
- verify: `pnpm astro build`
|
||||
|
||||
- [x] T2 UI 字典 + 组件串接
|
||||
- depends-on: task_0001
|
||||
- claim: op-gitea(mesh 车道,预约 src/i18n/ 与 Footer/SearchDialog/gallery/moments/about)
|
||||
- boundary: 新建 src/i18n/ui.ts(zh/en 字典)并串接 Footer/SearchDialog/gallery/moments/about 页的静态文案;不碰 Header/BaseLayout(orchestrator 的语言切换在那)
|
||||
- verify: `pnpm astro build && grep -q "zh" dist/index.html`
|
||||
|
||||
- [x] T3 语言切换组件(Header 中英互跳)
|
||||
- depends-on: task_0001
|
||||
- claim: orchestrator
|
||||
- boundary: 只改 src/components/Header.astro 与 src/lib/locale.ts
|
||||
- verify: `pnpm astro build && grep -q 'href="/en/"' dist/index.html`
|
||||
|
||||
- [x] T4 /en/ 页面骨架 + AI 译文标注组件
|
||||
- depends-on: task_0001
|
||||
- claim: orchestrator
|
||||
- boundary: 新建 src/pages/en/{index.astro,archives/[...slug].astro,archives/index.astro,gallery,moments,about}(复用组件+locale prop);src/components/AiTranslatedBadge.astro;posts-en 集合定义进 content.config.ts
|
||||
- verify: `pnpm astro build && test -f dist/en/index.html`
|
||||
|
||||
- [x] T5 TMT 翻译管线
|
||||
- claim: op-artalk(mesh 车道,预约 scripts/translate/)
|
||||
- boundary: 新建 scripts/translate/{tencent_tmt.py,translate_posts.py}:TC3-HMAC-SHA256 签名(--selftest 模式打印中间量)、段落分块(<2000 字符)、frontmatter 透传(title 翻译、locked:true 跳过)、.en.md 生成到 content/posts-en/、原文哈希记录防重译;凭据从宿主 0600 文件读(标签 Secert* 容错);不碰 src/、astro 配置
|
||||
- verify: `python3 scripts/translate/translate_posts.py --dry-run`
|
||||
|
||||
- [x] T6 存量翻译执行 + 标注落页
|
||||
- depends-on: task_0004, task_0005
|
||||
- claim: op-artalk(执行)+ orchestrator(验收)
|
||||
- boundary: 真实调用 TMT 译 6 文入 content/posts-en/;/en/archives 模板挂 AiTranslatedBadge(T4 已备)
|
||||
- verify: `test $(ls content/posts-en/*.md 2>/dev/null | wc -l) -eq 6`
|
||||
|
||||
- [x] T7 hreflang + 双语 RSS + sitemap locale
|
||||
- depends-on: task_0004
|
||||
- claim: orchestrator
|
||||
- boundary: BaseLayout 注入 hreflang 双向;src/pages/en/rss.xml.ts;sitemap 配置 i18n
|
||||
- verify: `pnpm astro build && grep -q hreflang dist/index.html && test -f dist/en/rss.xml`
|
||||
|
||||
- [x] T8 构建验收 + staging
|
||||
- depends-on: task_0002, task_0003, task_0004, task_0006, task_0007
|
||||
- claim: orchestrator
|
||||
- boundary: build.sh 跑 staging;抽检 /en/ 三页(首页/一文/图库)渲染
|
||||
- verify: `bash infra/deploy/build.sh && test -f ~/docker_apps/blog/www-staging/en/index.html`
|
||||
|
||||
---
|
||||
|
||||
**当前进度**:由脚本计算,无需手动维护
|
||||
@@ -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`
|
||||
@@ -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,18 @@ 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'],
|
||||
routing: { prefixDefaultLocale: false },
|
||||
},
|
||||
vite: {
|
||||
define: {
|
||||
'import.meta.env.GIT_COMMIT_SHA': JSON.stringify(sha),
|
||||
|
||||
@@ -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,20 @@
|
||||
{
|
||||
"posts/csge-lei-pei-zhi.md": {
|
||||
"sha256": "bd23b63a6453829fe3dccd4e261f2e9526917edc5d2fb8e23cac7e7371db140f"
|
||||
},
|
||||
"posts/deng-dai-ge-duo.md": {
|
||||
"sha256": "45dee1a0c7572ac0e18392798cc9c9fd755bc64562bf87872ed238de72b30892"
|
||||
},
|
||||
"posts/ni-tan.md": {
|
||||
"sha256": "07881bcae9ab5549ed888af4abbf314a487d29bdc03b22a60d0a19977c30321f"
|
||||
},
|
||||
"posts/shu-zui.md": {
|
||||
"sha256": "da9c4603b7e9bb03336466bbd725df2b25230d2d1e6621c8cfd616cfdc6b7ae2"
|
||||
},
|
||||
"posts/hai-feng-zhong-shi-luo-de-xie-se-kui-zeng.md": {
|
||||
"sha256": "52d1221b6193600b54d1bd4676786604207662651769ed503802de5555d87edc"
|
||||
},
|
||||
"posts/mian-sha.md": {
|
||||
"sha256": "0f80f12b0a3811d026e8300e49921c4a14056b5b04b8cb61b04b04afdcec1eb5"
|
||||
}
|
||||
}
|
||||
@@ -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,206 @@
|
||||
---
|
||||
title: "Various configurations of CS"
|
||||
pubDate: 2026-04-28
|
||||
category: tech
|
||||
tags: [Linux]
|
||||
---
|
||||
|
||||
# 1. Cloud Server
|
||||
|
||||
## 1.1 Docker-related Operations
|
||||
|
||||
- **Docker Installation**
|
||||
|
||||
<https://www.cnblogs.com/Lenbrother/p/18673403>
|
||||
|
||||
<https://mirrors.tuna.tsinghua.edu.cn/help/debian/>
|
||||
|
||||
<https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/>
|
||||
|
||||
```bash
|
||||
apt install docker-ce \
|
||||
docker-ce-cli \
|
||||
containerd.io \
|
||||
docker-buildx-plugin \
|
||||
docker-compose-plugin
|
||||
```
|
||||
|
||||
- **Changing the Default Storage Location for Docker**
|
||||
|
||||
### **1. Check the existing storage directory of Docker**
|
||||
|
||||
```powershell
|
||||
docker info
|
||||
```
|
||||
|
||||
Look for the “Docker Root Dir” in the output. By default, this directory is located at /var/lib/docker, as shown below:
|
||||
|
||||

|
||||
|
||||
### **2. Check the location of the docker.service file**
|
||||
|
||||
```powershell
|
||||
systemctl status docker.service
|
||||
```
|
||||
|
||||
The location of the docker.service file is shown below. You need to modify the contents of this file in subsequent steps.
|
||||
|
||||

|
||||
|
||||
### **3. Stop the docker.service process**
|
||||
|
||||
```powershell
|
||||
systemctl stop docker.service
|
||||
```
|
||||
|
||||
### **4. Move the existing storage directory to a new location**
|
||||
|
||||
Select a directory with sufficient space to serve as the new root directory for Docker. Copy all files from the current root directory to the new directory. Let’s assume the new directory is /data/docker.
|
||||
|
||||
As mentioned in step 1, the current storage directory is /var/lib/docker. Copy all files there to the new directory using the following command:
|
||||
|
||||
```powershell
|
||||
cp -r /var/lib/docker/* /data/docker
|
||||
```
|
||||
|
||||
*Note: The new directory must have read/write permissions for the root account. You can set these permissions directly by running `chmod -R 777 /data/docker`.*
|
||||
|
||||
### **5. Modify the path of the root directory**
|
||||
|
||||
There are two ways to do this. Choose either one as needed.
|
||||
|
||||
- **Modify the docker.service file**
|
||||
|
||||
Add `--data-root=/data/docker` after ExecStart=/usr/bin/dockerd. Separate the paths with spaces. /data/docker should be the new directory from step 4. If there are other configurations, leave them unchanged.
|
||||
|
||||

|
||||
|
||||
- **Modify the daemon.json file**
|
||||
|
||||
You can also modify the /etc/docker/daemon.json file to change the source of Docker images. If this file doesn’t exist, create one instead. Here’s an example:
|
||||
|
||||
```json
|
||||
{
|
||||
"data-root": "/data/docker"
|
||||
}
|
||||
```
|
||||
|
||||
Note: It’s best not to use both methods simultaneously. Conflicting configuration paths can cause the Docker service to fail to start. You can check the detailed logs using the command `journalctl -u docker.service`.
|
||||
|
||||
### **6. Restart the Docker service**
|
||||
|
||||
```powershell
|
||||
systemctl daemon-reload
|
||||
systemctl restart docker.service
|
||||
```
|
||||
|
||||
### **7. Verify the status of existing images and containers**
|
||||
|
||||
```powershell
|
||||
docker images
|
||||
docker ps -a
|
||||
```
|
||||
|
||||
- **Changing the Docker Image Source**
|
||||
|
||||
Since DockerHub is unavailable in China, you need to change the image source.
|
||||
|
||||
– Method for changing the source:
|
||||
|
||||
``` bash
|
||||
sudo mkdir -p /etc/docker
|
||||
sudo vim /etc/docker/daemon.json
|
||||
```
|
||||
|
||||
Write the following content:
|
||||
|
||||
``` json
|
||||
{
|
||||
"registry-mirrors":[
|
||||
"https://docker.1ms.run",
|
||||
"https://docker.xuanyuan.me",
|
||||
"https://hub.docker.hk.sqyyl.cn",
|
||||
"https://docker.rainbond.cc",
|
||||
"https://mirror.iscas.ac.cn",
|
||||
"https://docker.mirrors.sjtug.sjtu.edu.cn",
|
||||
"https://docker.m.daocloud.io"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
After saving and exiting, restart the Docker service.
|
||||
|
||||
``` bash
|
||||
sudo systemctl restart docker
|
||||
```
|
||||
|
||||
### 1.1.1 **Microsoft 365 E5 Renew X**
|
||||
|
||||
[Docker版本的E5续订服务:Microsoft 365 E5 Renew X_e5 Docker Customization - CSDN博客](https://blog.csdn.net/qq_40605167/article/details/122888580)
|
||||
|
||||
Enable the **IMAP/STMP** or **POP/STMP** feature for the email account.
|
||||
|
||||
The Docker deployment commands are as follows:
|
||||
|
||||
``` bash
|
||||
docker run -d -p 1066:1066 -e TZ=Asia/Shanghai -e [sender="XXX@xxx.com](mailto:sender=%22XXX@xxx.com)" -e pwd="LFQSdUsM4rJfUKQB" -e [receiver="XXX@xxx.com](mailto:receiver=%2XXX@xxx.com)" -e adminpwd="****" hanhongyong/ms365-e5-renew-x:pubemail
|
||||
```
|
||||
|
||||
Replace <XXX@xxx.com> with your email address, and \*\*\*\* with your password.
|
||||
|
||||
### 1.1.2 DPanel
|
||||
|
||||
**DPanel** is a **lightweight container management panel** that allows for visual management and monitoring of Docker containers.
|
||||
|
||||
The Docker deployment commands are as follows:
|
||||
|
||||
``` bash
|
||||
docker run -d --name dpanel --restart=always \
|
||||
-p 8807:8080 -e APP_NAME=dpanel \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /home/dpanel:/dpanel dpanel/dpanel:lite
|
||||
```
|
||||
|
||||
### 1.1.3 ZeroTier Client
|
||||
|
||||
- **Step 1: Docker pulls the image**
|
||||
|
||||
``` bash
|
||||
sudo docker pull zerotier/zerotier
|
||||
```
|
||||
|
||||
- **Step 2: Create and run the ZeroTier container**
|
||||
|
||||
``` bash
|
||||
sudo docker run -d \
|
||||
--name zerotier-one \
|
||||
--restart=unless-stopped \
|
||||
--device=/dev/net/tun \
|
||||
--net=host \
|
||||
--cap-add=NET_ADMIN \
|
||||
--cap-add=SYS_ADMIN \
|
||||
-v /home/wait4x/zerotier-one:/var/lib/zerotier-one \
|
||||
zerotier/zerotier:latest
|
||||
```
|
||||
|
||||
- **Step three: Replace the official files with the self-built Planet files.**
|
||||
|
||||
``` bash
|
||||
sudo cp … …
|
||||
```
|
||||
|
||||
Restart the Docker container.
|
||||
|
||||
``` bash
|
||||
sudo docker restart zerotier-one
|
||||
```
|
||||
|
||||
- **Step four: Adding a private ZeroTier network**
|
||||
|
||||
``` bash
|
||||
sudo docker exec zerotier-one zerotier-cli join XXXXXX
|
||||
```
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "Waiting for Godot"
|
||||
pubDate: 2026-03-31
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#b45309'
|
||||
---
|
||||
|
||||
### Who is Gordo? Why wait?
|
||||
|
||||
**Hopeless Waiting: Self-Salvation and the Prisoner’s Dilemma**
|
||||
|
||||
Godo is the character who runs through the entire book. However, no one has actually seen Godo in reality. Gogo and Didi simply wait for Godo. Is Godo a kind of hope? Or perhaps a belief? Waiting, waiting… Sometimes, they even forget about life itself, choosing to spend their time waiting by committing suicide. So what’s the meaning of waiting for Godo? Isn’t it a spiritual pursuit and a way of redemption? Or maybe waiting for Godo has become their only goal in life. They don’t know what the purpose of waiting is, but waiting for Godo is the only thing they can do, and it’s also the only way for them to escape reality. Whether Godo exists or not doesn’t matter anymore. What matters is the act of “waiting” itself, which serves as a reminder that they haven’t completely lost their physical vitality.
|
||||
|
||||
Perhaps Gordo doesn’t actually exist at all. Gordo is simply a way for Gogo and Didi to escape the real world. It’s the only way they can feel like they still have something to do. It’s a kind of absurd waiting, a waiting that has no meaning at all. And the process of waiting itself is also absurd. Gogo and Didi wait just because they have to wait. The waiting period becomes a very desolate experience for them. They don’t know what to do, they don’t know how to pass the time during this waiting. They just wait, waiting for Gordo to arrive.
|
||||
|
||||
The way humans sustain themselves in the meaningless world is often not through dramatic resistance, but through trivial, repetitive, and even ridiculous daily efforts. This is both a form of compromise and a kind of tragic resilience. However, why not take the brave step of dying, or breaking away from the status quo, and follow the passing of time? Why stay stuck in the present, trapped in the past? Perhaps dying requires determination, and breaking free requires a clear direction. But when both these resources are exhausted, “waiting” becomes the only viable action left.
|
||||
|
||||
Didi always ponders back in time, seeking consistency within the passage of time. Meanwhile, Gogo simply wants to live in the present. We are constantly torn between “trying to understand meaning” and “being forced to endure the moment at hand”. No one truly lives in the future—because it seems that no one has a future at all. The future is merely a waiting period, a constant state of anticipation. People go through day after day, resisting the real world and life in their own unique ways. They coexist with time, sometimes even opposing it, as if to prove that they still exist, and that what they do makes sense.
|
||||
|
||||
So, in this absurd life… time continues to pass as usual. What should we do? With what can we confront emptiness? When waiting becomes fate, when the future is reduced to mere “tomorrow” over and over again, how should we face this inevitable passage of time?
|
||||
|
||||
Between Decline and Rebirth: The Anchorage of Memories and the Flow of Time
|
||||
|
||||
There are five characters in the play, but it seems that only Didi is awake. Only he remembers the past, remembers what happened yesterday. Gogo, Pozo, the lucky guy, and the young man none of them remember anything that happened yesterday. It seems as though they are always living in the present. In reality, time continues to pass. The growth of green leaves on that tree signifies that everything keeps moving forward. They live in the present, but they also bear witness to the past and step towards the future. However, every day that passes is just a repetition of the same events, just like the constant discussions about suicide. Going to death is merely a way to pass time, without truly having the courage to take those steps.
|
||||
|
||||
The lucky person’s incoherent speeches in the play resemble both rational desperation and absurd reflections of reality. When ordered to think, his monologues initially follow a logical structure, using various terms and concepts. But soon, they become chaotic, with words failing to form any coherent meaning at all. It’s as if people try to use reason to combat absurdity, to explain the world that has lost its logic through thought… but all their efforts are in vain, and everything ultimately leads to nothingness.
|
||||
|
||||
On the other hand, Didi’s clarity seems to be another form of luck. He constantly tries to piece together fragments of memories to recall the past—that past which no one else remembers anymore. No one remembers yesterday; only Didi does. Didi fights against the passage of time with his memories, enduring pain yet remaining awake. Gogo, on the other hand, tries to ease the burden of hope, feeling numb but at the same time relaxed. For those around him, time simply doesn’t exist at all—it’s a state of utter absurdity. Dead trees grow leaves, witnessing the changes in the world, but people remain stuck in the same place, cycling endlessly. Didi is aware of the passing of time, but he still remains trapped in the cage of time. All he can do is wait quietly for Gotham to arrive.
|
||||
|
||||
A tree can be dead, or it can still have leaves.
|
||||
Waiting can be futile, or it can be a proof of existence.
|
||||
Memory can be a burden, or it can serve as a point of reference.
|
||||
Absurdity can represent despair, or it can be the beginning of freedom.
|
||||
|
||||
Perhaps in this absurd world, in this meaningless life, there are times when we don’t need to “overcome” the void. Sometimes, simply “continuing” is enough to bring meaning. Just like that tree—between its blooming and withering, that is the answer.
|
||||
|
||||
###
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
- Estragon: (after a long silence) What you’re saying is the opposite of what I said, right?
|
||||
Fradimir: It’s a matter of temperament.
|
||||
Estragon: It’s a matter of personality.
|
||||
Fradimir: People are powerless against this.
|
||||
Estragon: It’s useless to try anymore.
|
||||
Fradimir: One’s nature remains unchanged, no matter how hard one tries to change it.
|
||||
|
||||
The more people I meet, the happier I become. Even when dealing with the most humble people, I feel like I’m gaining new experiences, enriching my knowledge, and able to appreciate happiness in a better way.
|
||||
|
||||
- There is a certain amount of tears that can be shed in this world. When someone in one place starts crying, there must be someone else who stops crying in another place. The same goes for laughter. (He laughs.) Therefore, we shouldn’t speak ill of our time. It isn’t any worse than times in the past. (Silence.) Nor should we speak well of our time either.
|
||||
|
||||
- Without him, my thoughts and feelings would probably always be confined to the trivial and mundane matters of everyday life—things that don’t really matter at all. As for what constitutes the highest form of beauty, goodness, or truth, I truly cannot attain it. I also know that I simply don’t deserve it at all.
|
||||
|
||||
- What exactly is so extraordinary about it? Is it actually the heavens themselves? It’s pale yet bright, just like the sky at any moment during the day. (Loudon) The same sky, in the same direction.
|
||||
|
||||
The lights suddenly dimmed. For a moment, the sky turned dark. The moon rose at the end of the stage, climbing higher and higher, silently scattering silver light across the stage.
|
||||
|
||||
People don’t need us every day. To be honest, people don’t exactly need us that much. Others can also do these things. Even if they may not do them as well as we do, at least they can do them just as well. We just heard a cry for help… It seems like he is sending a message to all of humanity. But right now, at this moment, there are only two humans here: us. Whether we like it or not. We must seize this opportunity, while time isn’t yet too late. Since fate has brought us together among these despicable creatures, let’s at least try to represent them properly. Indeed, when we weigh the pros and cons, we remain worthy of our conditions of existence. A tiger will instinctively run to help its kind. Otherwise, it will flee into the depths of the jungle. But that’s not the issue. What we do here is what we must ask ourselves. We are honored to know the answer to that question. Yes, in this vast chaos, there is one thing that is clear: we are waiting for Gordo’s arrival.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
title: "The lost blood-red gift, lost in the sea breeze"
|
||||
pubDate: 2026-03-25
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#0f766e'
|
||||
---
|
||||
|
||||
### Autumn – The Autumn of Scott, the Autumn of a Father, the Autumn of a Youth (A Prelude to Winter)
|
||||
|
||||
Autumn brings a sense of desolation, signifying the twilight of time. The father has grown old, as has the strong and vigorous Mastro Scott. Under the burden of life, the once youthful and strong father can no longer handle everything with ease and confidence. Faced with his former companions, he has no choice. His two children witness all this: their former playmates must now distance themselves from them. For the first time, they confront the cruelty of reality. For the father, he has lost his most loyal companion, Scott, who has been with him through many hardships. Would the father be willing to sell Scott? No! But he must do so. With children still needing care around him, he is no longer the same person he used to be. Under the weight of life’s burdens, he has no choice. He loves Scott, he loves his children, he loves his wife… To survive, love also requires compromise.
|
||||
|
||||
The youth see firsthand the cruelty of life and the helplessness of growth. For the father, it is the helplessness of a hero at the end of his career; for Scott, it is the tragedy of loyalty being betrayed; for the children, it is the moment when their innocence ends.
|
||||
|
||||
### Darkness Beyond Reach – James’s “Departure”
|
||||
|
||||
At the age of 18, James bids farewell to his parents and grandmother, setting off on a journey to escape Breton Point. He wants to get away from the coal mines, to break free from fate… But he realizes that he can never truly escape them. It seems that since birth, serving the coal mines has been his destined mission, one he cannot escape.
|
||||
|
||||
This is a story of seeking a future, but also of stepping into darkness. Darkness comes from beneath the earth, from the absence of light. It comes from the future, from seeing clearly the destiny that awaits us. It comes from a darkness that we cannot escape. The darkness is vast and endless. We want to leave, to break free from our fate, but we are still trapped in its embrace, carried by it towards the unknown.
|
||||
|
||||
This is a shackle passed down through generations, a fate that originated from this land—Breton Point. It is impossible to change. We try to break free, but it seems like we are pushed forward by fate, towards this vast darkness. This is the longing of a young man, his resistance against fate, his helplessness… And then there is the acceptance of fate after seeing it clearly.
|
||||
|
||||
### The Lost Blood-red Gift in the Sea Wind – The Distant Home
|
||||
|
||||
When I return to the place where I was born, a seemingly successful existence, I meet my father, mother, my former wife, and my son. In the past, I chose to leave this place and go to Montreal. Now, as I return here again, the sea wind is fierce, as if trying to drive me away from these people who once were my family. I cannot hug them anymore, nor can I share a happy life with them.
|
||||
|
||||
For me, they are the blood-red gifts lost in the sea wind. They carry my past, my childhood memories of family and love. But now, all of that has disappeared in the sea wind. To them, I am an outsider. My parents treat me calmly, while my former lover hates me. My child doesn’t even recognize me. My presence might actually disrupt the existing balance, warmth, and happiness.
|
||||
|
||||
This is the helplessness of returning to the past once again, after reaching adulthood. Successfully getting away from that seaside, one waits for another return, only to find that it’s no longer possible to go back. One no longer belongs to that place. The sea breeze has swept away everything, and the cold wind stings everyone’s heart.
|
||||
|
||||
***Hometown is not a place where one can return; it’s a past that can never be reached again.*** We have all become strangers in our own lives. In the sea breeze, we gaze at each other, facing that lost, blood-red gift of the past.
|
||||
|
||||
### Returning to Hometown – The Journey Back
|
||||
|
||||
Returning to the hometown from the big city. Returning to the hometown is different from the return that comes with the lost, blood-red gift in the sea breeze. The story of returning to the hometown involves a more acute conflict. The story of returning to the hometown is based on blood ties and family bonds, but it also involves a sense of separation. Unlike the calmness of the lost, blood-red gift in the sea breeze, the main characters of this journey are from the big city. The father comes from the countryside, deeply bound by darkness, while the mother is a beautiful woman living in the bright and colorful city. As a child, the young man faces a strong opposition between these two emotions during his journey back to the hometown. His father wants to return to his former homeland to see his family again, to step onto that solid ground once more. His mother, however, hates this dark city from the bottom of her heart, unwilling to let her child be exposed to such fate and suffering again.
|
||||
|
||||
Then, in his youth, he was naive. He didn’t know what his parents felt about this dark city. He played with his siblings, searching for happiness in his unfamiliar homeland. But in the eyes of his grandmother, they no longer belong here. They have already left this city, and they have broken away from the family’s bloodline. His father wants to reconnect with the past, while his mother wants him to stay away from this darkness.
|
||||
|
||||
When everything ends, ***everyone waves goodbye, but the train keeps going forward, because it has no choice, and because it doesn’t like to see people waving goodbye***. His father can’t return; he is no longer a true Cape Bretoner. His mother doesn’t want to return; she despises this land from the bottom of her heart. His child can’t return either; he has only a brief interest in his hometown, without any deep attachment.
|
||||
|
||||
### The Gilded Gift of Grayness – The Bitterness of Growing Up
|
||||
|
||||
The youth’s courage and growth, but the pain caused by misunderstandings between parents.
|
||||
|
||||
The youth desires to gain his parents’ recognition through his own efforts, to reduce their pressure on him. But the way to earn money isn’t the traditional way of gaining acceptance. His parents hope that Jesse will be honest and have a sense of morality, and that he won’t go astray. They love Jesse, but they don’t approve of him earning money through gambling, nor do they approve of his behavior of staying out of bars all night and getting drunk.
|
||||
|
||||
On one side, the teenager believes that he has earned money through his own efforts and wants to gain his parents’ approval. On the other side, his parents, based on their moral values and beliefs, think that their child has gone astray and want to correct his “wrongful behaviors”. The teenager accepts his parents’ teachings, endures the “gray gifts” they give him—punishments and discipline—and loses the “golden gifts” of pride and sense of achievement. But in return, he receives the “gifts of growth”—there are always conflicts between a child’s actions and those of their parents. A child’s brave actions may seem insignificant to their parents. This is a problem between generations, as well as between different roles within a family. It’s a bitter but true reality.
|
||||
|
||||
### The perseverance of the older generation, and the departure of the younger ones
|
||||
|
||||
It’s much braver to spend a lifetime doing things that one dislikes, rather than constantly selfishly pursuing dreams and acting on a whim.
|
||||
|
||||
My father loved literature and cherished the idea of freedom. However, in the face of life’s challenges, he chose to bind himself tightly to this ship. He sent his children away, so that they wouldn’t be bound by this ship anymore. He was willing to spend his entire life on this ship, sitting in that chair, peacefully living out his days. He dedicated his life to life itself, to his descendants.
|
||||
|
||||
As the protagonist, I witnessed my father’s choices and his eventual death. In my heart, there was both admiration for my father and contempt for myself. I lacked my father’s courage, but in the end, I made the same decision he would have made: to stay on this ship and carry on his legacy. However, in my mother’s eyes, all of this was in vain. My father is gone now, and this ship no longer deserves its former glory. The younger generation has abandoned it, and it no longer belongs to us anymore.
|
||||
|
||||
This is a conversation between two generations. It reflects the aspirations of the younger generation for a new life, while the older generation maintains their attachment to the past. But the father acts as a bridge between the old and the new eras. He supports his children as they move towards the new era, while he willingly remains trapped in the old times. The death of the father was utterly powerless. In the vast ocean, everything shattered, and the bridge between the new and the old eras was destroyed. I, my brothers, my sisters—to those from the old era, to this land, we are all traitors.
|
||||
|
||||
### The road to Luancenjiao – A struggle between life and death
|
||||
|
||||
A father and son on the verge of death engage in a profound conversation at the end of their lives. The grandmother doesn’t want to live alone, nor does she wish to be confined in a nursing home. She wants to be redeemed by her grandson. Meanwhile, the grandson realizes that his life is coming to an end. In his last moments, he also wants to be close to his grandmother, hoping to receive her redemption. But things don’t always go as we hope. Sitting opposite each other, separated by countless years, neither of them can be redeemed by the other.
|
||||
|
||||
The path to the point of Julan Cen is the way for a grandson to search for past memories, a path to pursue kinship. It is also the road of grandmother’s tragic memories, and ultimately, it leads to death.
|
||||
|
||||
In her youth, her grandmother watched as her husband left her. In her old age, she saw her children grow up and move away from her. She lived alone in a house perched on the edge of a cliff, accompanied only by livestock, the sound of waves crashing against the cliffs, and endless loneliness. At the end of her life, she hoped to be redeemed by her grandchildren. But she failed. Her grandson, who was also facing death, could only allow her to enjoy a brief moment of redemption and love. After that, there was nothing but true loneliness.
|
||||
|
||||
Sun Tzu walked away from here, towards the city. In the last moments of his life, he stepped onto the path that led to Rangcenjiao, in search of the last trace of love. He remembered his grandfather’s death, witnessed the same patterns of behavior shown by his parents towards his grandmother. Then, during his grandmother’s dying moments, he witnessed the final stages of her life. His grandmother passed away. The world before him became dark. Even the last glimmer of hope disappeared. The world within his heart also turned dark. Two kinds of darkness began to merge together, and everything was lost in darkness.
|
||||
|
||||
### Chains woven from blood and sea, waves of growth and fate intertwining
|
||||
|
||||
Brent Island symbolizes barrenness, inheritance, and fate. It has witnessed generations of people struggling to maintain their traditions and traditions. The continuation of miners’ lives, the passing on of fishing traditions—all these have also shown how new generations resist their fate. They flee Brent Island, flee the gloomy cities, flee the sea. It also reflects the gap between generations, the dedication of ancestors towards their homeland, their perseverance in holding onto the facts and beliefs they have chosen. Children yearn for a new life and look forward to the outside world. Ultimately, Brent Island represents the helplessness, pain, hope, and longing that people experience under the constraints of their fate.
|
||||
|
||||
Brighton Point marks the beginning of everything, and also the end of everything. The story begins at the shores of Brighton Point, and ends there as well. This story about growth, about fate, about returning back... it is tightly linked to Brighton Point. Brighton Point is a chain woven from blood and sea… it’s like a unique fortress. New lives emerge from within this place, while old lives remain stubbornly clinging to their former ways. Those who have already left wish to return. This is a land bathed by the sea breeze, filled with sorrow and dignity. Some stay steadfast, others suffer… but these are their eternal roots. It’s also the root that many cannot escape from… even though they seem never to be able to break free from the chains woven by blood and sea… those chains belonging to the Soviet Union.
|
||||
|
||||
Brenton Point marks the beginning of life in this story, as well as the final chapter leading to death. People begin to grow here, escape from here, and return again. They stay steadfast here, and also move on to new lands. This place carries the helplessness people feel towards the challenges of reality; it carries their courage to break free from the cage of fate. It also embodies the indelible connection between those who leave their homeland and the land they once inhabited, along with the paradox of never being able to return. It bears witness to everything that happens on this island, this dimly lit city—every story about growth, every story about ideals and reality. It embodies the struggles of survival, the hope of living, and the tranquility of death. Everything occurs alongside this island, alongside the fierce, salty wind that blows through the calm sea.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
### Autumn
|
||||
|
||||
Every morning brings with it an increasingly dark atmosphere, and its expression becomes even more gloomy. The Atlantic Ocean’s murky waters, with their yellowish-tinted waves, hit the smooth rocks along the shore with great force, without any mercy. These rocks scattered beneath the ever-present cliffs seem to be remnants of some giant who have carelessly dropped them there.
|
||||
|
||||
- The heavy rain fell like countless beads tilted by the wind, covering the entire area with water.
|
||||
|
||||
### The darkness stretches endlessly...
|
||||
|
||||
At that time, we all prayed, including him, for the tendon to remain intact and for any infections to be avoided. After all, that hand was the only one he had left. In the dangerous and unpredictable ocean, we were all passengers who depended on that hand for survival.
|
||||
|
||||
– But all storms eventually diminish into mere strong winds, and ultimately return to calmness. Perhaps without storms and strong winds, we cannot achieve any true peace. Or maybe peace requires those storms as a prerequisite, in order for it to truly manifest its true nature.
|
||||
|
||||
Once you drink the water that comes from beneath the ground, it becomes part of your body. It’s like the blood that a man leaves in a woman’s body—it can change a woman for life, and remains with her forever. That part of the man continues to flow deep within the woman’s body. Such things can keep you awake at night, haunting you until the end of your life.
|
||||
|
||||
I extended my hand towards my grandfather. Suddenly, I felt a twisted and powerful force coming from his hand—it seemed like it could crush my palm completely. His fingers were all deformed; his thumbs were flattened and overly broad. The scars on his hands had been polished to a hard, black texture. Those enormous, irregularly shaped balls at the joints of his fingers indicated that he had suffered severe injuries due to broken bones. Yet, it was still a hand full of terrifying power. For a moment, I had a terrifying thought: perhaps I wouldn’t be able to walk anymore… perhaps I would never be released from this situation. But in the end, my grandfather let go of my hand. I felt free again.
|
||||
|
||||
- Even on those rough and uneven streets, when you realize that it may never be possible to step foot there again, or even to see them once more in this lifetime, they still seem desolate and lonely.
|
||||
|
||||
I saw the expressions on their faces—the expressions of hundreds and thousands of people who have appeared in my past life. I even saw the same expressions myself. I’ve encountered such vehicles before, and I saw the same looks reflected in the glass and mirrors. At this moment, I don’t really belong to their lives at all. I’m just trapped inside this semi-transparent, movable display box. I stay for a while along their sorrowful streets, then disappear. Their lives go on as usual, without any changes. I simply walk through their lives, without being involved with them at all. Another insignificant river carries away its floating debris, while only the banks remain eternal. The water flows towards some unknown destination. Those debris never reach that destination, nor can they ever reach it. In that brief moment, it’s enough for them to categorize me, and then dismiss me as if I weren’t even there: “Our struggles, the spirits buried in our graves… What can he understand?”
|
||||
|
||||
I finally understand that the elders in my past life were much more complex than I judged them to be. Grandpa was emotional, romantic, and loved coal mines. Grandma, on the other hand, was strict, practical, and hated coal mines. There were definitely differences between them. My mother was silent, strong, and obedient; my father was impatient, often becoming rude and inappropriate, yet he also had deep affection that couldn’t be expressed in words. They were so different, but both of them managed to carry on certain aspects of their lives over the past eighteen years, and I inherited those aspects from them. Other than that, I have no idea what kind of life or world I actually lived. Their lives flowed into mine, and my life became one of theirs. There were certainly differences between us, but in many ways, we were much more similar than I thought. Now I realize that perhaps one can live two lives, yet see the same truth.
|
||||
|
||||
It seems that there’s nothing I truly know about myself. But I do understand my mistakes: I wasn’t honest enough with others and with myself.
|
||||
|
||||
The car continued its journey, heading towards the darkness of the night. The headlights of the car searched for that tempting white line. It seemed to rise gently, as if it were pulling us forward, upward, towards some deep place. And we simply followed it, plunging deeper into the boundless darkness.
|
||||
|
||||
### The Lost Gift of Blood in the Sea breeze
|
||||
|
||||
- In the darkness, it’s warm; in the wind, it’s calm.
|
||||
|
||||
The darkness oppresses me, yet it prevents me from falling asleep. Everything I hear and see seems to be mere illusions. They are merely walls of memory, sparks of imagination that keep burning out against those walls.
|
||||
|
||||
### Returning to My Hometown
|
||||
|
||||
Everyone is waving, but the train just keeps going forward. It has no other choice, and it also doesn’t really care about people waving goodbye.
|
||||
|
||||
### A golden gift, tinged with grayness
|
||||
|
||||
He then thought about how terrifying the violence hidden within his father’s body was. That violence roared deep within him, like a turbulent stream beneath a mountain peak. It echoed in some deep, inaccessible caves, where water splashed against the rocks.
|
||||
|
||||
### Ship/Boat
|
||||
|
||||
For her, what we call the “longview” is nothing more than the place where her fearless black eyes gaze, that line separating the sea from the sky.
|
||||
|
||||
It’s much braver to spend a lifetime doing things that you hate, rather than constantly selfishly pursuing your dreams and acting on your own whims and desires.
|
||||
|
||||
Outside that solitary window, whether it’s the ice and snow of winter, the oppressive heat of summer, or in autumn when the wind blows and waves rise, her mother stands alone, gazing out at the sea. It’s still dark when she wakes up, and she hears the sound of shoes on the gravel path leading to the dock. She knows that those footsteps will never stop, because no one in her family wants to go with them to the fishing boats that take them into the sea. What she cannot bear is thinking about her mother’s deep affection for the sea… But when she thinks of you, her heart fills with bitterness: for to her, the former represents loyalty, while you represent betrayal.
|
||||
|
||||
### The way to Dulao Cenjiao
|
||||
|
||||
“We are the descendants of that despair in our hearts. Skye, Rum, Balla, Diri—they are our past.”
|
||||
|
||||
It is quiet and elegant. The beauty of its wings is restrained yet strong. Its extraordinary vitality is fully reflected in its demeanor.
|
||||
|
||||
When I cut my grandmother’s yellowed, dirty nails, I realized that I was allowed to participate in a silent, secret communication. This kind of communication is something that strong people often experience when they interact with those who are weaker. This gap between ability and understanding was something that my grandmother used to impose upon her children—sometimes even their children. This gap further fueled our hidden shortcomings, and adults always carried these flaws onto the next generation. I remember dirty diapers, wet sheets… the first time I tried to speak or walk. I remember Santa Claus coming and going, and all those silly, childish wishes. I remember fearing the passing of time, the fears from childhood that would wake me up from nightmares, and the screams I would let out alone in the middle of the night. I remember the release during the night, the real and imagined secrets and sins… I remember all of this, and I will never forget it. The gap between ability and understanding also includes the tangible support given through physical actions, maintaining health as a way to continue this commitment… Perhaps, it also includes love. In the past, when I thought of my grandmother, I thought more of her abilities. But now, I think of love. Maybe, what I am thinking now is that my grandmother’s strength was always too obvious.
|
||||
|
||||
– No one has ever said that living is an easy thing. Life is simply about surviving.
|
||||
|
||||
We sat at both ends of the kitchen table, looking at each other. It seemed as though there was a gap between us, filled with the long years that have passed since our times.
|
||||
|
||||
She held my hand so tightly, as if I could pull her out of that dark, dreamlike water. I tried to respond with the same strength, because somehow I imagined that she could save me. Suddenly, both of us started crying. We cried for each other, and also for ourselves. We thought we could find strength in each other, but in this moment of fragile tears, we realized that we had found it in each other.
|
||||
|
||||
Sometimes, in the fear of darkness, it’s difficult to distinguish between dreams and reality. We may wake up in the middle of the night, but because the world from our dreams seemed so real, we try to return to that comforting state of oblivion by using our own willpower. Other times, the situation is reversed: we might pinch ourselves or bang our fists against the iron bedposts. Sometimes, nightmares have no boundaries at all.
|
||||
|
||||
All expectations and fears, both those from the past and those of the present, collide and entangle with each other. Sometimes, when we reach the end of the “present,” the “past” seems to become even more significant. Because there is nothing else besides the past; we feel that there is nothing else we can understand. I feel like I’m falling back into the past, hoping to possess more of it, because my future seems to be getting fewer and fewer. Twenty-six years is too short—it isn’t enough. I want to follow in the footsteps of our ancestors, so that I can gain more from what remains of this insignificant present. Those superstitions, herbal remedies, the calls of fate, the sounds of violins that linger in our minds, and those cancer treatments that resemble spider webs… I want to understand them all. What do those所谓的“divine eyes,” psychic visions, dogs’ instincts, and seagulls’ cries have to say about survival and its end? I also want to know those things. I want to return to those priests who can cast spells with their hands. I want to return to those healers who believe in faith-based therapies… but my faith may not be enough. I can go back to any past, but science is too indifferent. I don’t want to die at its hands.
|
||||
|
||||
At that moment, just like the music, the darkness both inside and outside gradually merged into one. They flowed towards each other, blending together, becoming indistinguishable from one another. The differences were erased, leaving only purity behind. There was no space between them, no sound to be heard. This encounter brought all things back into unity.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
title: "Veil"
|
||||
pubDate: 2026-03-09
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#7c3aed'
|
||||
---
|
||||
|
||||
****Under the Veil: Ideal Martyrs and Real Survivors****
|
||||
|
||||
### You cover others’ eyes with veils, but it’s difficult for you to see the world as it really is.
|
||||
|
||||
This is a sentence that the translator wrote in the afterword. It truly reflects the truth hidden within the book “Veil” and its title. People live under a veil, using it to hide from others’ scrutiny, but in doing so, they end up trapped within their own veil.
|
||||
|
||||
It seems that Walter encountered the most tender love and the deepest sorrowful relationships. He loved someone who did not love him back, and in the process, he became trapped in a vortex of emotions, unable to escape. He was wise, with a keen insight into the world, society, and human nature. He understood the hardships of the world, the various appearances of society, and the underlying truths. He knew the true nature of people behind their false appearances. He seemed like a holy figure, yet he also had flawed and "malicious" thoughts. Even until his death, he couldn’t break free from the emotional vortex. In my opinion, Walter’s love for Katie was genuine, and his hatred for betrayal was also sincere. But love always remained with him throughout his life. Before going to Meitan Prefecture, he was a calm observer, an embodiment of reason. He had a precise understanding of truth, and he had his own moral standards and beliefs that could not be violated. These were indelible marks of reason. When he realized the betrayal and rushed to Meitan Prefecture, he was swept away by emotions. He loved and hated at the same time. He hated Katie for betraying him, and he hated himself for falling in love with a lover who went against his principles. At first, he wanted Katie to die as well, but in the end, he forced her to leave. It seemed like he had some relief from marriage and this relationship, but in reality, he never managed to cross the emotional barriers he had built for himself. He was contradictory. Until his death, he couldn’t forgive himself. As he wrote in his last message to the world: “It was a dog that died.” This wasn’t just a metaphor for Katie; it was also his ultimate judgment on his own soul. He finally admitted that the person filled with resentment, who tried to use love to destroy others, was actually the “crazy dog” devoured by hatred. Walter’s life was one of using sharp reason to analyze the world, but he never dared to face the desolate ruins within himself. He died of cholera, but actually, he died under the veil of arrogance and prejudice that could never be removed.
|
||||
|
||||
In contrast, Katie was lucky, and she finally achieved freedom. Under the mask of vanity, what was hidden were the cruelty of the world, the complexity of society, and the unpredictability of human nature. Her initial desire for vanity led her to marry Walter, only to discover that life after marriage wasn’t as she hoped. From the beginning, she knew she didn’t love Walter at all. So, when she arrived in Hong Kong and met Charlie, she fell for him. In Charlie, she saw the qualities she had hoped to find in a partner—humor, charm, and sexiness. But she ignored the true nature of human beings. Before going to Meitan Prefecture, she was vain, afraid, and worried that Walter would discover everything. She was weak, wanting to escape this marriage and be close to Charlie, but she didn’t dare to take action. When Walter forced her to face reality, when everything came to light, she finally saw the evil in human nature. She witnessed Charlie’s indifference and his so-called love being nothing more than empty words. She truly felt fear—fear of death, fear of Walter’s unpredictable thoughts. During her time in Meitan Prefecture, she still lived in memories of the past. She was afraid, unable to see the future. But the temple illuminated by holy light stood still on the other side of the river. She longed for those enchanting colors, eager to start anew. When she took the first step and began working in the monastery, her journey towards awakening began. She slowly reconciled with herself, became less critical of betrayal, and began to understand Charlie better. She started to shed her illusions and recognize her own weakness. She sought the meaning of life, understanding everything around her, even hoping to help Walter out of his suffering. However, until Walter died, they never truly reconciled. She felt guilty toward Walter, seeing her own insignificance. But she couldn’t cross the high wall erected by Walter. She was outside the wall, while Walter remained inside, each living their lives under their own masks, suffering separately. Walter’s death was a kind of “liberation” for him, and also for Katie.
|
||||
|
||||
Walter died because he failed to remove the “veil of rationality and morality”. He destroyed himself while still conscious, leaving behind only a tragic silhouette for the world to see. Meanwhile, Katie survived the disaster. She stripped off the “veil of vanity” during the chaos, and though covered in wounds, she finally saw the true light of day.
|
||||
|
||||
The so-called “veil” not only covers up the truth, but also the courage we need to face our true selves. Walter lacked this courage to accept imperfect love and human nature, and thus he perished in his own despair. Katie, on the other hand, was forced to develop this courage during a life-and-death moment. Thus, she gained freedom.
|
||||
|
||||
In the end, those who died find peace. Those who remain live on, carrying with them memories and lessons from their experiences. Perhaps this is what Maugham wanted to tell us: There is no perfect ending to life. Only by facing the cruel reality of life and still choosing to love and live truly, can one find redemption.
|
||||
|
||||
From betrayal to self-redemption, Katie found herself in a foreign land far from her hometown. She couldn’t escape the temptations of desire. After returning to Hong Kong, she once again fell into Charlie’s arms. But now, she was no longer the same person she was before leaving Meitan Prefecture. Her thoughts had undergone drastic changes. She despised the idea of her body betraying her soul. She decided to leave this place of chaos and pursue freedom once more. Just as she left Meitan Prefecture, she embarked on a new journey toward freedom. Back in her homeland, she saw her mother pass away, her sister become pregnant, and her father finally free himself from his old self. She also wanted to get rid of her old self and embrace a new future. At the end of the story, Katie and her father shared their hearts with each other. Together, they headed to a new place to start anew, to live as a new person.
|
||||
|
||||
### I have hope, and I also have the courage to pursue it.
|
||||
|
||||
Awakening is not a momentary miracle, but rather a long and often frustrating struggle. She will fall, repeat the same mistakes, and struggle in the mire of desire. But true courage lies not in maintaining purity without ever making mistakes, but in having the courage to tear off one's mask after seeing one's own ugliness, and turn towards light.
|
||||
|
||||
Walter died in his "correctness," while Katie lived in her "truth." Walter's story is one of tragic individualism and sacrifice, while Katie's story is one of fortunate individualism and rebirth. Walter felt hopeless, trapped in the mud of hardship, and lacked the courage to face a new life and a new world. He hid himself behind his mask, living in a world built upon his own beliefs, dying "heroically" for them. Katie, on the other hand, lives in a complex society, discovering the true self through introspection, and gaining freedom from a difficult situation. Step by step, she breaks through the false mask to see human nature, the warmth and coldness of society, and the hope of a new world. Her experiences in Meitan Prefecture have led her to self-reflection, and her journey there has given her insights into everything. In death and darkness, she has gained enlightenment and awakened to light. She returned to Hong Kong, where desire once again took hold. This time, she did not sink into the false reality. Finally, with courage, she made her decision and set out on the journey of "leaving."
|
||||
|
||||
At the end of the story, the mask is removed. Katie, with scars on her body, holds her father's hand, walking towards the sunshine of Bahamas. There, there is no false grandeur, no illusions of passion, only a simple yet solid life. Perhaps this is the most gentle answer that James Mangan left for those who struggle between vanity and confusion: Only truth can bring freedom.
|
||||
|
||||
In the story of The Mask, Walter is a highly "heroic" individual who sacrifices himself for his beliefs. Katie, on the other hand, grows up in a complex society, spending her early years amidst desires and vanity. Her journey to Meitan Prefecture brings her clarity in darkness, hope in death, and the true self in a foreign land. She gains freedom in the "new world."
|
||||
|
||||
###
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
- I don’t understand you at all. I really don’t know you at all.
|
||||
|
||||
- I know you are stupid, frivolous, and lacking in intelligence, but I love you. I know your goals and ideals are both vulgar and ordinary, but I still love you. I know you are of second rate quality, but I still love you. It’s funny to think that I try my best to like things that make you happy, and I endure pain just to hide my feelings from you. In fact, I am not ignorant or vulgar, nor do I spread rumors or act foolishly. I know how afraid you are of wisdom, so I do everything I can to make you think I’m a complete idiot, just like everyone else you know. I know you married me just for temporary benefits. I loved you so much that it didn’t matter to me. Most people, as far as I know, feel wronged and even more angry and painful when they love someone but don’t receive love in return. But I’m not like that. I never expected you to love me. I see no reason for you to love me, and I never thought I would be loved in return. I’m grateful to be allowed to love you. When I remember you happily being with me, or when I see the joy in your eyes, I become overjoyed. I try not to let my love bother you, because I know it would be too much for me. So I always watch your reactions, looking for the first signs that my love starts to annoy you. Most husbands see this as a form of power, but I see it as a blessing.
|
||||
|
||||
- He knows that you are vain, cowardly, and selfish. He wants me to see all of this with my own eyes. He knows that you will run away as quickly as a hare when danger approaches. He knows that I was deceived into believing that you loved me, because he knows that you never love anyone else—you only love yourself. He knows that you would sacrifice me in order to escape unharmed.
|
||||
|
||||
- Death comes suddenly and mercilessly, passing through the winding streets.
|
||||
|
||||
- Dawn breaks, and sunlight touches the mist, making it shine like snow descending upon the dying stars.
|
||||
|
||||
- Look, here only you and I are walking on solid ground. The nuns walk in the sky, while your husband walks in the darkness.
|
||||
|
||||
- His soul is torn apart. He has lived in a false illusion. When the truth shatters those illusions, he believes that reality itself has been shattered.
|
||||
|
||||
- At this hour, the streets of the city are empty, more like a dead city than ever before. Passersby look distant and mysterious, making one wonder if they are all ghosts. The sky is clear and cloudless, and the rising sun shines a gentle light over the earth. It’s hard to imagine that, in this joyful, fresh, and bright dawn, the city is struggling to survive under the dark reign of plague. It’s unbelievable that nature (the blue sky, so clear like a child’s heart) remains indifferent during such times.
|
||||
|
||||
- Peace exists only in people’s souls.
|
||||
|
||||
- All people, even humanity as a whole, are like droplets in this river. They flow unpredictably, getting closer together yet still far apart, forming an unnamed current that flows towards the sea. Since everything is fleeting, and nothing matters truly, people still foolishly value trivial things, causing themselves and others to suffer. This is truly sad.
|
||||
|
||||
- Those tiny droplets flow silently along the river, towards the unknown. They seem unique, but to the observer, they are just insignificant parts of the river.
|
||||
|
||||
- What do I understand now? Life is so strange. I feel like someone who has lived all his life near a small pond, suddenly seeing the vast ocean. It makes me feel breathless, but my heart is filled with joy. I don’t want to die; I want to live. So I feel a new courage. I’m like those old sailors, setting sail toward unexplored seas. My soul thirsts for the unknown.
|
||||
|
||||
- Those tiny droplets flow silently, towards that dark, eternal ocean.
|
||||
|
||||
- Path. Some of us seek the path in opium; others look to God. Still others search for whiskey, or try to find love elsewhere. There is only one such path, but it leads nowhere.
|
||||
|
||||
Everything here is both novel and touching. Her soul is like a calm yet dark ocean, with waves rising and falling in a magnificent display of beauty. The atmosphere is somber and overwhelming, making it seem daunting. Suddenly, a ray of sunlight shines through, bringing life, warmth, and joy to everything around her.
|
||||
|
||||
Life is so short. The world is already filled with suffering, yet people still choose to torment themselves. Isn’t that quite pitiful?
|
||||
|
||||
Her personality is like a rural landscape. At first glance, it seems vast and indifferent, but soon, you will discover villages hidden among the trees, along with lush grasslands where rivers flow gently. This beautiful scenery can be surprising, even comforting and reassuring. However, the distant yellow-brown hillsides, with their strong winds, still make it difficult for one to feel truly at ease.
|
||||
|
||||
There’s only one way to win people’s hearts—by becoming someone that people truly want to love.
|
||||
|
||||
I can’t put it into words. I don’t know if the pursuit of beauty is really that important, or if this very way of life itself is already beautiful enough. I think the only thing that allows us to focus on the world we live in without feeling resentment is the beauty that humans continuously create from chaos. The paintings they paint, the music they compose, the books they write… and their lives in general. Among all these things, what is most beautiful is a wonderful life—it’s a perfect work of art.
|
||||
|
||||
The “Tao” refers to both the paths that people walk along, and the beings who walk those paths. It is an eternal path, along which all living things move. Yet it wasn’t created by any living being—it exists inherently as part of life itself. It is everything at the same time, yet it isn’t anything in particular. All things originate from the Tao, conform to its principles, and ultimately return to the Tao. It is a square shape with no four corners; it is a sound that remains unheard; it is a picture without any definite shape. It is a vast net, its meshes so wide they encompass the entire ocean, yet nothing can pass through them. It serves as a refuge for all things in need of shelter. It cannot be found anywhere, but one can “see the Tao” if they refrain from seeking it. The Tao teaches us to have no desires at all, allowing everything to happen naturally. Those who are humble will ultimately achieve preservation, while those who bow down will stand upright. “Misfortune is the harbinger of fortune; fortune lies hidden beneath misfortune.” But who can say when a turning point will occur? Those who seek gentleness and compassion will be as peaceful as children. Gentleness and compassion bring victory to those who attack, and security to those who defend. Those who conquer themselves are the strongest.
|
||||
|
||||
– Freedom! That was the thought that constantly lingered in her mind. Although the future remained uncertain, this idea resembled a thin mist over a river, shining with colors under the sunlight of dawn. Freedom! It meant not only breaking free from the bonds of troubles and the oppressive relationships that weighed down her spirit. Freedom! It meant escaping not only the threat of death, but also from love that weakened her personality, from all the mental constraints that bound her spirit. Freedom meant a liberation from the physical body, a freedom for the spirit to emerge. Along with freedom came courage, and a strong will to ignore whatever might happen.
|
||||
|
||||
She thought she had changed, that her will was strong enough to allow her to return to Hong Kong as a calm and composed woman. New ideas flashed through her mind, like yellow butterflies dancing in the sunlight, filling her with hope for a better future. Freedom seemed to be calling to her, like an elf of light. The entire world seemed like a vast plain, where she could walk forward with confidence. She believed she had freed herself from lust and vile emotions, and could now live a pure and healthy spiritual life. She compared herself to herons flying gracefully over the rice fields at dusk—birds that soar freely in their tranquil minds. But in reality, she remained a slave to desire.
|
||||
|
||||
- I have hope, and I also have the courage to pursue it.
|
||||
|
||||
- You cover others’ eyes with veils, yet you yourself find it difficult to see the truth of the world.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "Quagmire"
|
||||
pubDate: 2026-02-09
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#166534'
|
||||
---
|
||||
|
||||
When lost in the darkness of the night, it’s advisable to look up at the stars. If one doesn’t know what to believe, then one must face their own conscience.
|
||||
|
||||
The history, as viscous as a mud pit, carries along all the creatures of the world. Amidst the torrents of time and the quagmire of social development, Hengling chose to commit suicide. Hengfeng was killed by stray bullets during his escape. Hengyu left this world peacefully after being redeemed. Guan Zhongqing chose to endure suffering in the aftermath of a fire. Father Matthew De, meanwhile, sought redemption and self-liberation amidst the chaos of time. In the end, he settled down in a foreign land.
|
||||
|
||||
The story begins with Father Matthew arriving in Wuhan from Belgium, aiming to save the poor people who were suffering during those turbulent times. Meanwhile, Guan Zhongqing went to Japan to study and participate in revolutionary struggles, hoping to save his country from ruin. As Guan Zhongqing returned to China and continued his revolutionary efforts, Hengling took his own life at the sound of a gunshot. From that moment on, the fates of Heng’s family, Guan Zhongqing, and Father Matthew became intertwined. The fateful spiral continued, and people began to struggle to survive in that treacherous situation.
|
||||
|
||||
After his father's death, Hengfeng was trapped in the mire of life. He embarked on a journey to find his sister, Hengyu. But everything seemed like something from another time and space—so close, yet so distant. Throughout his life, Hengfeng failed to find his sister at the last moment of his existence. Instead, due to his identity as a member of the Qi people, he was shot to death by a group of soldiers. His death was inevitable, an inevitable outcome of historical development. He was drowned in the darkness by the torrent of history. In the final moments of his life, during his conversation with Father Ma, Hengfeng reflected on his life and the sins that he could never forget. He broke free from his weak self, became brave at that moment, and stepped out of the mire. Guided by conscience, his heart headed towards light. His fate was a vivid portrayal of the transition between the old society and the new era. Dragged along by the currents of fate, he faced uncertainty and confusion. He had committed evil deeds, but there was also a trace of kindness in him. He was afraid, he was weak. Only in the last moments of his life did he become brave, trying to gain inner peace and redemption through his last shred of conscience. He anticipated his own death. In the final moments of his life, he stepped out of the mire. He felt guilty, but he bravely walked into the endless darkness.
|
||||
|
||||
Guan Zhongqing, as one of the most cultured and knowledgeable people of his time, represented the revolutionaries of the new era. The old era was too backward; struggle and revolution were the only ways to embrace the dawn. He dared to sacrifice his life for this bright future. But indeed, as a revolutionary of the new era, he still found himself trapped in the mire of time, being carried forward by the tide of history. Amid the debates among various revolutionary factions and the competition from multiple forces, he found himself trapped within a circle of new powers, each with their own motives and filled with deceit and cunning. As he was swept along by the current of history, he was forced to do so, yet he remained steadfast in his beliefs and conscience. He always upheld the belief in struggle, driving the progress of the revolution forward. He had kindness in his heart, and driven by his conscience, he saved Heng Yu, who was trapped in the mud of life and misfortune. Guan Zhongqing was complex, yet he also had a simple side.
|
||||
|
||||
Father Mathew was a witness and participant in all the events that occurred. He had an insight into the entire course of events. As a priest who sought to save others, he struggled in this mire of history, in the quagmire of human nature. He hesitated, and at times, he even felt hatred. When everything became history, he also found redemption within himself through his own actions.
|
||||
|
||||
The quagmire is the quagmire of time, the quagmire of life, the quagmire of complex human nature. Beneath the quagmire lies endless darkness, while above it stretches the vast starry sky. Believe in kindness, believe in conscience, believe in faith… and step out of this quagmire on one’s own.
|
||||
|
||||
###
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
- When lost in the darkness of the night, it’s worth looking up at the stars. If one doesn’t know what to believe, then one must face their own conscience.
|
||||
|
||||
Once the window was opened, everything from the outside rushed in. On that day, while reading “Xinmin Congbao”, he began to question the ideas of those who supported monarchy for the first time. Before that, like many other students, he believed that China should follow a moderate path towards constitutionalism, and that violent revolutions would lead to social chaos—France after the Great Revolution was a clear example of this. But this was the first time he felt unsure about this idea. Who exactly did the term “monarchy” refer to? Who was the cabinet actually composed of people? He remembered the braid on his head. After that day, whenever he looked at himself in the mirror or saw the braids of other students at school, he always felt a sense of discomfort. Before that day, he thought it was natural for Chinese people to have braids, just as everyone is born with a nose and two eyes. But now, he suddenly found these braids to be extremely ugly and offensive, like an unnecessary organ on a person’s body. People with braids seemed like deformed anomalies.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -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,49 @@
|
||||
---
|
||||
title: "Atonement"
|
||||
pubDate: 2026-02-26
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#9f1239'
|
||||
---
|
||||
|
||||
### You must come back, I will wait for you.
|
||||
|
||||
It was because of a person, because of a picture, because of a line of dialogue, because of reading a book.
|
||||
|
||||
“Dear Cecilia, our fate remains unfinished.”
|
||||
|
||||
Through a series of misunderstandings, an unbreakable love turned into the most absurd tragedy due to a child’s false accusation. Cecilia and Robbie’s love began in their childhood, with no secrets between them. It started with a broken vase by the pool, leading to a series of incredible events. When Robbie bravely expressed his passionate love to Cecilia for the first time, that moment determined that they would always be together forever.
|
||||
|
||||
Brianna’s false accusations led to Robbie’s imprisonment. But three years in prison did not extinguish the flame of their love. Being released from prison became like a lighthouse for Robbie during those three years, helping him endure the hardships and darkness. After three years, they thought they would meet again, but what awaited Robbie and Cecilia was the cruelty of World War II. After being released, Robbie rushed across the English Channel to join the war against Germany. Under the brutal war conditions, life became like imprisonment again. On the battlefield, there were constant dangers, and Robbie was always on the verge of death. Their letters kept him going, enabling him to survive on the battlefield, just for her sake. “You must come back, I will wait for you.” This witnessed their great and faithful love.
|
||||
|
||||
***False accusations and death cannot stop a great love.***
|
||||
|
||||
### There is no self in the face of war
|
||||
|
||||
McEwen’s skillful writing style allows readers to easily understand the cruelty of war and the insignificance and despair of individuals. In the first part, Robbie Turner is still the vibrant young man who could be called Robbie. But by the time the war begins, Robbie Turner has become just a synonym for “Turner,” not the Robbie who had his own identity. In war, he has no choice but to continue fleeing, one defeat after another.
|
||||
|
||||
During his escape, he witnessed everything becoming fragmented in the face of war. Even when he chose to follow his conscience at some point, he couldn’t change reality. He continued to see the deaths of young people and the craters left by bombs. This was a stark contrast to what he saw in the small town where he graduated. There were no clear lakes, no green lawns, no orderly streets or villages. Instead, there was only endless smoke, constant noise, and endless bloodshed, along with rotting corpses and their stench.
|
||||
|
||||
In war, survival becomes the only important thing, and also the only difficult thing.
|
||||
|
||||
### Atonement
|
||||
|
||||
If war hadn’t come, perhaps all this suffering would have been caused by Brianna’s selfish “adult thoughts”. For a 13-year-old girl, could she truly understand what love really means? What lies behind all these things she sees?
|
||||
|
||||
*I am old, silent, and too attached to my remaining life. I face a wave of forgetting, followed by permanent oblivion... Love never ends. As long as my last manuscript remains, my pure and unique sister and her prince will love each other forever... I believe that bringing the lovers in my novels together is not a sign of weakness or avoidance, but rather a great act of kindness, a resistance against forgetfulness and despair. I gave them happiness, but it wasn’t out of selfishness. I hope they will forgive me.*
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
- But what does “guilt” mean in this era? That question doesn’t make sense anymore. Everyone is guilty or innocent. No one can be saved just because of one testimony. There aren’t enough people, enough pens and paper, enough peace and patience to record all the testimonies and collect all the sins committed by everyone. Did you not kill anyone today? But how many people’s deaths did you turn a blind eye to?
|
||||
|
||||
- The river flows with spring tides, towards the sea between blue and green colors.
|
||||
|
||||
- I am old, silent, and too attached to my remaining life. I face a wave of forgetting, followed by permanent oblivion... Love never ends. As long as my last manuscript remains, my pure and unique sister and her prince will love each other forever... I believe that bringing the lovers in my novels together is not a sign of weakness or avoidance, but rather a great act of kindness, a resistance against forgetfulness and despair. I gave them happiness, but it wasn’t out of selfishness. It’s not like that, it’s not even that bad.
|
||||
|
||||
- After our hardships, love begins to grow.
|
||||
|
||||
- You must come back, I will wait for you.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: "Spec Harness in Depth: Packaging Long-Horizon Development into Verifiable Task Packages"
|
||||
pubDate: 2026-11-01
|
||||
category: tech
|
||||
tags: [Engineering, AI Collaboration]
|
||||
locked: true
|
||||
---
|
||||
|
||||
What AI coding agents lack is not bandwidth for writing code — it is memory. A task that spans several sessions accumulates half-finished plans, already-falsified approaches, and acceptance criteria that get lost in chat history; restart the terminal and the context resets to zero. [Spec Harness](https://github.com/MicTx/spec-harness) targets exactly this: a task-package workflow tool for AI coding agents that persists long-horizon changes as a **recoverable, verifiable Development Record**, governed by scope, evidence, and Git gates. It provides no model and makes no product decisions for you.
|
||||
|
||||
Its worldview in one sentence: **when a coding task outlives one conversation, the missing tool is not a smarter model — it is a contract that everyone (including your future self) can read.**
|
||||
|
||||
## 1. The core abstraction: the task package
|
||||
|
||||
All work lives in a `.spec/specs/YYYY-MM-DD_slug/` directory with two core files: `spec.md` records scope, assumptions, and trade-offs (including a remarkable "falsified paths" section, more on that below), while `tasks.md` holds the task list. After closeout the whole package is archived under `.spec/specs/archive/` — the archive directory is the project's engineering log.
|
||||
|
||||
Every task must carry a two-line contract: **boundary** (which files this task may touch) and **verify** (how completion is proven). The verify is a read-only assertion that must exit 0 before the checkbox goes on. A real example from this blog's archived `2026-10-26_blog-5f` package, task T3:
|
||||
|
||||

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

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

|
||||
|
||||
## 8. Link cards
|
||||
|
||||
A bare URL on its own line becomes an OG preview card at build time; if the fetch fails it degrades to a plain link, never breaking the layout:
|
||||
|
||||
https://astro.build
|
||||
|
||||
## 9. Folding long content
|
||||
|
||||
Long appendices, full logs and big configs can fold away with zero JavaScript, using native `details`:
|
||||
|
||||
<details>
|
||||
<summary>Open the folded example</summary>
|
||||
|
||||
A folded region can hold anything — paragraphs, lists, code blocks.
|
||||
|
||||
- Best for “read on demand” detail
|
||||
- Not for conclusions (readers may never open it)
|
||||
|
||||
</details>
|
||||
|
||||
## 10. Closing after the rule
|
||||
|
||||
---
|
||||
|
||||
Three sentences are enough to close: what this covered, what the reader should take away, and where to go next. The template is not a demand to write ten sections — it is a promise that **the capabilities are at hand when you need to express something**.
|
||||
|
||||
Next time, copy this file and delete until only your point remains.
|
||||
@@ -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: 2.1 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。*
|
||||
@@ -4,6 +4,8 @@ pubDate: 2026-03-09
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#7c3aed'
|
||||
bookAuthor: '威廉·萨默塞特·毛姆'
|
||||
description: '面纱之下:理想的殉道者与真实的幸存者。'
|
||||
---
|
||||
|
||||
****面纱之下:理想的殉道者和真实的幸存者****
|
||||
|
||||
@@ -4,6 +4,8 @@ pubDate: 2026-02-09
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#166534'
|
||||
bookAuthor: '刘楚昕'
|
||||
description: '迷失于黑夜时,不妨抬头看看星空。'
|
||||
---
|
||||
|
||||
### 迷失在黑夜中时,不妨抬头看看星空;如果不知道该相信什么,人应当面对自己的良知。
|
||||
|
||||
@@ -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 紧急处置,另一边赶去补刀的编辑被预约机制正确拦下——撞车靠规则防住,不靠谁手快。
|
||||
|
||||
四件套加起来没有一个数据库、没有一个中心大脑,却把"谁在线、谁在干啥、学到过啥、长活谁看着"四件事全部钉死。小工具、硬边界、诚实状态——这套哲学比任何一个具体包都值得抄。
|
||||
@@ -4,6 +4,8 @@ pubDate: 2026-02-26
|
||||
category: reading
|
||||
tags: [read]
|
||||
mood: '#9f1239'
|
||||
bookAuthor: '伊恩·麦克尤恩'
|
||||
description: '因一个人、一张图、一句台词,看了一本书。'
|
||||
---
|
||||
|
||||
### 你要回来,我会等你。
|
||||
|
||||
@@ -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,124 @@
|
||||
---
|
||||
title: "写作模板:这篇文章本身就是模板"
|
||||
pubDate: 2026-11-02
|
||||
category: tech
|
||||
tags: [写作, 模板]
|
||||
pinned: true
|
||||
description: 一篇文章演示全站可用的排版能力:标题层级、彩色强调、字族、引用、代码窗、表格、图、链接卡、折叠。复制它开始写新文。
|
||||
---
|
||||
|
||||
这篇不是教程,是**可复制的模板**。下面每一节都在真跑——你看到的效果,就是 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,21 @@
|
||||
tagline:
|
||||
zh: 写作者 · 读者 · 快门按压者,拥抱开源。
|
||||
en: Writer · Reader · Shutter-presser — embracing 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: RSS
|
||||
url: /rss.xml
|
||||
icon: rss
|
||||
- label: 邮件
|
||||
url: mailto:X@alumni.nudt.edu.cn
|
||||
icon: email
|
||||