MVP 增补:章节级音频与章节插图(封面维持书级) #37

Closed
opened 2026-09-15 20:27:02 +08:00 by ila · 12 comments
Owner

来源与目标

用户在 2026-09-15 提出实际使用场景(原话):「有个场景,我收集了多篇网络文章,每篇有自己的音频,要怎样来实现」;随后补充**「书籍封面只有一个,我想给每章节加个图片,当插图或章节图片,增加阅读氛围和兴趣」。经讨论确认组织方式为「多篇文章合并成一本书的多章」,因此附件挂载单位需要从「每本书一个音频」改为「每个章节一个音频」,并新增章节插图**(一章一张,固定展示在该章正文开头);封面维持书级不变。

本单在已验收的 #21 书级实现(PR #36,commit ab23ff6)基础上继续改,不推倒重做:类型嗅探、Range/ETag 包装、播放器组件、带凭据 fetch + 对象 URL 的机制全部复用。用户已确认这一处理方式。完整契约草案见 #21 评论 8129。

已确认的范围

项目 内容
封面 维持书级(lexgo_book_attachments 的 kind='cover'、书级读写接口与 coverVersion 缓存机制原样保留)
音频 改为章节级:一章最多一段 MP3,上传/替换/移除
插图 新增章节级附件:一章最多一张 JPG/PNG/WebP,展示在该章正文开头(章节标题与播放器条之下、正文之上),无插图不占位;可复用为书籍页章节列表的缩略图
播放位置 粒度从「账号+书」改为「账号+章节」;规则不变(0~24 小时上界、最后写入为准、不解析时长)
退役 书级音频接口(POST/DELETE/GET /api/v1/books/:id/audio、PUT /api/v1/books/:id/playback)整体下线

非目标

多张插图、正文中间图文混排、缩略图墙式独立浏览界面、多音轨与分章音轨、字幕与时间戳同步、逐句跟读、转写、TTS、音频转码、自动搜索封面或插图、在线音频地址抓取、公开分享与跨用户共享。

