用户在 2026-09-15 提出实际使用场景(原话):「有个场景,我收集了多篇网络文章,每篇有自己的音频,要怎样来实现」;随后补充**「书籍封面只有一个,我想给每章节加个图片,当插图或章节图片,增加阅读氛围和兴趣」。经讨论确认组织方式为「多篇文章合并成一本书的多章」,因此附件挂载单位需要从「每本书一个音频」改为「每个章节一个音频」,并新增章节插图**(一章一张,固定展示在该章正文开头);封面维持书级不变。
本单在已验收的 #21 书级实现(PR #36,commit ab23ff6)基础上继续改,不推倒重做:类型嗅探、Range/ETag 包装、播放器组件、带凭据 fetch + 对象 URL 的机制全部复用。用户已确认这一处理方式。完整契约草案见 #21 评论 8129。
ab23ff6
lexgo_book_attachments
kind='cover'
coverVersion
POST/DELETE/GET /api/v1/books/:id/audio
PUT /api/v1/books/:id/playback
多张插图、正文中间图文混排、缩略图墙式独立浏览界面、多音轨与分章音轨、字幕与时间戳同步、逐句跟读、转写、TTS、音频转码、自动搜索封面或插图、在线音频地址抓取、公开分享与跨用户共享。
ETag
304
kind='audio'
前置:#21(已验收,代码基础)、#10(章节编辑与删除入口、级联)、#15(备份恢复契约)。状态:待验收(2026-09-15,提交 0fd661e,PR #38;线框与契约见评论 8152,实施证据见最新评论)。
设计证据门禁:新增交互(章节列表行内的插图缩略图与「音频 · 插图」编辑入口、编辑对话框内的上传/替换/移除、阅读页插图位置)需先出标注线框 + 状态说明(与 #21 相同的 (b) 形式),经用户确认后再编码。
初估 5~8 人日(含设计证据、前后端、测试与文档;是规划参考,不是排期承诺)。主要风险:#21 刚交付的书级音频功能整体作废,相关实现与测试投入不再产生用户可见价值;迁移会清空书级音频测试数据且回退到 v8 无法找回(当前无真实用户,代价可接受)。回退:v9 只新增两张表,写回 lexgo_schema.version=8 并恢复上一版二进制即可。
lexgo_schema.version=8
后续如需再次评估附件挂载粒度,应先确认真实使用场景,避免第三次返工。
实施时更新 Architecture-and-Code-Map、Business-Rules-and-Glossary、Local-Development-and-Verification、Product-Requirements-Overview、Home 与 Deployment-and-Operations;#15 的恢复演练第三次重跑并把结果写入本单。结束评论记录测试、未验证内容、提交与 Wiki revision;工单正文保留基线,重要变化追加评论。
Gitea MCP 仍指向其他站点,沿用目标站点 API 回退;凭据仅从既有安全配置读入进程。
用户 2026-09-15 确认「接受」,即同意按以下线框评审 #37 的界面改动。按本单正文的门禁,线框需用户确认后才编码,因此本评论交付设计证据与增量契约,工单保持「待确认」。
线框按现有真实布局绘制:书籍页章节列表当前是「序号 · 标题/字符数 · 已读标记 · 状态标签 · 重试 · 编辑」的网格行,章节编辑对话框当前是「章节标题 · 正文 · 字符计数 · 取消/删除章节/保存章节」。
封面仍是书级,coverVersion 缓存失效机制与卡片排版保持 #21 已验收的样子。
封面与章节附件 ┌────────────────────────────────────────────────────────────┐ │ 封面 [预览 160×90] 已设置 · PNG · 0.5 MB │ │ [替换封面] [移除] │ │ 提示:音频与插图按章节设置,见下方章节列表。 │ └────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐ │ 1 │ [缩略图] Fictional chapter [已读][已就绪] │ │ │ 1204 字符 · 🎧 有音频 · 上次 1:05 [附件][编辑] │ └──────────────────────────────────────────────────────────────────────┘
复用书级附件区块的排版(预览 + 已设置状态 + 操作按钮 + 规格提示):
编辑章节附件:Fictional chapter ┌──────────────────────────────────────────────────────────┐ │ 插图 [预览 160×90] 已设置 · JPG · 0.3 MB │ │ [替换插图] [移除] │ │ JPG、PNG 或 WebP,≤2 MiB,像素 ≤4096×4096 │ │ │ │ 音频 MP3 · 3.4 MB · 上次播放位置 1:05 │ │ [上传音频] [替换] [移除] │ │ MP3,≤20 MiB;不与文本同步;替换或移除会重置位置 │ └──────────────────────────────────────────────────────────┘ [关闭]
[顶部导航] ┌──────────────────────────────────────────────────────────┐ │ ▶ 0:12 ──────●──────────── 12:30 1.0× ↻重试 │ ← 仅该章有音频时出现 └──────────────────────────────────────────────────────────┘ 章节标题 [已就绪] ┌──────────────────────────────────────────────────────────┐ │ [插图,宽度跟随正文栏,最大高度 40dvh] │ ← 仅该章有插图时出现 └──────────────────────────────────────────────────────────┘ 正文…
保留不动:封面(书级表、书级接口、coverVersion)、magic bytes 类型嗅探与尺寸校验、binaryResponse + http.ServeContent 的 Range/ETag/条件请求包装、AudioPlayer.vue、session.requestBlob 取字节转对象 URL、上传"先校验后写入、失败保留旧附件"的流程。
binaryResponse
http.ServeContent
AudioPlayer.vue
session.requestBlob
退役:POST/DELETE/GET /api/v1/books/:id/audio、PUT /api/v1/books/:id/playback;lexgo_book_attachments 的 kind='audio' 不再接受新写入。
新增(schema v9,两张新表 + 一条幂等清理语句):
lexgo_chapter_attachments
(chapter_id, kind)
kind ∈ {audio, illustration}
owner_id
mime
byte_size
sha256
bytes MEDIUMBLOB
lexgo_chapter_playback_positions
(owner_id, chapter_id)
position_seconds
updated_at
DELETE FROM lexgo_book_attachments WHERE kind='audio'
lexgo_playback_positions
新接口:POST/DELETE/GET /api/v1/chapters/:id/{audio,illustration}、PUT /api/v1/chapters/:id/playback;书籍详情的章节列表增加「插图版本号 / 是否有音频 / 上次位置」。
POST/DELETE/GET /api/v1/chapters/:id/{audio,illustration}
PUT /api/v1/chapters/:id/playback
校验:音频规则与上限不变;插图与封面共用同一套校验(JPG/PNG/WebP、≤2 MiB、≤4096×4096、按内容判定)。
测试与证据:在 v8 用例上新增插图类型/尺寸校验、章节删除级联、v8→v9 迁移与回退重升级、书级音频接口已下线的确认、历史音频行被清理;前端补 S2b/S2c/S3 的单测与 390×844 触摸视口 e2e;#15 恢复演练第三次重跑(附件按章节归属、行数与 sha256 一致、恢复后仍可按章播放与显示插图)。
回退:v9 只新增两张表;写回 lexgo_schema.version=8 并恢复上一版二进制即可,但迁移已清空的书级音频测试数据找不回(当前无真实用户,工单已记录此代价)。
用户 2026-09-15 回复「可以」,确认评论 8152 的线框(S2 收窄为封面、S2b 章节行缩略图与「附件/编辑」两个入口、S2c 独立的章节附件对话框、S3 章级播放器与正文上方插图、S4 账号+章节的位置规则)与增量契约。设计证据按该评论冻结。分支 feat/37-chapter-attachments 从 main 8a25b7b 创建。
feat/37-chapter-attachments
8a25b7b
实施顺序:schema v9(两张新表 + 幂等清理书级音频行)→ 退役书级音频接口、新增章级附件与位置接口 → 章节列表与阅读响应带章级附件元数据 → 学习端 S2/S2b/S2c/S3 → 测试(含 v8→v9 迁移与回退)→ 真实链路验证 → #15 恢复演练第三次重跑 → 文档改为章级口径。完成后停在待验收。
用户 2026-09-15 回复「可以」,确认评论 8152 的线框与增量契约。分支 feat/37-chapter-attachments 从 main 8a25b7b 创建,提交 0fd661e 已推送;PR #38 未合并,工单停在待用户验收。
0fd661e
chapter_id+kind
owner_id+chapter_id
illustrationVersion
audioVersion
playbackSeconds
audioChapterId
go vet
gofmt -l
LEXGO_TEST_DB_NAME=lexgo_test_issue13 python scripts/server.py test-integration
vitest
vue-tsc
build
playwright test
check --strict
sync --check
迁移:schema v8→v9,开发库逐表业务计数无变化,schema 记到 9;本机 lexgo-api 已用新二进制重启,回退件 .local/lexgo-pre-issue37.exe。
.local/lexgo-pre-issue37.exe
fabdaf13861b4cae9ad6cd66a5bd904c08361f73
b9eda78d3e71
v9 只新增两张表:停止 lexgo-api、把 lexgo_schema.version 写回 8、恢复 .local/lexgo-pre-issue37.exe 后重启;既有书籍与学习数据不受影响,但被清空的书级音频行无法找回。
lexgo_schema.version
上一条实施评论里把 Business-Rules、Local-Development、Product-Requirements 与 Home 的 revision 写成「见下一条评论」;实际 revision 如下(本次发布,含章级口径改写):
a4b1832fcda35aac0587ca0ef05006a8baefcd3d
2c81572818a57c8cc1b2e42792f7aed6bbaef05b
e0e0b98e74a2e920e78928ff69cdd92e145ccb88
2512f727759bababca9de6697552da0f8c3f09ea
37324e022b16323eef302d1524cabe27607a3934
b9eda78d3e71dab02dbf2e3af700c81d9c725f8d
镜像校验:harness.py sync --check 通过,并对 6 个变更页做逐字节正文比对(全部一致)。站点在本次发布中对连续请求返回过 429/503,已按退避重试完成,未跳过任何页面的回读确认。
harness.py sync --check
用户 2026-09-15 指出:「不要在书籍页的章节列表展示章节图片,进入章节内容里,缩略图展示,点击图片后弹窗查看原始图片」,并给出示例页面 /books/5。这条指令覆盖评论 8152 中 S2b 与 S3 的插图部分,其余(封面仍书级、音频章级、位置账号+章节、S2c 附件对话框、S4 状态规则)不变。
/books/5
S2b 章节列表行(去掉缩略图列)
┌──────────────────────────────────────────────────────────────────────┐ │ 1 │ Fictional chapter [已读][已就绪] │ │ │ 1204 字符 · 🎧 有音频 · 上次 1:05 [附件][编辑] │ └──────────────────────────────────────────────────────────────────────┘
S3 阅读页(缩略图 + 弹窗看原图)
[顶部导航] ┌──────────────────────────────────────────────────────────┐ │ ▶ 0:12 ──────●──────────── 12:30 1.0× ↻重试 │ ← 仅本章有音频时出现 └──────────────────────────────────────────────────────────┘ 章节标题 [已就绪] ┌───────────────┐ │ [缩略图] │ ← 高 120px、宽度自适应,下面一行小字「点击查看大图」 └───────────────┘ 正文…
aria-label
min(88vw, 1200px)
78vh
BookView.vue
library.ts
ReaderView.vue
ElDialog
.illustration-thumb
.illustration-full
<img>
/illustration
img
src
naturalWidth > 0
.local/evidence/issue37-real-book-no-image.png
issue37-real-reader-thumbnail.png
issue37-real-illustration-dialog.png
按用户要求把章节插图从「书籍页列表缩略图 + 阅读页大图」改为「只在阅读页显示缩略图,点击弹窗看原图」,并去掉书籍页章节列表的图片列(同时不再为列表预取图片)。设计证据修订见评论 8167。
提交 3ab26af(同一分支 PR #38,未合并)。
3ab26af
验证:学习端 157 项单测与 26 项 E2E 全部通过(新断言:章节列表无 <img> 且不请求 /illustration;缩略图是按钮;点击后弹窗内图片 src 为对象 URL);真实链路(真实 API+MySQL+真实浏览器)在 issue5_a 自己的一本书上完成:列表 <img> 为 0、缩略图高 ≤130px、弹窗内图片大于缩略图且 naturalWidth > 0、Esc 关闭后正文仍可见,随后移除 fixture 插图复原数据。示例地址 /books/5 属于其他账号(返回 404),实际使用该账号自己的书。
后端与 schema 无变化(仍是 v9),因此 #15 恢复演练不需要重跑。
文档 revision(本次发布):
Architecture-and-Code-Map: b9666699e8f01a117412b0b65880b0402b1bef0c
b9666699e8f01a117412b0b65880b0402b1bef0c
Business-Rules-and-Glossary: a226a6026e8415d6f2981fc87b52396f30ab3708
a226a6026e8415d6f2981fc87b52396f30ab3708
Local-Development-and-Verification: 0550efcfd3844fb0af2dd7e59471f24f444eadf0
0550efcfd3844fb0af2dd7e59471f24f444eadf0
Product-Requirements-Overview: a54b9c33ece05a90cc28254273777dff9388167b
a54b9c33ece05a90cc28254273777dff9388167b
Home: 6ff5329b98016970310da9f8f6d2c5e821e39714
6ff5329b98016970310da9f8f6d2c5e821e39714
Deployment-and-Operations: b9eda78d3e71dab02dbf2e3af700c81d9c725f8d
镜像校验:harness.py sync --check 通过,6 个变更页逐字节正文比对一致。
用户提出:章节列表每行的「附件」与「编辑」合成一个——保留「编辑」,点开后在同一编辑页里编辑章节标题、内容、插图和音频。已按此实施。
合适:一个入口、一处编辑,章节行更干净,符合「编辑这一章」的直觉;正文改版(重新处理)不会影响已上传的插图与音频,因为它们挂在章节上。
需要说清的唯一风险:同一个对话框里有两种生效时机——标题与正文点「保存章节」后生效,而文件是选中即上传。用户可能以为点「取消」什么都没发生,其实图片/音频已经存进去了。我没有改成延迟上传(那会引入「正文保存成功、文件上传失败」这类半成品状态,更复杂),而是在对话框里明确写出这一点:data-testid="attachment-timing" 的文案为「标题与正文点「保存章节」后生效;插图与音频在选中文件后立即上传,取消不会撤销已上传的文件。」
data-testid="attachment-timing"
data-testid="edit-chapter-{id}"
学习端 157 项单测与 26 项 E2E 全部通过(新断言:章节行只有一个按钮;编辑对话框同时包含标题、正文、插图与音频控件以及生效时机说明;在对话框内上传后仍可用「保存章节」提交标题与正文)。
真实链路(真实 API+MySQL+真实浏览器,issue5_a 自己的书):章节行按钮数 = 1 且文本为「编辑」;对话框内标题/正文/插图状态/音频状态/时机说明齐备(截图 issue37-real-chapter-editor.png);在编辑框内上传真实 PNG 后状态立即变为「已设置」而标题仍是原值(说明文件即时生效、文本等待保存);取消后进入阅读页仍为缩略图+弹窗原图;最后移除 fixture 插图复原数据。
issue37-real-chapter-editor.png
文档 revision:
Architecture-and-Code-Map: c75593d72432e5195ea84c766a81449ccb91f648
c75593d72432e5195ea84c766a81449ccb91f648
Business-Rules-and-Glossary: 081daac73fa8d172a477d196e964382735122466
081daac73fa8d172a477d196e964382735122466
Local-Development-and-Verification: 5459d79e2bf6339450af5add58a61ef22b0467ea
5459d79e2bf6339450af5add58a61ef22b0467ea
Product-Requirements-Overview: 5ecd946c6731afbbb87f0ab3d68988583fa4e2bb
5ecd946c6731afbbb87f0ab3d68988583fa4e2bb
Home: 7a96206070fbd799d76a12ec07dd4520c2be1a81
7a96206070fbd799d76a12ec07dd4520c2be1a81
若这个合并方式与「立即上传+明确文案」的处理可以接受,本单即可验收;若你希望文件也改成「点保存才上传」(可能出现部分成功),或希望在关闭时对未保存的正文做二次确认,请指出。
用户追问「为什么还是在章节列表下面展示编辑区域呢,可以弹窗或新 tab 吗」——查明确实是缺陷,谢谢指出。
learner/src/main.ts 只按需导入了 button/input/radio/select/option/dropdown 的 Element Plus 样式,从未导入 dialog/style/css 与 message-box/style/css。因此自 #10 起,所有 ElDialog(书名编辑、章节编辑、生词库编辑、本次新增的章节编辑与插图弹窗)和 ElMessageBox(删除确认等)都没有样式,退化成文档流中的普通块,看起来就像"长在章节列表下面"。
learner/src/main.ts
dialog/style/css
message-box/style/css
ElMessageBox
探针实测(同一页面、同一操作):
position
.el-overlay
static
top = 648px
relative
fixed
top = 92px
rgba(0,0,0,0.5)
main.ts
learner/e2e/overlay.ts
expectModalOverlay(page, dialog)
expectMessageBoxOverlay(page)
position: fixed
attachments.spec.ts
edit.spec.ts
此前所有 UI 验证只断言「元素可见 / DOM 结构 / 交互结果」,而 toBeVisible() 对文档流里的普通块同样成立,所以 16 单测 + 26 E2E 全绿也没能发现它;截图我无法查看,也就没看出来。这条教训已写入 Wiki 的本地验证页:涉及"浮层/遮挡/布局"的属性必须显式断言,不能只依赖可见性。
toBeVisible()
issue37-real-dialog-modal.png
issue37-real-confirm-modal.png
c22f9c3
现在它是弹窗(模态浮层):点「编辑」后覆盖在页面上、带遮罩、Esc 或「取消」关闭。如果你更希望它打开独立页面/新标签(例如 /chapters/:id/edit 整页编辑),那是一次导航结构变化,我可以改——请说一声要哪种(以及是否要保留弹窗方式作为快速编辑)。
/chapters/:id/edit
按用户两点要求实施:①「章节标题」标签与输入框同一行,下面新增「作者」标签与输入框;②精简插图与音频的提示文字,把高度让给正文编辑框。
lexgo_chapters 原本没有作者字段,所以这不是纯前端改动:本单新增 schema v10 —— author VARCHAR(120) NOT NULL DEFAULT ''。因为 MySQL 没有 ADD COLUMN IF NOT EXISTS,加列写成 Go 条件步骤 addChapterAuthorColumn(先查 information_schema,缺列才 ALTER TABLE),并且放在语句列表执行之后,这样既有的两条性质仍然成立:部分迁移可重试、回退版本标记后可重新升级;新建库的 v3 建表语句也直接带上该列。开发库迁移后 schema = 10,既有 34 个章节的 author 均为空串(无数据变化)。
lexgo_chapters
author VARCHAR(120) NOT NULL DEFAULT ''
ADD COLUMN IF NOT EXISTS
addChapterAuthorColumn
information_schema
ALTER TABLE
ChapterSummary
ChapterSource
data-testid="chapter-author-line"
.field-row
TestMySQLChapterAuthorRoundTrip
TestMigrationFromV9AddsChapterAuthor
issue37-real-editor-layout.png
issue37-real-reader-author.png
.local/lexgo-pre-issue37b.exe
提交 3fe9bcb(同一分支 PR #38,未合并)。
3fe9bcb
Architecture-and-Code-Map: 2ccdc108a21abe67ad77c05af59bb34bac304093
2ccdc108a21abe67ad77c05af59bb34bac304093
Business-Rules-and-Glossary: 77e1f1291a201744bdbb556ed1ef7d81cef2f969
77e1f1291a201744bdbb556ed1ef7d81cef2f969
Local-Development-and-Verification: 92144cd570153b560180e195c49524fdb95a1048
92144cd570153b560180e195c49524fdb95a1048
Product-Requirements-Overview: 1fc22dc050d51e4d4013d2a58b914f7b972116ca
1fc22dc050d51e4d4013d2a58b914f7b972116ca
Home: dd5bc8b5423219fcc90d772bafeb56845644d16b
dd5bc8b5423219fcc90d772bafeb56845644d16b
按用户三点要求实施:①书籍页去掉「封面」「书籍封面」「JPG、PNG 或 WebP,最大 2 MiB,像素不超过 4096×4096。」「音频与插图按章节设置,见下方章节列表的「附件」。」四段文字,空间让给章节列表;②「编辑书名」改为**「编辑书籍」**,可改书名与作者;③书名右侧显示当前书籍作者。
书级作者是新字段:lexgo_books.author VARCHAR(120) NOT NULL DEFAULT ''。开发库已在 v10,所以这次是 v11;加列复用 v10 的条件步骤(改名 addAuthorColumn,表名取自本文件常量,先查 information_schema 再 ALTER TABLE,在语句列表之后执行),「部分迁移可重试 / 回退标记后可重新升级」仍然成立;新建库的 v3 建表语句也带该列。开发库迁移后 schema = 11,既有 25 本书 author 全为空串(无数据变化)。
lexgo_books.author VARCHAR(120) NOT NULL DEFAULT ''
addAuthorColumn
书籍作者与章节作者是两份互相独立、都可选的自由文本(各 ≤120 字符,各自 trim,留空即清空):书级作者显示在书籍页书名右侧、由「编辑书籍」维护;章节作者显示在阅读页标题下、由章节「编辑」维护。章节不会继承书级作者,两者都不参与归类、检索或权限判断。省略作者字段的编辑请求保留原值。
PATCH /api/v1/books/:id
author
BookSummary
BookRef
.title-line
TestMySQLBookAuthorRoundTrip
TestMigrationFromV10AddsBookAuthor
issue37-real-book-page.png
.local/lexgo-pre-issue37c.exe
提交 728f7d6(同一分支 PR #38,未合并)。
728f7d6
Architecture-and-Code-Map: 0bca600ea0dfcb18141920f448954bb9e2fa067c
0bca600ea0dfcb18141920f448954bb9e2fa067c
Business-Rules-and-Glossary: b688ef278f83c8055e16f91843a239f9ea2dd492
b688ef278f83c8055e16f91843a239f9ea2dd492
Local-Development-and-Verification: 8985f93f7319b2e6a428fc4417f353a7b909d797
8985f93f7319b2e6a428fc4417f353a7b909d797
Product-Requirements-Overview: 21ca050a33bdbe8133c921b6b302fbfc8d8cf7fe
21ca050a33bdbe8133c921b6b302fbfc8d8cf7fe
Home: da0f9a0feb70b7099edcf8adf50a18a15435dd33
da0f9a0feb70b7099edcf8adf50a18a15435dd33
审核对象:分支 feat/37-chapter-attachments,提交 0fd661e→728f7d6(PR #38)。这单在实施过程中经历了 6 轮迭代(章级音频/插图 → 插图交互改缩略图+弹窗 → 附件并入章节编辑对话框 → 修复对话框样式缺陷 → 章节作者字段 → 书籍作者字段),只读审阅代码,没有重跑测试。
database.go
CREATE TABLE IF NOT EXISTS
DELETE
information_schema.COLUMNS
table
attachment.go
registerAttachmentRoutes
/api/v1/books/:id/audio
/api/v1/books/:id/playback
/books/:id/cover
/chapters/:id/{audio,illustration}
/chapters/:id/playback
SaveChapterAttachment
kind=audio
ChapterPlaybackPosition
DeleteChapterAttachment
SaveChapterPlaybackPosition
ON DELETE CASCADE
lexgo_books
评论 8178 报告的"对话框退化成文档流块"是这几轮审核里遇到的最严重的一次真实缺陷——从 #10 起、全站所有 ElDialog 和 ElMessageBox 都没有样式,根源是 main.ts 按需引入 Element Plus 组件样式时漏了 dialog/style/css 和 message-box/style/css 两行。我核对了 main.ts,确认两行已经补上。这个缺陷本身很难通过"元素可见"类断言发现(toBeVisible() 对文档流里的普通块同样成立),pi 如实记录了这是"157 单测+26 E2E 全绿也没能发现"的验证盲区,并补充了显式断言浮层定位属性的新用例(expectModalOverlay),把方法论教训写回了 Wiki——这是审核里比较少见的、主动暴露自己验证方法缺陷的记录方式,值得肯定。
expectModalOverlay
达标,可以进入用户验收。 尽管这单经历了多轮范围追加(几乎变成了另一个功能:章节/书籍作者字段),但每次都有对应的设计确认和测试补充,没有出现"先斩后奏"或"隐瞒代价"的情况;两次 schema 变更都清楚说明了后果(回退到 v8 会永久丢失测试期的书级音频数据),这个代价在无真实用户的当前阶段是可接受的。
本次审核只读代码,没有重跑 test-integration、learner 的 vitest/E2E 与 #15 恢复演练;pi 报告的 91 项集成用例、157 项前端单测、26 项 E2E、52 项真实链路检查、22 项恢复演练等结果未被本次复核重复验证,如需更高把握建议在验收前独立重跑一次,尤其是恢复演练(涉及数据可恢复性,风险等级较高)。
test-integration
工单末尾还有两个开放问题没有答复(书级作者是否显示到书库卡片、章节作者是否继承书级作者留空时的默认值),这些不影响"达标"结论,但建议在最终验收前一并决定,避免验收后又要开新一轮迭代。
Gitea MCP 仍指向其他站点,本次沿用已记录的目标站点 API 回退;凭据只从 ~/.claude/gitea.env 安全配置读入进程。
~/.claude/gitea.env
用户于 2026-09-16 回复「#37 通过验收」(此前回复中提到的 #3 已于 2026-09-10 验收关闭,本单为实际待验收项,已在会话中核对确认)。代码审核见评论 8197:达标,可以进入用户验收,未提出待改项。
-b
-c
c293a41
3cf18f98cf824dd3bbd5e8c300a09a321e47b357
daf31e0d00508a7d0cf504a62072ed69322a5644
7c41d62a131bfcc8c00a3b42a9830600a31f9c6a
3af7d77905777a9b0725e5be755eb4a7f461cad7
39adde482a8729fcf303ef7ca24cd8bf136c58a6
lexgo_schema.version=9
单元工单关闭;#16 的 #37 复选框同步勾选。
No dependencies set.
The note is not visible to the blocked user.
来源与目标
用户在 2026-09-15 提出实际使用场景(原话):「有个场景,我收集了多篇网络文章,每篇有自己的音频,要怎样来实现」;随后补充**「书籍封面只有一个,我想给每章节加个图片,当插图或章节图片,增加阅读氛围和兴趣」。经讨论确认组织方式为「多篇文章合并成一本书的多章」,因此附件挂载单位需要从「每本书一个音频」改为「每个章节一个音频」,并新增章节插图**(一章一张,固定展示在该章正文开头);封面维持书级不变。
本单在已验收的 #21 书级实现(PR #36,commit
ab23ff6)基础上继续改,不推倒重做:类型嗅探、Range/ETag 包装、播放器组件、带凭据 fetch + 对象 URL 的机制全部复用。用户已确认这一处理方式。完整契约草案见 #21 评论 8129。已确认的范围
lexgo_book_attachments的kind='cover'、书级读写接口与coverVersion缓存机制原样保留)POST/DELETE/GET /api/v1/books/:id/audio、PUT /api/v1/books/:id/playback)整体下线非目标
多张插图、正文中间图文混排、缩略图墙式独立浏览界面、多音轨与分章音轨、字幕与时间戳同步、逐句跟读、转写、TTS、音频转码、自动搜索封面或插图、在线音频地址抓取、公开分享与跨用户共享。
验收标准
ETag/304;跨账号读写与位置上报一律 404,匿名 401。kind='audio'的书级历史行按已确认方式清空、不做存量迁移;写回版本号回到 v8 时能看到(已清空的)书级接口,此代价在工单记录。依赖与执行
前置:#21(已验收,代码基础)、#10(章节编辑与删除入口、级联)、#15(备份恢复契约)。状态:待验收(2026-09-15,提交 0fd661e,PR #38;线框与契约见评论 8152,实施证据见最新评论)。
设计证据门禁:新增交互(章节列表行内的插图缩略图与「音频 · 插图」编辑入口、编辑对话框内的上传/替换/移除、阅读页插图位置)需先出标注线框 + 状态说明(与 #21 相同的 (b) 形式),经用户确认后再编码。
估算、风险与回退
初估 5~8 人日(含设计证据、前后端、测试与文档;是规划参考,不是排期承诺)。主要风险:#21 刚交付的书级音频功能整体作废,相关实现与测试投入不再产生用户可见价值;迁移会清空书级音频测试数据且回退到 v8 无法找回(当前无真实用户,代价可接受)。回退:v9 只新增两张表,写回
lexgo_schema.version=8并恢复上一版二进制即可。后续如需再次评估附件挂载粒度,应先确认真实使用场景,避免第三次返工。
文档与证据
实施时更新 Architecture-and-Code-Map、Business-Rules-and-Glossary、Local-Development-and-Verification、Product-Requirements-Overview、Home 与 Deployment-and-Operations;#15 的恢复演练第三次重跑并把结果写入本单。结束评论记录测试、未验证内容、提交与 Wiki revision;工单正文保留基线,重要变化追加评论。
Gitea MCP 仍指向其他站点,沿用目标站点 API 回退;凭据仅从既有安全配置读入进程。
#37 设计证据(形式 b:标注线框 + 状态说明)与增量契约(2026-09-15)
用户 2026-09-15 确认「接受」,即同意按以下线框评审 #37 的界面改动。按本单正文的门禁,线框需用户确认后才编码,因此本评论交付设计证据与增量契约,工单保持「待确认」。
线框按现有真实布局绘制:书籍页章节列表当前是「序号 · 标题/字符数 · 已读标记 · 状态标签 · 重试 · 编辑」的网格行,章节编辑对话框当前是「章节标题 · 正文 · 字符计数 · 取消/删除章节/保存章节」。
S1 书库卡片(不变)
封面仍是书级,
coverVersion缓存失效机制与卡片排版保持 #21 已验收的样子。S2 书籍页「封面」区块(收窄)
S2b 章节列表行(新增缩略图与附件入口)
S2c 章节附件对话框(新增)
复用书级附件区块的排版(预览 + 已设置状态 + 操作按钮 + 规格提示):
S3 阅读页(插图 + 章级播放器)
S4 状态与持久化
增量契约(相对 #21 的差异点)
保留不动:封面(书级表、书级接口、
coverVersion)、magic bytes 类型嗅探与尺寸校验、binaryResponse+http.ServeContent的 Range/ETag/条件请求包装、AudioPlayer.vue、session.requestBlob取字节转对象 URL、上传"先校验后写入、失败保留旧附件"的流程。退役:
POST/DELETE/GET /api/v1/books/:id/audio、PUT /api/v1/books/:id/playback;lexgo_book_attachments的kind='audio'不再接受新写入。新增(schema v9,两张新表 + 一条幂等清理语句):
lexgo_chapter_attachments(chapter_id, kind),kind ∈ {audio, illustration};owner_id、mime、byte_size、sha256、bytes MEDIUMBLOB、时间戳;外键级联到章节与账号lexgo_chapter_playback_positions(owner_id, chapter_id),position_seconds、updated_at;级联到账号与章节DELETE FROM lexgo_book_attachments WHERE kind='audio'(幂等、可重放);lexgo_playback_positions表保留但停止写入(不删表,维持"只加不删"的迁移习惯)新接口:
POST/DELETE/GET /api/v1/chapters/:id/{audio,illustration}、PUT /api/v1/chapters/:id/playback;书籍详情的章节列表增加「插图版本号 / 是否有音频 / 上次位置」。校验:音频规则与上限不变;插图与封面共用同一套校验(JPG/PNG/WebP、≤2 MiB、≤4096×4096、按内容判定)。
测试与证据:在 v8 用例上新增插图类型/尺寸校验、章节删除级联、v8→v9 迁移与回退重升级、书级音频接口已下线的确认、历史音频行被清理;前端补 S2b/S2c/S3 的单测与 390×844 触摸视口 e2e;#15 恢复演练第三次重跑(附件按章节归属、行数与 sha256 一致、恢复后仍可按章播放与显示插图)。
回退:v9 只新增两张表;写回
lexgo_schema.version=8并恢复上一版二进制即可,但迁移已清空的书级音频测试数据找不回(当前无真实用户,工单已记录此代价)。请确认
#37 实施启动(2026-09-15)
用户 2026-09-15 回复「可以」,确认评论 8152 的线框(S2 收窄为封面、S2b 章节行缩略图与「附件/编辑」两个入口、S2c 独立的章节附件对话框、S3 章级播放器与正文上方插图、S4 账号+章节的位置规则)与增量契约。设计证据按该评论冻结。分支
feat/37-chapter-attachments从 main8a25b7b创建。实施顺序:schema v9(两张新表 + 幂等清理书级音频行)→ 退役书级音频接口、新增章级附件与位置接口 → 章节列表与阅读响应带章级附件元数据 → 学习端 S2/S2b/S2c/S3 → 测试(含 v8→v9 迁移与回退)→ 真实链路验证 → #15 恢复演练第三次重跑 → 文档改为章级口径。完成后停在待验收。
#37 实施完成,待用户验收(2026-09-15)
用户 2026-09-15 回复「可以」,确认评论 8152 的线框与增量契约。分支
feat/37-chapter-attachments从 main8a25b7b创建,提交0fd661e已推送;PR #38 未合并,工单停在待用户验收。实现与差异
lexgo_chapter_attachments(主键chapter_id+kind,kind ∈ {audio, illustration},bytes MEDIUMBLOB)与lexgo_chapter_playback_positions(主键owner_id+chapter_id);迁移内执行一条幂等语句DELETE FROM lexgo_book_attachments WHERE kind='audio'清空退役的书级音频历史行(测试期数据,不做存量迁移)。书级两张表结构保留、不再写入。POST/DELETE/GET /api/v1/chapters/:id/{audio,illustration}与PUT /api/v1/chapters/:id/playback;书级音频与播放接口下线(路由不再注册,请求得 404);封面仍为书级且行为不变。章节列表与阅读器响应新增illustrationVersion/audioVersion/playbackSeconds,字段始终存在(无文件时为空串)。binaryResponse+http.ServeContent(Range/206、416、ETag/304)、上传"先校验后写入、失败保留旧文件"的流程与AudioPlayer.vue全部保留。audioChapterId保证切章不会复用上一章音频,离开或切换章节时上报位置。验证
go vet/gofmt -lLEXGO_TEST_DB_NAME=lexgo_test_issue13 python scripts/server.py test-integrationvitest/vue-tsc/build/playwright testcheck --strict、sync --check通过迁移:schema v8→v9,开发库逐表业务计数无变化,schema 记到 9;本机 lexgo-api 已用新二进制重启,回退件
.local/lexgo-pre-issue37.exe。未验证与代价
文档
fabdaf13861b4cae9ad6cd66a5bd904c08361f73b9eda78d3e71sync --check通过,6 个变更页逐字节正文比对一致回退
v9 只新增两张表:停止 lexgo-api、把
lexgo_schema.version写回 8、恢复.local/lexgo-pre-issue37.exe后重启;既有书籍与学习数据不受影响,但被清空的书级音频行无法找回。Gitea MCP 仍指向其他站点,沿用目标站点 API 回退;凭据仅从既有安全配置读入进程。
#37 文档 revision 补录(2026-09-15)
上一条实施评论里把 Business-Rules、Local-Development、Product-Requirements 与 Home 的 revision 写成「见下一条评论」;实际 revision 如下(本次发布,含章级口径改写):
a4b1832fcda35aac0587ca0ef05006a8baefcd3d2c81572818a57c8cc1b2e42792f7aed6bbaef05be0e0b98e74a2e920e78928ff69cdd92e145ccb882512f727759bababca9de6697552da0f8c3f09ea37324e022b16323eef302d1524cabe27607a3934b9eda78d3e71dab02dbf2e3af700c81d9c725f8d镜像校验:
harness.py sync --check通过,并对 6 个变更页做逐字节正文比对(全部一致)。站点在本次发布中对连续请求返回过 429/503,已按退避重试完成,未跳过任何页面的回读确认。#37 设计证据修订(2026-09-15,按用户要求):插图改为阅读页缩略图 + 弹窗看原图
用户 2026-09-15 指出:「不要在书籍页的章节列表展示章节图片,进入章节内容里,缩略图展示,点击图片后弹窗查看原始图片」,并给出示例页面
/books/5。这条指令覆盖评论 8152 中 S2b 与 S3 的插图部分,其余(封面仍书级、音频章级、位置账号+章节、S2c 附件对话框、S4 状态规则)不变。修订后的界面
S2b 章节列表行(去掉缩略图列)
S3 阅读页(缩略图 + 弹窗看原图)
aria-label为「查看《章节标题》的插图大图」。min(88vw, 1200px)×78vh,保持比例不裁切;Esc 或关闭按钮退出,退出后正文位置不变。实现与验证
BookView.vue移除缩略图列、library.ts去掉列表预取、ReaderView.vue把插图换成缩略图按钮 +ElDialog,样式新增.illustration-thumb/.illustration-full;后端与 schema 无变化。<img>且不请求/illustration;阅读页缩略图是按钮且点击后弹窗内img的src为对象 URL;弹窗默认不存在)。/books/5不是该账号的书,返回 404;实际用该账号有就绪章节的书)上传真实 PNG → 书籍页确认章节列表中<img>数量为 0 → 进入章节看到缩略图(高 ≤130px)→ 点击打开弹窗,弹窗内图片大于缩略图且naturalWidth > 0(即真实原始图片)→ Esc 关闭后正文仍在 → 移除 fixture 插图使开发数据复原。截图:.local/evidence/issue37-real-book-no-image.png、issue37-real-reader-thumbnail.png、issue37-real-illustration-dialog.png。请确认
#37 插图交互修订完成,待验收(2026-09-15)
按用户要求把章节插图从「书籍页列表缩略图 + 阅读页大图」改为「只在阅读页显示缩略图,点击弹窗看原图」,并去掉书籍页章节列表的图片列(同时不再为列表预取图片)。设计证据修订见评论 8167。
提交
3ab26af(同一分支 PR #38,未合并)。验证:学习端 157 项单测与 26 项 E2E 全部通过(新断言:章节列表无
<img>且不请求/illustration;缩略图是按钮;点击后弹窗内图片src为对象 URL);真实链路(真实 API+MySQL+真实浏览器)在 issue5_a 自己的一本书上完成:列表<img>为 0、缩略图高 ≤130px、弹窗内图片大于缩略图且naturalWidth > 0、Esc 关闭后正文仍可见,随后移除 fixture 插图复原数据。示例地址/books/5属于其他账号(返回 404),实际使用该账号自己的书。后端与 schema 无变化(仍是 v9),因此 #15 恢复演练不需要重跑。
文档 revision(本次发布):
Architecture-and-Code-Map:
b9666699e8f01a117412b0b65880b0402b1bef0cBusiness-Rules-and-Glossary:
a226a6026e8415d6f2981fc87b52396f30ab3708Local-Development-and-Verification:
0550efcfd3844fb0af2dd7e59471f24f444eadf0Product-Requirements-Overview:
a54b9c33ece05a90cc28254273777dff9388167bHome:
6ff5329b98016970310da9f8f6d2c5e821e39714Deployment-and-Operations:
b9eda78d3e71dab02dbf2e3af700c81d9c725f8d镜像校验:
harness.py sync --check通过,6 个变更页逐字节正文比对一致。#37 附件并入章节编辑对话框,待验收(2026-09-15)
用户提出:章节列表每行的「附件」与「编辑」合成一个——保留「编辑」,点开后在同一编辑页里编辑章节标题、内容、插图和音频。已按此实施。
我的评估(工单里请用户确认的部分)
合适:一个入口、一处编辑,章节行更干净,符合「编辑这一章」的直觉;正文改版(重新处理)不会影响已上传的插图与音频,因为它们挂在章节上。
需要说清的唯一风险:同一个对话框里有两种生效时机——标题与正文点「保存章节」后生效,而文件是选中即上传。用户可能以为点「取消」什么都没发生,其实图片/音频已经存进去了。我没有改成延迟上传(那会引入「正文保存成功、文件上传失败」这类半成品状态,更复杂),而是在对话框里明确写出这一点:
data-testid="attachment-timing"的文案为「标题与正文点「保存章节」后生效;插图与音频在选中文件后立即上传,取消不会撤销已上传的文件。」实现
data-testid="edit-chapter-{id}"),原「章节附件」对话框与「附件」按钮移除。验证
学习端 157 项单测与 26 项 E2E 全部通过(新断言:章节行只有一个按钮;编辑对话框同时包含标题、正文、插图与音频控件以及生效时机说明;在对话框内上传后仍可用「保存章节」提交标题与正文)。
真实链路(真实 API+MySQL+真实浏览器,issue5_a 自己的书):章节行按钮数 = 1 且文本为「编辑」;对话框内标题/正文/插图状态/音频状态/时机说明齐备(截图
issue37-real-chapter-editor.png);在编辑框内上传真实 PNG 后状态立即变为「已设置」而标题仍是原值(说明文件即时生效、文本等待保存);取消后进入阅读页仍为缩略图+弹窗原图;最后移除 fixture 插图复原数据。文档 revision:
Architecture-and-Code-Map:
c75593d72432e5195ea84c766a81449ccb91f648Business-Rules-and-Glossary:
081daac73fa8d172a477d196e964382735122466Local-Development-and-Verification:
5459d79e2bf6339450af5add58a61ef22b0467eaProduct-Requirements-Overview:
5ecd946c6731afbbb87f0ab3d68988583fa4e2bbHome:
7a96206070fbd799d76a12ec07dd4520c2be1a81镜像校验:
harness.py sync --check通过,6 个变更页逐字节正文比对一致。请确认
若这个合并方式与「立即上传+明确文案」的处理可以接受,本单即可验收;若你希望文件也改成「点保存才上传」(可能出现部分成功),或希望在关闭时对未保存的正文做二次确认,请指出。
#37 缺陷修复:对话框退化成文档流块(2026-09-15)
用户追问「为什么还是在章节列表下面展示编辑区域呢,可以弹窗或新 tab 吗」——查明确实是缺陷,谢谢指出。
根因
learner/src/main.ts只按需导入了 button/input/radio/select/option/dropdown 的 Element Plus 样式,从未导入dialog/style/css与message-box/style/css。因此自 #10 起,所有ElDialog(书名编辑、章节编辑、生词库编辑、本次新增的章节编辑与插图弹窗)和ElMessageBox(删除确认等)都没有样式,退化成文档流中的普通块,看起来就像"长在章节列表下面"。探针实测(同一页面、同一操作):
position.el-overlaystaticstatic)top = 648px,正好等于章节列表底部relative(由浮层定位)fixedtop = 92px,居中于 720px 视口,遮罩rgba(0,0,0,0.5)修复
main.ts补上两行样式导入(一处修复覆盖全站所有对话框与确认框)。learner/e2e/overlay.ts:expectModalOverlay(page, dialog)与expectMessageBoxOverlay(page),断言目标位于.el-overlay且该层为position: fixed、控件在视口内、横向不溢出。attachments.spec.ts(章节编辑对话框、插图弹窗)与edit.spec.ts(书名对话框、删除确认)中调用,堵住这次暴露的盲区。我的验证盲区(如实记录)
此前所有 UI 验证只断言「元素可见 / DOM 结构 / 交互结果」,而
toBeVisible()对文档流里的普通块同样成立,所以 16 单测 + 26 E2E 全绿也没能发现它;截图我无法查看,也就没看出来。这条教训已写入 Wiki 的本地验证页:涉及"浮层/遮挡/布局"的属性必须显式断言,不能只依赖可见性。验证
issue37-real-dialog-modal.png、issue37-real-confirm-modal.png);取消确认后回到编辑对话框,取消编辑后对话框关闭。c22f9c3(同一分支 PR #38,未合并);文档 revision 见下一条评论。关于「弹窗或新 tab」
现在它是弹窗(模态浮层):点「编辑」后覆盖在页面上、带遮罩、Esc 或「取消」关闭。如果你更希望它打开独立页面/新标签(例如
/chapters/:id/edit整页编辑),那是一次导航结构变化,我可以改——请说一声要哪种(以及是否要保留弹窗方式作为快速编辑)。#37 作者字段与对话框紧凑化完成,待验收(2026-09-15)
按用户两点要求实施:①「章节标题」标签与输入框同一行,下面新增「作者」标签与输入框;②精简插图与音频的提示文字,把高度让给正文编辑框。
先说清一个前提(需要 schema 变更)
lexgo_chapters原本没有作者字段,所以这不是纯前端改动:本单新增 schema v10 ——author VARCHAR(120) NOT NULL DEFAULT ''。因为 MySQL 没有ADD COLUMN IF NOT EXISTS,加列写成 Go 条件步骤addChapterAuthorColumn(先查information_schema,缺列才ALTER TABLE),并且放在语句列表执行之后,这样既有的两条性质仍然成立:部分迁移可重试、回退版本标记后可重新升级;新建库的 v3 建表语句也直接带上该列。开发库迁移后 schema = 10,既有 34 个章节的 author 均为空串(无数据变化)。实现
ChapterSummary与ChapterSource都返回它,阅读页在章节标题下显示非空作者(data-testid="chapter-author-line")。.field-row),正文 textarea 由 10 行提升到 18 行;插图与音频压成各一两行(标签+状态+上传/替换/移除,规格写在下面一行);时机说明合并成一行:「标题、作者与正文点保存后生效;插图与音频选中文件后立即上传。」验证
TestMySQLChapterAuthorRoundTrip:作者往返、trim、≤120 边界、清空、仅改作者不重新处理、列表与阅读器都带、越权 404;新增TestMigrationFromV9AddsChapterAuthor:旧库缺列 → 迁移补列 → 回退标记 → 重新升级不丢数据)issue37-real-editor-layout.png、issue37-real-reader-author.png).local/lexgo-pre-issue37b.exe(回退时把lexgo_schema.version写回 9 即可,多出的空列不影响旧二进制)提交
3fe9bcb(同一分支 PR #38,未合并)。文档 revision:
Architecture-and-Code-Map:
2ccdc108a21abe67ad77c05af59bb34bac304093Business-Rules-and-Glossary:
77e1f1291a201744bdbb556ed1ef7d81cef2f969Local-Development-and-Verification:
92144cd570153b560180e195c49524fdb95a1048Product-Requirements-Overview:
1fc22dc050d51e4d4013d2a58b914f7b972116caHome:
dd5bc8b5423219fcc90d772bafeb56845644d16b请确认
#37 书籍页精简与书籍作者完成,待验收(2026-09-15)
按用户三点要求实施:①书籍页去掉「封面」「书籍封面」「JPG、PNG 或 WebP,最大 2 MiB,像素不超过 4096×4096。」「音频与插图按章节设置,见下方章节列表的「附件」。」四段文字,空间让给章节列表;②「编辑书名」改为**「编辑书籍」**,可改书名与作者;③书名右侧显示当前书籍作者。
又一次 schema 变更(v11)
书级作者是新字段:
lexgo_books.author VARCHAR(120) NOT NULL DEFAULT ''。开发库已在 v10,所以这次是 v11;加列复用 v10 的条件步骤(改名addAuthorColumn,表名取自本文件常量,先查information_schema再ALTER TABLE,在语句列表之后执行),「部分迁移可重试 / 回退标记后可重新升级」仍然成立;新建库的 v3 建表语句也带该列。开发库迁移后 schema = 11,既有 25 本书 author 全为空串(无数据变化)。语义(请一并确认)
书籍作者与章节作者是两份互相独立、都可选的自由文本(各 ≤120 字符,各自 trim,留空即清空):书级作者显示在书籍页书名右侧、由「编辑书籍」维护;章节作者显示在阅读页标题下、由章节「编辑」维护。章节不会继承书级作者,两者都不参与归类、检索或权限判断。省略作者字段的编辑请求保留原值。
实现
PATCH /api/v1/books/:id接受可选author;BookSummary与BookRef都返回它(书库列表因此也带着作者数据,但当前界面未在书库列表显示,需要的话我可以加)。.title-line在书名右侧显示作者(未设置则不显示)。验证
TestMySQLBookAuthorRoundTrip:书名+作者同保存、trim、省略保留、空串清空、≤120 边界、列表/详情/阅读器响应都带、越权 404;新增TestMigrationFromV10AddsBookAuthor:旧库缺列 → 迁移补列 → 回退标记 → 重新升级保留数据)issue37-real-book-page.png).local/lexgo-pre-issue37c.exe(回退写回版本号 10)提交
728f7d6(同一分支 PR #38,未合并)。文档 revision:
Architecture-and-Code-Map:
0bca600ea0dfcb18141920f448954bb9e2fa067cBusiness-Rules-and-Glossary:
b688ef278f83c8055e16f91843a239f9ea2dd492Local-Development-and-Verification:
8985f93f7319b2e6a428fc4417f353a7b909d797Product-Requirements-Overview:
21ca050a33bdbe8133c921b6b302fbfc8d8cf7feHome:
da0f9a0feb70b7099edcf8adf50a18a15435dd33请确认
#37 代码审核:达标(2026-09-16,Claude Code)
审核对象:分支
feat/37-chapter-attachments,提交0fd661e→728f7d6(PR #38)。这单在实施过程中经历了 6 轮迭代(章级音频/插图 → 插图交互改缩略图+弹窗 → 附件并入章节编辑对话框 → 修复对话框样式缺陷 → 章节作者字段 → 书籍作者字段),只读审阅代码,没有重跑测试。流程合规性核对
代码核对
database.go核对无误。v9 是纯CREATE TABLE IF NOT EXISTS加一条幂等DELETE;v10/v11 用addAuthorColumn先查information_schema.COLUMNS判断列是否存在,存在则跳过,table参数是代码里的常量而非用户输入(无注入风险),且这一步在版本号推进之前、原有加法语句之后执行,"部分迁移可重试、回退标记后可重新升级"这条项目一贯的保证仍然成立。这是 #11 讨论过但没采用的"带守卫的 DDL"方案,这次真正落地且实现正确。达标。attachment.go的registerAttachmentRoutes核对确认,/api/v1/books/:id/audio、/api/v1/books/:id/playback相关路由已不存在,只保留/books/:id/cover;章级的/chapters/:id/{audio,illustration}与/chapters/:id/playback都已注册。达标。SaveChapterAttachment在写入kind=audio后立即删除该章的ChapterPlaybackPosition;DeleteChapterAttachment移除音频时同样删除位置;SaveChapterPlaybackPosition在写入前检查该章是否存在音频,没有则 404——三处都核对了代码而非只信评论。达标。ON DELETE CASCADE到lexgo_chapters/lexgo_books,删除逻辑不需要额外代码,级联由数据库保证,比在应用层手写清理更不容易漏。达标。对自查缺陷的复核
评论 8178 报告的"对话框退化成文档流块"是这几轮审核里遇到的最严重的一次真实缺陷——从 #10 起、全站所有
ElDialog和ElMessageBox都没有样式,根源是main.ts按需引入 Element Plus 组件样式时漏了dialog/style/css和message-box/style/css两行。我核对了main.ts,确认两行已经补上。这个缺陷本身很难通过"元素可见"类断言发现(toBeVisible()对文档流里的普通块同样成立),pi 如实记录了这是"157 单测+26 E2E 全绿也没能发现"的验证盲区,并补充了显式断言浮层定位属性的新用例(expectModalOverlay),把方法论教训写回了 Wiki——这是审核里比较少见的、主动暴露自己验证方法缺陷的记录方式,值得肯定。结论
达标,可以进入用户验收。 尽管这单经历了多轮范围追加(几乎变成了另一个功能:章节/书籍作者字段),但每次都有对应的设计确认和测试补充,没有出现"先斩后奏"或"隐瞒代价"的情况;两次 schema 变更都清楚说明了后果(回退到 v8 会永久丢失测试期的书级音频数据),这个代价在无真实用户的当前阶段是可接受的。
本次审核只读代码,没有重跑
test-integration、learner 的 vitest/E2E 与 #15 恢复演练;pi 报告的 91 项集成用例、157 项前端单测、26 项 E2E、52 项真实链路检查、22 项恢复演练等结果未被本次复核重复验证,如需更高把握建议在验收前独立重跑一次,尤其是恢复演练(涉及数据可恢复性,风险等级较高)。工单末尾还有两个开放问题没有答复(书级作者是否显示到书库卡片、章节作者是否继承书级作者留空时的默认值),这些不影响"达标"结论,但建议在最终验收前一并决定,避免验收后又要开新一轮迭代。
Gitea MCP 仍指向其他站点,本次沿用已记录的目标站点 API 回退;凭据只从
~/.claude/gitea.env安全配置读入进程。#37 验收通过(2026-09-16)
用户于 2026-09-16 回复「#37 通过验收」(此前回复中提到的 #3 已于 2026-09-10 验收关闭,本单为实际待验收项,已在会话中核对确认)。代码审核见评论 8197:达标,可以进入用户验收,未提出待改项。
.local/lexgo-pre-issue37.exe、-b、-c。728f7d6;验收文档提交c293a41已推送。3cf18f98cf824dd3bbd5e8c300a09a321e47b357、Product-Requirementsdaf31e0d00508a7d0cf504a62072ed69322a5644、Local-Development7c41d62a131bfcc8c00a3b42a9830600a31f9c6a、Home3af7d77905777a9b0725e5be755eb4a7f461cad7、Project-Profile39adde482a8729fcf303ef7ca24cd8bf136c58a6、Deployment-and-Operationsb9eda78d3e71dab02dbf2e3af700c81d9c725f8d。harness.py sync --check通过,6 个变更页逐字节正文比对一致。lexgo_schema.version=9(或 v10)并恢复对应备份二进制;被 v9 清空的书级音频测试数据无法找回。单元工单关闭;#16 的 #37 复选框同步勾选。