验收标准

  • 每章可上传、替换、移除一段音频与一张插图;插图在阅读页固定显示于该章正文开头,并在书籍页章节列表显示缩略图(无附件时显示占位,不产生横向溢出)。
  • 类型按文件内容判定(MP3 与 JPG/PNG/WebP 的 magic bytes,不看扩展名);音频 ≤20 MiB、图片 ≤2 MiB 且 ≤4096×4096;超限 413、类型或内容非法 400 并给出可读原因;被拒绝的上传保留旧附件。
  • 附件读取需要会话并支持 HTTP Range(206)、416 与 ETag/304;跨账号读写与位置上报一律 404,匿名 401。
  • 播放位置按账号+章节保存;替换或移除音频时重置;离开章节或切换章节时上报一次;并发以最后一次写入为准。
  • 删除章节(复用 #10 的事务级联)连带删除该章附件与位置;删除书籍通过章节级联清空。
  • 迁移 v8→v9 只新增对象且可重放;kind='audio' 的书级历史行按已确认方式清空、不做存量迁移;写回版本号回到 v8 时能看到(已清空的)书级接口,此代价在工单记录。
  • 更新 #15 的恢复演练(第三次):附件按章节归属、行数与 sha256 一致、恢复后仍可按章播放与显示插图。
  • 桌面与 390×844 触摸视口都验证:插图不遮挡正文与划词面板、播放器条仍为 sticky、章节列表行内的编辑入口可点开。
  • 文档改为章级口径(Architecture、Business-Rules、Local-Development、Product-Requirements、Home、Deployment-and-Operations),删除书级音频描述。

依赖与执行

前置:#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 回退;凭据仅从既有安全配置读入进程。

## 来源与目标 用户在 2026-09-15 提出实际使用场景(原话):**「有个场景,我收集了多篇网络文章,每篇有自己的音频,要怎样来实现」**;随后补充**「书籍封面只有一个,我想给每章节加个图片,当插图或章节图片,增加阅读氛围和兴趣」**。经讨论确认组织方式为「多篇文章合并成一本书的多章」,因此附件挂载单位需要从「每本书一个音频」改为「**每个章节一个音频**」,并**新增章节插图**(一章一张,固定展示在该章正文开头);封面维持**书级**不变。 本单在已验收的 #21 书级实现(PR #36,commit `ab23ff6`)基础上继续改,**不推倒重做**:类型嗅探、Range/ETag 包装、播放器组件、带凭据 fetch + 对象 URL 的机制全部复用。用户已确认这一处理方式。完整契约草案见 #21 评论 [8129](https://git.ilapage.cn/OPC/lexgo/issues/21#issuecomment-8129)。 ## 已确认的范围 | 项目 | 内容 | |---|---| | 封面 | **维持书级**(`lexgo_book_attachments` 的 `kind='cover'`、书级读写接口与 `coverVersion` 缓存机制原样保留) | | 音频 | 改为**章节级**:一章最多一段 MP3,上传/替换/移除 | | 插图 | **新增章节级附件**:一章最多一张 JPG/PNG/WebP,展示在该章正文开头(章节标题与播放器条之下、正文之上),无插图不占位;可复用为书籍页章节列表的缩略图 | | 播放位置 | 粒度从「账号+书」改为「**账号+章节**」;规则不变(0~24 小时上界、最后写入为准、不解析时长) | | 退役 | 书级音频接口(`POST/DELETE/GET /api/v1/books/:id/audio`、`PUT /api/v1/books/:id/playback`)整体下线 | ## 非目标 多张插图、正文中间图文混排、缩略图墙式独立浏览界面、多音轨与分章音轨、字幕与时间戳同步、逐句跟读、转写、TTS、音频转码、自动搜索封面或插图、在线音频地址抓取、公开分享与跨用户共享。 ## 验收标准 - [ ] 每章可上传、替换、移除一段音频与一张插图;插图在阅读页固定显示于该章正文开头,并在书籍页章节列表显示缩略图(无附件时显示占位,不产生横向溢出)。 - [ ] 类型**按文件内容判定**(MP3 与 JPG/PNG/WebP 的 magic bytes,不看扩展名);音频 ≤20 MiB、图片 ≤2 MiB 且 ≤4096×4096;超限 413、类型或内容非法 400 并给出可读原因;**被拒绝的上传保留旧附件**。 - [ ] 附件读取需要会话并支持 **HTTP Range(206)**、416 与 `ETag`/`304`;跨账号读写与位置上报一律 404,匿名 401。 - [ ] 播放位置按**账号+章节**保存;替换或移除音频时重置;离开章节或切换章节时上报一次;并发以最后一次写入为准。 - [ ] 删除章节(复用 #10 的事务级联)连带删除该章附件与位置;删除书籍通过章节级联清空。 - [ ] 迁移 v8→v9 只新增对象且可重放;`kind='audio'` 的书级历史行按已确认方式清空、不做存量迁移;写回版本号回到 v8 时能看到(已清空的)书级接口,此代价在工单记录。 - [ ] 更新 #15 的恢复演练(第三次):附件按**章节**归属、行数与 sha256 一致、恢复后仍可按章播放与显示插图。 - [ ] 桌面与 390×844 触摸视口都验证:插图不遮挡正文与划词面板、播放器条仍为 sticky、章节列表行内的编辑入口可点开。 - [ ] 文档改为章级口径(Architecture、Business-Rules、Local-Development、Product-Requirements、Home、Deployment-and-Operations),删除书级音频描述。 ## 依赖与执行 前置:#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 回退;凭据仅从既有安全配置读入进程。
Author
Owner

#37 设计证据(形式 b:标注线框 + 状态说明)与增量契约(2026-09-15)

用户 2026-09-15 确认「接受」,即同意按以下线框评审 #37 的界面改动。按本单正文的门禁,线框需用户确认后才编码,因此本评论交付设计证据与增量契约,工单保持「待确认」。

线框按现有真实布局绘制:书籍页章节列表当前是「序号 · 标题/字符数 · 已读标记 · 状态标签 · 重试 · 编辑」的网格行,章节编辑对话框当前是「章节标题 · 正文 · 字符计数 · 取消/删除章节/保存章节」。

S1 书库卡片(不变)

封面仍是书级,coverVersion 缓存失效机制与卡片排版保持 #21 已验收的样子。

S2 书籍页「封面」区块(收窄)

封面与章节附件
┌────────────────────────────────────────────────────────────┐
│ 封面   [预览 160×90]   已设置 · PNG · 0.5 MB               │
│                         [替换封面] [移除]                   │
│ 提示:音频与插图按章节设置,见下方章节列表。                │
└────────────────────────────────────────────────────────────┘
  • 删除 #21 的「音频」行(书级音频退役);封面行为与拷贝保持原样。
  • 新增一行说明,把用户引向章节列表,避免"音频去哪了"的困惑。

S2b 章节列表行(新增缩略图与附件入口)

┌──────────────────────────────────────────────────────────────────────┐
│ 1 │ [缩略图]  Fictional chapter                          [已读][已就绪] │
│   │           1204 字符 · 🎧 有音频 · 上次 1:05            [附件][编辑] │
└──────────────────────────────────────────────────────────────────────┘
  • 缩略图:64×36(16:9)圆角小图,插图的缩略版本(同一份字节,浏览器缩放);无插图时显示淡色占位块(不显示破图)。
  • meta 行:字符数之后追加「🎧 有音频 · 上次 m:ss」(无音频则整段不显示;位置为 0 不显示时间)。
  • 操作:「附件」按钮打开该章的附件对话框(S2c);「编辑」仍是 #10 的正文编辑对话框(打开时会重新处理这一章,语义不变)。
  • 手机(390×844):缩略图 + 文本 + 两个按钮换行成两列,按钮全宽,不产生横向溢出。

S2c 章节附件对话框(新增)

复用书级附件区块的排版(预览 + 已设置状态 + 操作按钮 + 规格提示):

编辑章节附件:Fictional chapter
┌──────────────────────────────────────────────────────────┐
│ 插图   [预览 160×90]  已设置 · JPG · 0.3 MB              │
│                    [替换插图] [移除]                      │
│        JPG、PNG 或 WebP,≤2 MiB,像素 ≤4096×4096          │
│                                                          │
│ 音频   MP3 · 3.4 MB · 上次播放位置 1:05                   │
│                    [上传音频] [替换] [移除]               │
│        MP3,≤20 MiB;不与文本同步;替换或移除会重置位置    │
└──────────────────────────────────────────────────────────┘
                          [关闭]
  • 上传中:按钮 loading + 「正在上传…」;失败:区块内可读错误(格式/大小/网络),保留旧附件。
  • 移除:二次确认(沿用现有确认样式);移除音频同时清除该章播放位置。
  • 与「编辑」分开:改正文会重新处理章节,改附件不会——两件事不放在一个对话框里,避免误操作。

S3 阅读页(插图 + 章级播放器)

[顶部导航]
┌──────────────────────────────────────────────────────────┐
│ ▶   0:12 ──────●──────────── 12:30   1.0×   ↻重试        │  ← 仅该章有音频时出现
└──────────────────────────────────────────────────────────┘
章节标题   [已就绪]
┌──────────────────────────────────────────────────────────┐
│                    [插图,宽度跟随正文栏,最大高度 40dvh]  │  ← 仅该章有插图时出现
└──────────────────────────────────────────────────────────┘
正文…
  • 播放器条仍是 sticky 顶部,判据从「该书有音频」改为「该章有音频」。
  • 插图位于章节标题与播放器条之下、正文之上;无插图时不占位(不保留空框)。
  • 插图不参与划词:它是图片,点击不触发查词;正文中的词仍然照旧可选。
  • 手机:插图按正文栏宽度自适应,长图最多占 40dvh;播放器控件换行但仍在正文之前。

S4 状态与持久化

状态 行为
打开章节 只恢复该章位置显示,不自动播放
播放中 每 5 秒、暂停时、离开当前章节或切换章节时各上报一次(账号+章节)
替换音频 该章位置重置为 0
移除音频 删除该章位置记录,播放器条消失
删除章节 该章附件与位置随之删除(复用 #10 的事务级联)
其他账号 读取、替换、移除、上报位置一律 404

增量契约(相对 #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 并恢复上一版二进制即可,但迁移已清空的书级音频测试数据找不回(当前无真实用户,工单已记录此代价)。

请确认

  1. S2b 的章节行布局(缩略图 + meta 追加 + 「附件」「编辑」两个入口)与 S2c 的独立附件对话框是否可以直接编码?
  2. 插图在阅读页的位置(章节标题与播放器条之下、正文之上,最多占 40dvh)是否符合预期?
  3. 若确认,工单转「进行中」,我按增量契约实施,完成后仍停在待验收。
## #37 设计证据(形式 b:标注线框 + 状态说明)与增量契约(2026-09-15) 用户 2026-09-15 确认「接受」,即同意按以下线框评审 #37 的界面改动。按本单正文的门禁,**线框需用户确认后才编码**,因此本评论交付设计证据与增量契约,工单保持「待确认」。 线框按**现有真实布局**绘制:书籍页章节列表当前是「序号 · 标题/字符数 · 已读标记 · 状态标签 · 重试 · 编辑」的网格行,章节编辑对话框当前是「章节标题 · 正文 · 字符计数 · 取消/删除章节/保存章节」。 ### S1 书库卡片(**不变**) 封面仍是书级,`coverVersion` 缓存失效机制与卡片排版保持 #21 已验收的样子。 ### S2 书籍页「封面」区块(**收窄**) ``` 封面与章节附件 ┌────────────────────────────────────────────────────────────┐ │ 封面 [预览 160×90] 已设置 · PNG · 0.5 MB │ │ [替换封面] [移除] │ │ 提示:音频与插图按章节设置,见下方章节列表。 │ └────────────────────────────────────────────────────────────┘ ``` - 删除 #21 的「音频」行(书级音频退役);封面行为与拷贝保持原样。 - 新增一行说明,把用户引向章节列表,避免"音频去哪了"的困惑。 ### S2b 章节列表行(**新增缩略图与附件入口**) ``` ┌──────────────────────────────────────────────────────────────────────┐ │ 1 │ [缩略图] Fictional chapter [已读][已就绪] │ │ │ 1204 字符 · 🎧 有音频 · 上次 1:05 [附件][编辑] │ └──────────────────────────────────────────────────────────────────────┘ ``` - **缩略图**:64×36(16:9)圆角小图,插图的缩略版本(同一份字节,浏览器缩放);无插图时显示淡色占位块(不显示破图)。 - **meta 行**:字符数之后追加「🎧 有音频 · 上次 m:ss」(无音频则整段不显示;位置为 0 不显示时间)。 - **操作**:「附件」按钮打开该章的附件对话框(S2c);「编辑」仍是 #10 的正文编辑对话框(打开时会重新处理这一章,语义不变)。 - 手机(390×844):缩略图 + 文本 + 两个按钮换行成两列,按钮全宽,不产生横向溢出。 ### S2c 章节附件对话框(**新增**) 复用书级附件区块的排版(预览 + 已设置状态 + 操作按钮 + 规格提示): ``` 编辑章节附件:Fictional chapter ┌──────────────────────────────────────────────────────────┐ │ 插图 [预览 160×90] 已设置 · JPG · 0.3 MB │ │ [替换插图] [移除] │ │ JPG、PNG 或 WebP,≤2 MiB,像素 ≤4096×4096 │ │ │ │ 音频 MP3 · 3.4 MB · 上次播放位置 1:05 │ │ [上传音频] [替换] [移除] │ │ MP3,≤20 MiB;不与文本同步;替换或移除会重置位置 │ └──────────────────────────────────────────────────────────┘ [关闭] ``` - 上传中:按钮 loading + 「正在上传…」;失败:区块内可读错误(格式/大小/网络),**保留旧附件**。 - 移除:二次确认(沿用现有确认样式);移除音频同时清除该章播放位置。 - 与「编辑」分开:改正文会重新处理章节,改附件不会——两件事不放在一个对话框里,避免误操作。 ### S3 阅读页(**插图 + 章级播放器**) ``` [顶部导航] ┌──────────────────────────────────────────────────────────┐ │ ▶ 0:12 ──────●──────────── 12:30 1.0× ↻重试 │ ← 仅该章有音频时出现 └──────────────────────────────────────────────────────────┘ 章节标题 [已就绪] ┌──────────────────────────────────────────────────────────┐ │ [插图,宽度跟随正文栏,最大高度 40dvh] │ ← 仅该章有插图时出现 └──────────────────────────────────────────────────────────┘ 正文… ``` - 播放器条仍是 sticky 顶部,**判据从「该书有音频」改为「该章有音频」**。 - 插图位于章节标题与播放器条之下、正文之上;无插图时不占位(不保留空框)。 - 插图**不参与划词**:它是图片,点击不触发查词;正文中的词仍然照旧可选。 - 手机:插图按正文栏宽度自适应,长图最多占 40dvh;播放器控件换行但仍在正文之前。 ### S4 状态与持久化 | 状态 | 行为 | |---|---| | 打开章节 | 只恢复该章位置显示,不自动播放 | | 播放中 | 每 5 秒、暂停时、**离开当前章节或切换章节时**各上报一次(账号+章节) | | 替换音频 | 该章位置重置为 0 | | 移除音频 | 删除该章位置记录,播放器条消失 | | 删除章节 | 该章附件与位置随之删除(复用 #10 的事务级联) | | 其他账号 | 读取、替换、移除、上报位置一律 404 | ### 增量契约(相对 #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` 并恢复上一版二进制即可,但**迁移已清空的书级音频测试数据找不回**(当前无真实用户,工单已记录此代价)。 ### 请确认 1. S2b 的章节行布局(缩略图 + meta 追加 + 「附件」「编辑」两个入口)与 S2c 的独立附件对话框是否可以直接编码? 2. 插图在阅读页的位置(章节标题与播放器条之下、正文之上,最多占 40dvh)是否符合预期? 3. 若确认,工单转「进行中」,我按增量契约实施,完成后仍停在待验收。
Author
Owner

#37 实施启动(2026-09-15)

用户 2026-09-15 回复「可以」,确认评论 8152 的线框(S2 收窄为封面、S2b 章节行缩略图与「附件/编辑」两个入口、S2c 独立的章节附件对话框、S3 章级播放器与正文上方插图、S4 账号+章节的位置规则)与增量契约。设计证据按该评论冻结。分支 feat/37-chapter-attachments 从 main 8a25b7b 创建。

实施顺序:schema v9(两张新表 + 幂等清理书级音频行)→ 退役书级音频接口、新增章级附件与位置接口 → 章节列表与阅读响应带章级附件元数据 → 学习端 S2/S2b/S2c/S3 → 测试(含 v8→v9 迁移与回退)→ 真实链路验证 → #15 恢复演练第三次重跑 → 文档改为章级口径。完成后停在待验收。

## #37 实施启动(2026-09-15) 用户 2026-09-15 回复「可以」,确认评论 [8152](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8152) 的线框(S2 收窄为封面、S2b 章节行缩略图与「附件/编辑」两个入口、S2c 独立的章节附件对话框、S3 章级播放器与正文上方插图、S4 账号+章节的位置规则)与增量契约。设计证据按该评论冻结。分支 `feat/37-chapter-attachments` 从 main `8a25b7b` 创建。 实施顺序:schema v9(两张新表 + 幂等清理书级音频行)→ 退役书级音频接口、新增章级附件与位置接口 → 章节列表与阅读响应带章级附件元数据 → 学习端 S2/S2b/S2c/S3 → 测试(含 v8→v9 迁移与回退)→ 真实链路验证 → **#15 恢复演练第三次重跑** → 文档改为章级口径。完成后停在待验收。
Author
Owner

#37 实施完成,待用户验收(2026-09-15)

用户 2026-09-15 回复「可以」,确认评论 8152 的线框与增量契约。分支 feat/37-chapter-attachments 从 main 8a25b7b 创建,提交 0fd661e 已推送;PR #38 未合并,工单停在待用户验收。

实现与差异

  • schema v9(三张表都只用可重放 DDL):新增 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,字段始终存在(无文件时为空串)。
  • 复用:#21 的 magic bytes 嗅探、尺寸校验、binaryResponse + http.ServeContent(Range/206、416、ETag/304)、上传"先校验后写入、失败保留旧文件"的流程与 AudioPlayer.vue 全部保留。
  • 学习端:封面区块收窄为封面并指向章节列表;章节行新增插图缩略图与「附件」按钮(与「编辑」分开);新增章节附件对话框(插图/音频各自的预览、状态、上传/替换/移除与规格提示);阅读页在正文上方渲染本章插图,播放器只在本章有音频时出现,audioChapterId 保证切章不会复用上一章音频,离开或切换章节时上报位置。

验证

验证 结果
go vet / gofmt -l 通过、无输出
LEXGO_TEST_DB_NAME=lexgo_test_issue13 python scripts/server.py test-integration 87 项顶层用例通过、0 跳过(章级附件与位置集成、封面仍为书级、请求形态校验、v8→v9 迁移与回退重升级、v7→v8 测试改用原始 SQL 写已退役表)
learner vitest / vue-tsc / build / playwright test 157 项单测、类型检查、构建、26 项 E2E(桌面 22 + 移动 4)全部通过
admin / 治理 / 镜像 31 项与 lint;65 项、check --strict、sync --check 通过
真实 API+MySQL 52 项检查通过:两章书的 fixture 证明附件属于章而非书(第二章为空、书级音频行 0);真实 PNG 与三秒 MP3 逐字节、sha256、ETag/304、Range 206 与切片、416;书级接口 404;位置按章保存、负值 400、无音频章 404;替换归零;SVG/文本/21 MiB 被拒且旧文件保留;越权 404、匿名 401;移除音频清位置但插图不受影响;删章节与删书籍级联清空
#15 恢复演练(第三次) 22 项检查通过:Go 二进制备份(manifest 含两张新表)→ 恢复到空库 → 校验通过(含「章节附件都指向存在的章节」等新检查)→ 17 张表逐表内容校验和与源库一致 → 恢复实例上同一账号可登录、章节仍报告两个文件、位置保留、音频与插图按字节相同返回、Range 仍 206、越权仍 404

迁移:schema v8→v9,开发库逐表业务计数无变化,schema 记到 9;本机 lexgo-api 已用新二进制重启,回退件 .local/lexgo-pre-issue37.exe。

未验证与代价

  • 真机与真实浏览器解码播放仍未验证(E2E 中媒体元素被桩替换);Safari/Firefox 未测;渐进式流式播放仍未做。
  • 迁移会清空书级音频测试数据,回退到 v8 也找不回;当前无真实用户,已在工单记录此代价。
  • 没有做「把既有书级音频迁到章级」的转换(无真实数据),也没有为「一章多图/图文混排」留实现。

文档

  • Architecture-and-Code-Map: fabdaf13861b4cae9ad6cd66a5bd904c08361f73
  • Business-Rules-and-Glossary: 本次发布 revision 见工单下一条评论(同一提交内已更新)
  • Local-Development-and-Verification: 同上
  • Product-Requirements-Overview: 同上
  • Home: 同上
  • Deployment-and-Operations: b9eda78d3e71
  • 镜像校验:sync --check 通过,6 个变更页逐字节正文比对一致

回退

v9 只新增两张表:停止 lexgo-api、把 lexgo_schema.version 写回 8、恢复 .local/lexgo-pre-issue37.exe 后重启;既有书籍与学习数据不受影响,但被清空的书级音频行无法找回。

Gitea MCP 仍指向其他站点,沿用目标站点 API 回退;凭据仅从既有安全配置读入进程。

## #37 实施完成,待用户验收(2026-09-15) 用户 2026-09-15 回复「可以」,确认评论 [8152](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8152) 的线框与增量契约。分支 `feat/37-chapter-attachments` 从 main `8a25b7b` 创建,提交 `0fd661e` 已推送;PR #38 未合并,工单停在待用户验收。 ### 实现与差异 - **schema v9**(三张表都只用可重放 DDL):新增 `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`,字段始终存在(无文件时为空串)。 - **复用**:#21 的 magic bytes 嗅探、尺寸校验、`binaryResponse` + `http.ServeContent`(Range/206、416、ETag/304)、上传"先校验后写入、失败保留旧文件"的流程与 `AudioPlayer.vue` 全部保留。 - **学习端**:封面区块收窄为封面并指向章节列表;章节行新增插图缩略图与「附件」按钮(与「编辑」分开);新增章节附件对话框(插图/音频各自的预览、状态、上传/替换/移除与规格提示);阅读页在正文上方渲染本章插图,播放器只在本章有音频时出现,`audioChapterId` 保证切章不会复用上一章音频,离开或切换章节时上报位置。 ### 验证 | 验证 | 结果 | |---|---| | `go vet` / `gofmt -l` | 通过、无输出 | | `LEXGO_TEST_DB_NAME=lexgo_test_issue13 python scripts/server.py test-integration` | **87 项顶层用例通过、0 跳过**(章级附件与位置集成、封面仍为书级、请求形态校验、v8→v9 迁移与回退重升级、v7→v8 测试改用原始 SQL 写已退役表) | | learner `vitest` / `vue-tsc` / `build` / `playwright test` | 157 项单测、类型检查、构建、**26 项 E2E**(桌面 22 + 移动 4)全部通过 | | admin / 治理 / 镜像 | 31 项与 lint;65 项、`check --strict`、`sync --check` 通过 | | 真实 API+MySQL | **52 项检查通过**:两章书的 fixture 证明附件属于章而非书(第二章为空、书级音频行 0);真实 PNG 与三秒 MP3 逐字节、sha256、ETag/304、Range 206 与切片、416;书级接口 404;位置按章保存、负值 400、无音频章 404;替换归零;SVG/文本/21 MiB 被拒且旧文件保留;越权 404、匿名 401;移除音频清位置但插图不受影响;删章节与删书籍级联清空 | | **#15 恢复演练(第三次)** | **22 项检查通过**:Go 二进制备份(manifest 含两张新表)→ 恢复到空库 → 校验通过(含「章节附件都指向存在的章节」等新检查)→ **17 张表逐表内容校验和与源库一致** → 恢复实例上同一账号可登录、章节仍报告两个文件、位置保留、**音频与插图按字节相同返回**、Range 仍 206、越权仍 404 | 迁移:schema v8→v9,开发库逐表业务计数无变化,schema 记到 9;本机 lexgo-api 已用新二进制重启,回退件 `.local/lexgo-pre-issue37.exe`。 ### 未验证与代价 - **真机与真实浏览器解码播放仍未验证**(E2E 中媒体元素被桩替换);Safari/Firefox 未测;渐进式流式播放仍未做。 - 迁移会**清空书级音频测试数据**,回退到 v8 也找不回;当前无真实用户,已在工单记录此代价。 - 没有做「把既有书级音频迁到章级」的转换(无真实数据),也没有为「一章多图/图文混排」留实现。 ### 文档 - Architecture-and-Code-Map: `fabdaf13861b4cae9ad6cd66a5bd904c08361f73` - Business-Rules-and-Glossary: 本次发布 revision 见工单下一条评论(同一提交内已更新) - Local-Development-and-Verification: 同上 - Product-Requirements-Overview: 同上 - Home: 同上 - Deployment-and-Operations: `b9eda78d3e71` - 镜像校验:`sync --check` 通过,6 个变更页逐字节正文比对一致 ### 回退 v9 只新增两张表:停止 lexgo-api、把 `lexgo_schema.version` 写回 8、恢复 `.local/lexgo-pre-issue37.exe` 后重启;既有书籍与学习数据不受影响,但被清空的书级音频行无法找回。 Gitea MCP 仍指向其他站点,沿用目标站点 API 回退;凭据仅从既有安全配置读入进程。
Author
Owner

#37 文档 revision 补录(2026-09-15)

上一条实施评论里把 Business-Rules、Local-Development、Product-Requirements 与 Home 的 revision 写成「见下一条评论」;实际 revision 如下(本次发布,含章级口径改写):

  • Architecture-and-Code-Map: a4b1832fcda35aac0587ca0ef05006a8baefcd3d
  • Business-Rules-and-Glossary: 2c81572818a57c8cc1b2e42792f7aed6bbaef05b
  • Local-Development-and-Verification: e0e0b98e74a2e920e78928ff69cdd92e145ccb88
  • Product-Requirements-Overview: 2512f727759bababca9de6697552da0f8c3f09ea
  • Home: 37324e022b16323eef302d1524cabe27607a3934
  • Deployment-and-Operations: b9eda78d3e71dab02dbf2e3af700c81d9c725f8d

镜像校验:harness.py sync --check 通过,并对 6 个变更页做逐字节正文比对(全部一致)。站点在本次发布中对连续请求返回过 429/503,已按退避重试完成,未跳过任何页面的回读确认。

## #37 文档 revision 补录(2026-09-15) 上一条实施评论里把 Business-Rules、Local-Development、Product-Requirements 与 Home 的 revision 写成「见下一条评论」;实际 revision 如下(本次发布,含章级口径改写): - Architecture-and-Code-Map: `a4b1832fcda35aac0587ca0ef05006a8baefcd3d` - Business-Rules-and-Glossary: `2c81572818a57c8cc1b2e42792f7aed6bbaef05b` - Local-Development-and-Verification: `e0e0b98e74a2e920e78928ff69cdd92e145ccb88` - Product-Requirements-Overview: `2512f727759bababca9de6697552da0f8c3f09ea` - Home: `37324e022b16323eef302d1524cabe27607a3934` - Deployment-and-Operations: `b9eda78d3e71dab02dbf2e3af700c81d9c725f8d` 镜像校验:`harness.py sync --check` 通过,并对 6 个变更页做逐字节正文比对(全部一致)。站点在本次发布中对连续请求返回过 429/503,已按退避重试完成,未跳过任何页面的回读确认。
Author
Owner

#37 设计证据修订(2026-09-15,按用户要求):插图改为阅读页缩略图 + 弹窗看原图

用户 2026-09-15 指出:「不要在书籍页的章节列表展示章节图片,进入章节内容里,缩略图展示,点击图片后弹窗查看原始图片」,并给出示例页面 /books/5。这条指令覆盖评论 8152 中 S2b 与 S3 的插图部分,其余(封面仍书级、音频章级、位置账号+章节、S2c 附件对话框、S4 状态规则)不变。

修订后的界面

S2b 章节列表行(去掉缩略图列)

┌──────────────────────────────────────────────────────────────────────┐
│ 1 │ Fictional chapter                                    [已读][已就绪] │
│   │ 1204 字符 · 🎧 有音频 · 上次 1:05                    [附件][编辑]   │
└──────────────────────────────────────────────────────────────────────┘
  • 章节列表不再显示任何章节图片,也不再为列表预取图片(省掉每章一次请求)。
  • 章节是否有插图仍可在「附件」对话框里看到;列表本身不额外加文字标记(保持精简,如需要可再加「有插图」字样)。

S3 阅读页(缩略图 + 弹窗看原图)

[顶部导航]
┌──────────────────────────────────────────────────────────┐
│ ▶   0:12 ──────●──────────── 12:30   1.0×   ↻重试        │  ← 仅本章有音频时出现
└──────────────────────────────────────────────────────────┘
章节标题   [已就绪]
┌───────────────┐
│  [缩略图]      │  ← 高 120px、宽度自适应,下面一行小字「点击查看大图」
└───────────────┘
正文…
  • 缩略图是按钮(可 Tab、可回车/Space 打开),aria-label 为「查看《章节标题》的插图大图」。
  • 点击后在对话框里显示原图:最大 min(88vw, 1200px) × 78vh,保持比例不裁切;Esc 或关闭按钮退出,退出后正文位置不变。
  • 无插图的章节:不显示占位、不显示按钮(与之前一致)。

实现与验证

  • 前端改动:BookView.vue 移除缩略图列、library.ts 去掉列表预取、ReaderView.vue 把插图换成缩略图按钮 + ElDialog,样式新增 .illustration-thumb/.illustration-full;后端与 schema 无变化。
  • 测试:学习端 157 项单测与 26 项 E2E 全部通过(新增/更新的断言:章节列表无 <img> 且不请求 /illustration;阅读页缩略图是按钮且点击后弹窗内 img 的 src 为对象 URL;弹窗默认不存在)。
  • 真实链路(真实 API+MySQL+真实浏览器,本机):在 issue5_a 自己的一本书(示例 /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。

请确认

  1. 修订后的 S2b(列表无图片)与 S3(缩略图 + 弹窗原图)是否符合要求?
  2. 若确认,本单可进入验收;如需在列表上加「有插图」文字标记,或希望缩略图更大/更小,请指出尺寸或位置。
## #37 设计证据修订(2026-09-15,按用户要求):插图改为阅读页缩略图 + 弹窗看原图 用户 2026-09-15 指出:**「不要在书籍页的章节列表展示章节图片,进入章节内容里,缩略图展示,点击图片后弹窗查看原始图片」**,并给出示例页面 `/books/5`。这条指令覆盖评论 [8152](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8152) 中 S2b 与 S3 的插图部分,其余(封面仍书级、音频章级、位置账号+章节、S2c 附件对话框、S4 状态规则)不变。 ### 修订后的界面 **S2b 章节列表行(去掉缩略图列)** ``` ┌──────────────────────────────────────────────────────────────────────┐ │ 1 │ Fictional chapter [已读][已就绪] │ │ │ 1204 字符 · 🎧 有音频 · 上次 1:05 [附件][编辑] │ └──────────────────────────────────────────────────────────────────────┘ ``` - 章节列表**不再显示任何章节图片**,也不再为列表预取图片(省掉每章一次请求)。 - 章节是否有插图仍可在「附件」对话框里看到;列表本身不额外加文字标记(保持精简,如需要可再加「有插图」字样)。 **S3 阅读页(缩略图 + 弹窗看原图)** ``` [顶部导航] ┌──────────────────────────────────────────────────────────┐ │ ▶ 0:12 ──────●──────────── 12:30 1.0× ↻重试 │ ← 仅本章有音频时出现 └──────────────────────────────────────────────────────────┘ 章节标题 [已就绪] ┌───────────────┐ │ [缩略图] │ ← 高 120px、宽度自适应,下面一行小字「点击查看大图」 └───────────────┘ 正文… ``` - 缩略图是按钮(可 Tab、可回车/Space 打开),`aria-label` 为「查看《章节标题》的插图大图」。 - 点击后在对话框里显示**原图**:最大 `min(88vw, 1200px)` × `78vh`,保持比例不裁切;Esc 或关闭按钮退出,退出后正文位置不变。 - 无插图的章节:不显示占位、不显示按钮(与之前一致)。 ### 实现与验证 - 前端改动:`BookView.vue` 移除缩略图列、`library.ts` 去掉列表预取、`ReaderView.vue` 把插图换成缩略图按钮 + `ElDialog`,样式新增 `.illustration-thumb`/`.illustration-full`;**后端与 schema 无变化**。 - 测试:学习端 157 项单测与 26 项 E2E 全部通过(新增/更新的断言:章节列表无 `<img>` 且不请求 `/illustration`;阅读页缩略图是按钮且点击后弹窗内 `img` 的 `src` 为对象 URL;弹窗默认不存在)。 - 真实链路(真实 API+MySQL+真实浏览器,本机):在 issue5_a 自己的一本书(示例 `/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`。 ### 请确认 1. 修订后的 S2b(列表无图片)与 S3(缩略图 + 弹窗原图)是否符合要求? 2. 若确认,本单可进入验收;如需在列表上加「有插图」文字标记,或希望缩略图更大/更小,请指出尺寸或位置。
Author
Owner

#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: b9666699e8f01a117412b0b65880b0402b1bef0c

  • Business-Rules-and-Glossary: a226a6026e8415d6f2981fc87b52396f30ab3708

  • Local-Development-and-Verification: 0550efcfd3844fb0af2dd7e59471f24f444eadf0

  • Product-Requirements-Overview: a54b9c33ece05a90cc28254273777dff9388167b

  • Home: 6ff5329b98016970310da9f8f6d2c5e821e39714

  • Deployment-and-Operations: b9eda78d3e71dab02dbf2e3af700c81d9c725f8d

  • 镜像校验:harness.py sync --check 通过,6 个变更页逐字节正文比对一致。

## #37 插图交互修订完成,待验收(2026-09-15) 按用户要求把章节插图从「书籍页列表缩略图 + 阅读页大图」改为「**只在阅读页显示缩略图,点击弹窗看原图**」,并去掉书籍页章节列表的图片列(同时不再为列表预取图片)。设计证据修订见评论 [8167](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-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: `b9666699e8f01a117412b0b65880b0402b1bef0c` - Business-Rules-and-Glossary: `a226a6026e8415d6f2981fc87b52396f30ab3708` - Local-Development-and-Verification: `0550efcfd3844fb0af2dd7e59471f24f444eadf0` - Product-Requirements-Overview: `a54b9c33ece05a90cc28254273777dff9388167b` - Home: `6ff5329b98016970310da9f8f6d2c5e821e39714` - Deployment-and-Operations: `b9eda78d3e71dab02dbf2e3af700c81d9c725f8d` - 镜像校验:`harness.py sync --check` 通过,6 个变更页逐字节正文比对一致。
Author
Owner

#37 附件并入章节编辑对话框,待验收(2026-09-15)

用户提出:章节列表每行的「附件」与「编辑」合成一个——保留「编辑」,点开后在同一编辑页里编辑章节标题、内容、插图和音频。已按此实施。

我的评估(工单里请用户确认的部分)

合适:一个入口、一处编辑,章节行更干净,符合「编辑这一章」的直觉;正文改版(重新处理)不会影响已上传的插图与音频,因为它们挂在章节上。

需要说清的唯一风险:同一个对话框里有两种生效时机——标题与正文点「保存章节」后生效,而文件是选中即上传。用户可能以为点「取消」什么都没发生,其实图片/音频已经存进去了。我没有改成延迟上传(那会引入「正文保存成功、文件上传失败」这类半成品状态,更复杂),而是在对话框里明确写出这一点:data-testid="attachment-timing" 的文案为「标题与正文点「保存章节」后生效;插图与音频在选中文件后立即上传,取消不会撤销已上传的文件。」

实现

  • 章节行只保留一个「编辑」按钮(data-testid="edit-chapter-{id}"),原「章节附件」对话框与「附件」按钮移除。
  • 章节编辑对话框内新增「本章插图」「本章音频」两个区块(复用附件预览/状态/上传/替换/移除与规格提示的排版),标题与正文的保存按钮与语义不变。
  • 后端与 schema 无变化(仍 v9)。

验证

  • 学习端 157 项单测与 26 项 E2E 全部通过(新断言:章节行只有一个按钮;编辑对话框同时包含标题、正文、插图与音频控件以及生效时机说明;在对话框内上传后仍可用「保存章节」提交标题与正文)。

  • 真实链路(真实 API+MySQL+真实浏览器,issue5_a 自己的书):章节行按钮数 = 1 且文本为「编辑」;对话框内标题/正文/插图状态/音频状态/时机说明齐备(截图 issue37-real-chapter-editor.png);在编辑框内上传真实 PNG 后状态立即变为「已设置」而标题仍是原值(说明文件即时生效、文本等待保存);取消后进入阅读页仍为缩略图+弹窗原图;最后移除 fixture 插图复原数据。

  • 文档 revision:

  • Architecture-and-Code-Map: c75593d72432e5195ea84c766a81449ccb91f648

  • Business-Rules-and-Glossary: 081daac73fa8d172a477d196e964382735122466

  • Local-Development-and-Verification: 5459d79e2bf6339450af5add58a61ef22b0467ea

  • Product-Requirements-Overview: 5ecd946c6731afbbb87f0ab3d68988583fa4e2bb

  • Home: 7a96206070fbd799d76a12ec07dd4520c2be1a81

  • 镜像校验:harness.py sync --check 通过,6 个变更页逐字节正文比对一致。

请确认

若这个合并方式与「立即上传+明确文案」的处理可以接受,本单即可验收;若你希望文件也改成「点保存才上传」(可能出现部分成功),或希望在关闭时对未保存的正文做二次确认,请指出。

## #37 附件并入章节编辑对话框,待验收(2026-09-15) 用户提出:章节列表每行的「附件」与「编辑」合成一个——**保留「编辑」,点开后在同一编辑页里编辑章节标题、内容、插图和音频**。已按此实施。 ### 我的评估(工单里请用户确认的部分) **合适**:一个入口、一处编辑,章节行更干净,符合「编辑这一章」的直觉;正文改版(重新处理)不会影响已上传的插图与音频,因为它们挂在章节上。 **需要说清的唯一风险**:同一个对话框里有两种生效时机——标题与正文点「保存章节」后生效,而文件是**选中即上传**。用户可能以为点「取消」什么都没发生,其实图片/音频已经存进去了。我没有改成延迟上传(那会引入「正文保存成功、文件上传失败」这类半成品状态,更复杂),而是在对话框里明确写出这一点:`data-testid="attachment-timing"` 的文案为「标题与正文点「保存章节」后生效;插图与音频在选中文件后立即上传,取消不会撤销已上传的文件。」 ### 实现 - 章节行只保留一个「编辑」按钮(`data-testid="edit-chapter-{id}"`),原「章节附件」对话框与「附件」按钮移除。 - 章节编辑对话框内新增「本章插图」「本章音频」两个区块(复用附件预览/状态/上传/替换/移除与规格提示的排版),标题与正文的保存按钮与语义不变。 - 后端与 schema **无变化**(仍 v9)。 ### 验证 - 学习端 157 项单测与 26 项 E2E 全部通过(新断言:章节行只有一个按钮;编辑对话框同时包含标题、正文、插图与音频控件以及生效时机说明;在对话框内上传后仍可用「保存章节」提交标题与正文)。 - 真实链路(真实 API+MySQL+真实浏览器,issue5_a 自己的书):章节行**按钮数 = 1 且文本为「编辑」**;对话框内标题/正文/插图状态/音频状态/时机说明齐备(截图 `issue37-real-chapter-editor.png`);在编辑框内上传真实 PNG 后状态立即变为「已设置」而标题仍是原值(说明文件即时生效、文本等待保存);取消后进入阅读页仍为缩略图+弹窗原图;最后移除 fixture 插图复原数据。 - 文档 revision: - Architecture-and-Code-Map: `c75593d72432e5195ea84c766a81449ccb91f648` - Business-Rules-and-Glossary: `081daac73fa8d172a477d196e964382735122466` - Local-Development-and-Verification: `5459d79e2bf6339450af5add58a61ef22b0467ea` - Product-Requirements-Overview: `5ecd946c6731afbbb87f0ab3d68988583fa4e2bb` - Home: `7a96206070fbd799d76a12ec07dd4520c2be1a81` - 镜像校验:`harness.py sync --check` 通过,6 个变更页逐字节正文比对一致。 ### 请确认 若这个合并方式与「立即上传+明确文案」的处理可以接受,本单即可验收;若你希望文件也改成「点保存才上传」(可能出现部分成功),或希望在关闭时对未保存的正文做二次确认,请指出。
Author
Owner

#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-overlay 位置
移除导入(复现) static 无(static) top = 648px,正好等于章节列表底部
恢复导入(修复后) relative(由浮层定位) fixed top = 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 的本地验证页:涉及"浮层/遮挡/布局"的属性必须显式断言,不能只依赖可见性。

验证

  • 学习端 157 项单测、26 项 E2E(含新增的浮层断言)全部通过;构建通过。
  • 真实链路:章节编辑对话框与删除确认均为固定浮层、对话框居中且带半透明遮罩(截图 issue37-real-dialog-modal.png、issue37-real-confirm-modal.png);取消确认后回到编辑对话框,取消编辑后对话框关闭。
  • 提交 c22f9c3(同一分支 PR #38,未合并);文档 revision 见下一条评论。

关于「弹窗或新 tab」

现在它是弹窗(模态浮层):点「编辑」后覆盖在页面上、带遮罩、Esc 或「取消」关闭。如果你更希望它打开独立页面/新标签(例如 /chapters/:id/edit 整页编辑),那是一次导航结构变化,我可以改——请说一声要哪种(以及是否要保留弹窗方式作为快速编辑)。

## #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-overlay` | 位置 | |---|---|---|---| | 移除导入(复现) | `static` | 无(`static`) | `top = 648px`,正好等于章节列表底部 | | 恢复导入(修复后) | `relative`(由浮层定位) | `fixed` | `top = 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 的本地验证页:涉及"浮层/遮挡/布局"的属性必须显式断言,不能只依赖可见性。 ### 验证 - 学习端 157 项单测、26 项 E2E(含新增的浮层断言)全部通过;构建通过。 - 真实链路:章节编辑对话框与删除确认均为固定浮层、对话框居中且带半透明遮罩(截图 `issue37-real-dialog-modal.png`、`issue37-real-confirm-modal.png`);取消确认后回到编辑对话框,取消编辑后对话框关闭。 - 提交 `c22f9c3`(同一分支 PR #38,未合并);文档 revision 见下一条评论。 ### 关于「弹窗或新 tab」 现在它是**弹窗(模态浮层)**:点「编辑」后覆盖在页面上、带遮罩、Esc 或「取消」关闭。如果你更希望它**打开独立页面/新标签**(例如 `/chapters/:id/edit` 整页编辑),那是一次导航结构变化,我可以改——请说一声要哪种(以及是否要保留弹窗方式作为快速编辑)。
Author
Owner

#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 均为空串(无数据变化)。

实现

  • 作者语义:可选自由文本,≤120 字符,去首尾空白,留空即清空;只用于展示,不参与归类/检索/权限;只改作者不会重新处理章节(状态与内容版本不变)。ChapterSummary 与 ChapterSource 都返回它,阅读页在章节标题下显示非空作者(data-testid="chapter-author-line")。
  • 对话框排版:标题与作者改为「标签在左、输入框在右」的同一行(.field-row),正文 textarea 由 10 行提升到 18 行;插图与音频压成各一两行(标签+状态+上传/替换/移除,规格写在下面一行);时机说明合并成一行:「标题、作者与正文点保存后生效;插图与音频选中文件后立即上传。」

验证

项 结果
Go 89 项顶层用例通过(新增 TestMySQLChapterAuthorRoundTrip:作者往返、trim、≤120 边界、清空、仅改作者不重新处理、列表与阅读器都带、越权 404;新增 TestMigrationFromV9AddsChapterAuthor:旧库缺列 → 迁移补列 → 回退标记 → 重新升级不丢数据)
学习端 157 项单测与 26 项 E2E 全部通过(新断言:编辑对话框含作者输入与其同行标签;文件提示已精简;保存请求带 author)
真实链路 标题标签与输入框中线偏差 <8px(同一行)、作者行位于标题下方且标签同行、正文编辑框高度 415px(此前约 250px)、保存作者后阅读页显示该行、清空后该行消失(截图 issue37-real-editor-layout.png、issue37-real-reader-author.png)
迁移 开发库 v9→v10,既有数据未变;回退件 .local/lexgo-pre-issue37b.exe(回退时把 lexgo_schema.version 写回 9 即可,多出的空列不影响旧二进制)
  • 提交 3fe9bcb(同一分支 PR #38,未合并)。

  • 文档 revision:

  • Architecture-and-Code-Map: 2ccdc108a21abe67ad77c05af59bb34bac304093

  • Business-Rules-and-Glossary: 77e1f1291a201744bdbb556ed1ef7d81cef2f969

  • Local-Development-and-Verification: 92144cd570153b560180e195c49524fdb95a1048

  • Product-Requirements-Overview: 1fc22dc050d51e4d4013d2a58b914f7b972116ca

  • Home: dd5bc8b5423219fcc90d772bafeb56845644d16b

请确认

  1. 作者字段的语义(可选、≤120 字符、空即清空、只在阅读页显示)是否符合你的预期?如果你希望在书库/章节列表也显示作者,或希望它能从书级默认作者继承,请说明。
  2. 对话框现在的排版(标题/作者各一行、正文 18 行、插图与音频各两行)是否可以验收?
## #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 均为空串(无数据变化)。 ### 实现 - **作者语义**:可选自由文本,≤120 字符,去首尾空白,**留空即清空**;只用于展示,不参与归类/检索/权限;**只改作者不会重新处理章节**(状态与内容版本不变)。`ChapterSummary` 与 `ChapterSource` 都返回它,阅读页在章节标题下显示非空作者(`data-testid="chapter-author-line"`)。 - **对话框排版**:标题与作者改为「标签在左、输入框在右」的同一行(`.field-row`),正文 textarea 由 10 行提升到 **18 行**;插图与音频压成各一两行(标签+状态+上传/替换/移除,规格写在下面一行);时机说明合并成一行:「标题、作者与正文点保存后生效;插图与音频选中文件后立即上传。」 ### 验证 | 项 | 结果 | |---|---| | Go | **89 项顶层用例通过**(新增 `TestMySQLChapterAuthorRoundTrip`:作者往返、trim、≤120 边界、清空、仅改作者不重新处理、列表与阅读器都带、越权 404;新增 `TestMigrationFromV9AddsChapterAuthor`:旧库缺列 → 迁移补列 → 回退标记 → 重新升级不丢数据) | | 学习端 | 157 项单测与 26 项 E2E 全部通过(新断言:编辑对话框含作者输入与其同行标签;文件提示已精简;保存请求带 author) | | 真实链路 | 标题标签与输入框**中线偏差 <8px**(同一行)、作者行位于标题下方且标签同行、**正文编辑框高度 415px**(此前约 250px)、保存作者后阅读页显示该行、清空后该行消失(截图 `issue37-real-editor-layout.png`、`issue37-real-reader-author.png`) | | 迁移 | 开发库 v9→v10,既有数据未变;回退件 `.local/lexgo-pre-issue37b.exe`(回退时把 `lexgo_schema.version` 写回 9 即可,多出的空列不影响旧二进制) | - 提交 `3fe9bcb`(同一分支 PR #38,未合并)。 - 文档 revision: - Architecture-and-Code-Map: `2ccdc108a21abe67ad77c05af59bb34bac304093` - Business-Rules-and-Glossary: `77e1f1291a201744bdbb556ed1ef7d81cef2f969` - Local-Development-and-Verification: `92144cd570153b560180e195c49524fdb95a1048` - Product-Requirements-Overview: `1fc22dc050d51e4d4013d2a58b914f7b972116ca` - Home: `dd5bc8b5423219fcc90d772bafeb56845644d16b` ### 请确认 1. 作者字段的语义(可选、≤120 字符、空即清空、只在阅读页显示)是否符合你的预期?如果你希望**在书库/章节列表也显示作者**,或希望它能从书级默认作者继承,请说明。 2. 对话框现在的排版(标题/作者各一行、正文 18 行、插图与音频各两行)是否可以验收?
Author
Owner

#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 都返回它(书库列表因此也带着作者数据,但当前界面未在书库列表显示,需要的话我可以加)。
  • 书籍页封面压成一行:预览 120px +「上传/替换封面」+「移除」,不再有标题、字段名与规格提示;.title-line 在书名右侧显示作者(未设置则不显示)。
  • 「编辑书籍」对话框用与章节编辑相同的同行标签排版编辑书名与作者。

验证

项 结果
Go 91 项顶层用例通过(新增 TestMySQLBookAuthorRoundTrip:书名+作者同保存、trim、省略保留、空串清空、≤120 边界、列表/详情/阅读器响应都带、越权 404;新增 TestMigrationFromV10AddsBookAuthor:旧库缺列 → 迁移补列 → 回退标记 → 重新升级保留数据)
学习端 157 项单测与 26 项 E2E 全部通过(更新断言:书籍对话框含作者输入、保存请求带 author、封面区块不再有规格文字、章节行仍只有一个按钮)
真实链路 页面不再出现那四段文字;封面行高 68px、章节列表紧随其后(间距 18px);「编辑书籍」对话框可改书名与作者;保存后作者显示在书名同一行右侧,刷新后保留;清空作者后标签消失(截图 issue37-real-book-page.png)
迁移 v10→v11,既有数据未变;回退件 .local/lexgo-pre-issue37c.exe(回退写回版本号 10)
  • 提交 728f7d6(同一分支 PR #38,未合并)。

  • 文档 revision:

  • Architecture-and-Code-Map: 0bca600ea0dfcb18141920f448954bb9e2fa067c

  • Business-Rules-and-Glossary: b688ef278f83c8055e16f91843a239f9ea2dd492

  • Local-Development-and-Verification: 8985f93f7319b2e6a428fc4417f353a7b909d797

  • Product-Requirements-Overview: 21ca050a33bdbe8133c921b6b302fbfc8d8cf7fe

  • Home: da0f9a0feb70b7099edcf8adf50a18a15435dd33

请确认

  1. 书籍页现在这样(一行封面控件 + 书名右侧作者 + 章节列表上移)可以验收吗?
  2. 需要的话我可以把书级作者显示到书库卡片,或做「章节作者留空时显示书级作者」的继承;这两项都还没做,等你明确。
## #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` 都返回它(书库列表因此也带着作者数据,但**当前界面未在书库列表显示**,需要的话我可以加)。 - 书籍页封面压成一行:预览 120px +「上传/替换封面」+「移除」,不再有标题、字段名与规格提示;`.title-line` 在书名右侧显示作者(未设置则不显示)。 - 「编辑书籍」对话框用与章节编辑相同的同行标签排版编辑书名与作者。 ### 验证 | 项 | 结果 | |---|---| | Go | **91 项顶层用例通过**(新增 `TestMySQLBookAuthorRoundTrip`:书名+作者同保存、trim、省略保留、空串清空、≤120 边界、列表/详情/阅读器响应都带、越权 404;新增 `TestMigrationFromV10AddsBookAuthor`:旧库缺列 → 迁移补列 → 回退标记 → 重新升级保留数据) | | 学习端 | 157 项单测与 26 项 E2E 全部通过(更新断言:书籍对话框含作者输入、保存请求带 author、封面区块不再有规格文字、章节行仍只有一个按钮) | | 真实链路 | 页面不再出现那四段文字;**封面行高 68px**、章节列表紧随其后(间距 18px);「编辑书籍」对话框可改书名与作者;保存后作者显示在书名**同一行右侧**,刷新后保留;清空作者后标签消失(截图 `issue37-real-book-page.png`) | | 迁移 | v10→v11,既有数据未变;回退件 `.local/lexgo-pre-issue37c.exe`(回退写回版本号 10) | - 提交 `728f7d6`(同一分支 PR #38,未合并)。 - 文档 revision: - Architecture-and-Code-Map: `0bca600ea0dfcb18141920f448954bb9e2fa067c` - Business-Rules-and-Glossary: `b688ef278f83c8055e16f91843a239f9ea2dd492` - Local-Development-and-Verification: `8985f93f7319b2e6a428fc4417f353a7b909d797` - Product-Requirements-Overview: `21ca050a33bdbe8133c921b6b302fbfc8d8cf7fe` - Home: `da0f9a0feb70b7099edcf8adf50a18a15435dd33` ### 请确认 1. 书籍页现在这样(一行封面控件 + 书名右侧作者 + 章节列表上移)可以验收吗? 2. 需要的话我可以把**书级作者显示到书库卡片**,或做「章节作者留空时显示书级作者」的继承;这两项都还没做,等你明确。
Author
Owner

#37 代码审核:达标(2026-09-16,Claude Code)

审核对象:分支 feat/37-chapter-attachments,提交 0fd661e→728f7d6(PR #38)。这单在实施过程中经历了 6 轮迭代(章级音频/插图 → 插图交互改缩略图+弹窗 → 附件并入章节编辑对话框 → 修复对话框样式缺陷 → 章节作者字段 → 书籍作者字段),只读审阅代码,没有重跑测试。

流程合规性核对

  • 每一轮新增交互都先出线框/设计证据(评论 8152、8167)经用户确认后才编码,符合工单自设的"设计证据门禁"。
  • 中途两次追加的 schema 变更(v10 章节作者、v11 书籍作者)都在评论里先说明"这不是纯前端改动,需要 schema 变更",再给出实现方式,属于如实披露、没有先斩后奏。
  • 范围从最初的"音频+插图改章级"膨胀到"新增作者字段",是用户在会话中逐步追加的要求("按用户两点要求""按用户三点要求"),不是 pi 自行扩大范围。

代码核对

  • schema v9/v10/v11: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,Claude Code) 审核对象:分支 `feat/37-chapter-attachments`,提交 `0fd661e`→`728f7d6`(PR #38)。这单在实施过程中经历了 6 轮迭代(章级音频/插图 → 插图交互改缩略图+弹窗 → 附件并入章节编辑对话框 → 修复对话框样式缺陷 → 章节作者字段 → 书籍作者字段),只读审阅代码,没有重跑测试。 ### 流程合规性核对 - 每一轮新增交互都先出线框/设计证据(评论 8152、8167)经用户确认后才编码,符合工单自设的"设计证据门禁"。 - 中途两次追加的 schema 变更(v10 章节作者、v11 书籍作者)都在评论里**先说明"这不是纯前端改动,需要 schema 变更"**,再给出实现方式,属于如实披露、没有先斩后奏。 - 范围从最初的"音频+插图改章级"膨胀到"新增作者字段",是用户在会话中逐步追加的要求("按用户两点要求""按用户三点要求"),不是 pi 自行扩大范围。 ### 代码核对 - **schema v9/v10/v11**:`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` 安全配置读入进程。
Author
Owner

#37 验收通过(2026-09-16)

用户于 2026-09-16 回复「#37 通过验收」(此前回复中提到的 #3 已于 2026-09-10 验收关闭,本单为实际待验收项,已在会话中核对确认)。代码审核见评论 8197:达标,可以进入用户验收,未提出待改项。

  • 验收范围:设计证据与增量契约(评论 8152)及其后用户驱动的六轮迭代(8167 插图改缩略图+弹窗、8172 附件并入章节编辑、8178 对话框样式缺陷修复、8182 章节作者与对话框紧凑化、8185 书籍作者与书籍页精简)。
  • 最终形态:音频与插图挂在章节上、封面留在书上;阅读页插图缩略图+点击弹窗看原图,播放器只在本章有音频时出现;章节行只有一个「编辑」,对话框同时管理标题/作者/正文/插图/音频;书籍页封面一行紧凑控件、书名右侧显示书级作者、「编辑书籍」可改书名与作者。
  • schema 最终为 v11(v9 章级附件并退役书级音频、v10 章节作者、v11 书籍作者),v10/v11 使用带守卫的条件加列保持迁移可重放;开发库迁移前后逐表业务计数无变化,回退件分别为 .local/lexgo-pre-issue37.exe、-b、-c。
  • 合并:PR #38 已 fast-forward-only 合入 main,merge commit 728f7d6;验收文档提交 c293a41 已推送。
  • 验证:Go 91 项顶层用例、学习端 157 单测与 26 项 E2E、管理端 31 项、治理 65 项、真实 API+MySQL 52 项、#15 恢复演练第三次 22 项(附件按章节归属、按字节恢复、Range 可用)。
  • 文档 revision:Architecture 3cf18f98cf824dd3bbd5e8c300a09a321e47b357、Product-Requirements daf31e0d00508a7d0cf504a62072ed69322a5644、Local-Development 7c41d62a131bfcc8c00a3b42a9830600a31f9c6a、Home 3af7d77905777a9b0725e5be755eb4a7f461cad7、Project-Profile 39adde482a8729fcf303ef7ca24cd8bf136c58a6、Deployment-and-Operations b9eda78d3e71dab02dbf2e3af700c81d9c725f8d。
  • 镜像校验:harness.py sync --check 通过,6 个变更页逐字节正文比对一致。
  • 未实现(经确认不做):书库卡片显示书级作者、章节作者留空时回落显示书级作者。数据层已就绪,需要时另开小工单。
  • 未验证:真机听感与浏览器真实解码播放(E2E 中媒体元素被桩替换)、Safari/Firefox、渐进式流式播放。
  • 回退:写回 lexgo_schema.version=9(或 v10)并恢复对应备份二进制;被 v9 清空的书级音频测试数据无法找回。

单元工单关闭;#16 的 #37 复选框同步勾选。

## #37 验收通过(2026-09-16) 用户于 2026-09-16 回复「#37 通过验收」(此前回复中提到的 #3 已于 2026-09-10 验收关闭,本单为实际待验收项,已在会话中核对确认)。代码审核见评论 [8197](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8197):**达标,可以进入用户验收**,未提出待改项。 - 验收范围:设计证据与增量契约(评论 [8152](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8152))及其后用户驱动的六轮迭代([8167](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8167) 插图改缩略图+弹窗、[8172](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8172) 附件并入章节编辑、[8178](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8178) 对话框样式缺陷修复、[8182](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8182) 章节作者与对话框紧凑化、[8185](https://git.ilapage.cn/OPC/lexgo/issues/37#issuecomment-8185) 书籍作者与书籍页精简)。 - 最终形态:音频与插图挂在章节上、封面留在书上;阅读页插图缩略图+点击弹窗看原图,播放器只在本章有音频时出现;章节行只有一个「编辑」,对话框同时管理标题/作者/正文/插图/音频;书籍页封面一行紧凑控件、书名右侧显示书级作者、「编辑书籍」可改书名与作者。 - **schema 最终为 v11**(v9 章级附件并退役书级音频、v10 章节作者、v11 书籍作者),v10/v11 使用带守卫的条件加列保持迁移可重放;开发库迁移前后逐表业务计数无变化,回退件分别为 `.local/lexgo-pre-issue37.exe`、`-b`、`-c`。 - 合并:PR [#38](https://git.ilapage.cn/OPC/lexgo/pulls/38) 已 fast-forward-only 合入 main,merge commit `728f7d6`;验收文档提交 `c293a41` 已推送。 - 验证:Go **91 项顶层用例**、学习端 **157 单测与 26 项 E2E**、管理端 31 项、治理 65 项、真实 API+MySQL **52 项**、**#15 恢复演练第三次 22 项**(附件按章节归属、按字节恢复、Range 可用)。 - 文档 revision:Architecture `3cf18f98cf824dd3bbd5e8c300a09a321e47b357`、Product-Requirements `daf31e0d00508a7d0cf504a62072ed69322a5644`、Local-Development `7c41d62a131bfcc8c00a3b42a9830600a31f9c6a`、Home `3af7d77905777a9b0725e5be755eb4a7f461cad7`、Project-Profile `39adde482a8729fcf303ef7ca24cd8bf136c58a6`、Deployment-and-Operations `b9eda78d3e71dab02dbf2e3af700c81d9c725f8d`。 - 镜像校验:`harness.py sync --check` 通过,6 个变更页逐字节正文比对一致。 - 未实现(经确认不做):书库卡片显示书级作者、章节作者留空时回落显示书级作者。数据层已就绪,需要时另开小工单。 - 未验证:真机听感与浏览器真实解码播放(E2E 中媒体元素被桩替换)、Safari/Firefox、渐进式流式播放。 - 回退:写回 `lexgo_schema.version=9`(或 v10)并恢复对应备份二进制;被 v9 清空的书级音频测试数据无法找回。 单元工单关闭;#16 的 #37 复选框同步勾选。
ila closed this issue 2026-09-16 10:27:35 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/lexgo#37