hectorqin
7bff77eaf8
feat(reader): deliver P0-P2 reading tools and ecosystem integrations
...
Add reliable reading progress, verified backup and restore, reading tools, offline downloads, source quality checks, personal credential status, OPDS serving and verified WebDAV backup uploads.
2026-09-24 07:47:32 +08:00
hectorqin
95cb3134db
feat(sources): support default source and refine switching UI
2026-09-23 15:16:15 +08:00
hectorqin
77adffb160
fix(web): keep extension tabs readable on narrow screens
2026-09-23 14:00:43 +08:00
hectorqin
f773b6e1fc
feat(search): group books by title and authors with source selection
2026-09-23 13:40:38 +08:00
hectorqin
4896e5efc7
feat(reader): stream alternative sources and refresh current chapters
...
Add book information dialogs and compact toolbar actions with source provenance. Reuse Streamable HTTP for alternative discovery and preserve cached text when a forced refresh fails.
2026-09-23 13:16:54 +08:00
hectorqin
cb96d48d62
fix(search): stop on stream disconnect and allow concurrent book actions
2026-09-23 11:41:24 +08:00
hectorqin
47baee014e
feat(ui): unify page feedback with global Notyf notifications
...
Route login, shelf, library, file operations, reading, sources and extension feedback through one compact themed notification host. Keep native modal feedback in the top layer, escape remote text, update progress in place and let reading controls receive clicks beneath message text. Cover lifecycle, error handling and layout with unit and Chromium checks.
2026-09-23 10:13:03 +08:00
hectorqin
0782202dae
fix(plugins): isolate search stop failures and float operation feedback
...
A searchCancel deadline previously killed the shared plugin process and prevented subsequent acquisition. Release only the stop request and allow late cleanup to finish. Keep ordinary hard cancellation unchanged. Cover process and HTTP acquisition regressions and compact extension feedback without layout shifts.
2026-09-23 09:46:02 +08:00
hectorqin
1fda200d69
fix(ui): show source operation feedback as compact floating notices
2026-09-23 09:33:45 +08:00
hectorqin
d7ebc3c898
feat(plugins): support web npm and tgz installation
2026-09-22 19:21:37 +08:00
hectorqin
a131a8cc45
feat(plugins): add bounded diagnostic output
2026-09-22 15:56:22 +08:00
hectorqin
b49caf0ffc
feat(sources): add cancellable batch search and configurable concurrency
2026-09-22 10:30:46 +08:00
hectorqin
6a6a391197
fix(web): unify desktop source widths and refine reading layout
2026-09-21 17:15:58 +08:00
hectorqin
1663773f4d
feat(web): adapt collections and reading controls for desktop
2026-09-21 17:01:19 +08:00
hectorqin
4a1aa7a4d0
fix(web): remove shelf settings sticky header gap
2026-09-21 15:45:01 +08:00
hectorqin
3420bf07ce
fix(library): 限定管理员上传并完善文件管理导航与布局
2026-09-21 15:25:28 +08:00
hectorqin
854bfff1ce
fix(ui): 统一书架书库入口与空状态层级
2026-09-21 14:25:05 +08:00
hectorqin
3a253c1c1a
fix(sources): 使用配置弹窗并拆分搜书与书源管理
2026-09-21 14:03:35 +08:00
hectorqin
d8cb7cc84c
fix(sources): 明确顶部 Tab 并收起次要管理操作
2026-09-21 13:48:45 +08:00
hectorqin
8e8ca23d3b
fix(sources): 完善配置交互并增加真实端到端测试
2026-09-21 13:03:30 +08:00
hectorqin
c27c505f24
feat(sources): 按书源实例隔离插件配置并拆分管理 Tab
2026-09-21 12:28:50 +08:00
hectorqin
964a666f5b
feat(plugins): 支持独立配置页、多书源订阅与目录换源
2026-09-21 11:50:21 +08:00
hectorqin
59faec336a
merge: 同步远程阅读界面更新并保留书源配置样式
2026-09-21 09:39:39 +08:00
hectorqin
c192fc2f12
feat(sources): add configuration pages and chapter updates
2026-09-20 23:59:12 +08:00
hectorqin
d60d7e67f9
feat(reader): 配置四角阅读信息
2026-09-20 17:36:59 +08:00
hectorqin
da1aa15174
fix(reader): 稳定阅读位置并重整手机阅读界面
...
固定工具栏显隐时的正文区域,保存最新阅读偏移并抑制亚像素量测抖动。重组底部四入口、半屏面板和章内滑杆,采用 Lucide SVG 图标并新增浅绿主题;补齐 PDF 内容传递。
验证:构建通过,498 项单元测试、29 项接口及分页测试、30 项 UI 检查通过。
2026-09-20 17:02:40 +08:00
hectorqin
847f046724
合并来自 auto/reader-indicator-avoid-text-b5b4 的合并请求 #53
...
fix(reader): 读数行不再压住正文,翻页落在整行上
PR-URL: #53
2026-09-20 15:21:27 +08:00
hectorqin
ab22d2c3c6
fix(reader): 读数行不再压住正文,翻页落在整行上
2026-09-20 12:15:36 +08:00
hectorqin
ae397b6181
feat(web,server): 补全书架的加入/移除两侧并统一可见性判据
2026-09-20 11:52:15 +08:00
hectorqin
2afb137d37
合并来自 auto/reader-page-turn-indicator-0add 的合并请求 #51
...
fix(reader): 翻页不再丢滚动位置,阅读指示占满整行
PR-URL: #51
2026-09-20 10:48:59 +08:00
hectorqin
80382c078d
fix(reader): 翻页不再丢滚动位置,阅读指示占满整行
2026-09-20 10:37:27 +08:00
hectorqin
204b6c6388
修改样式
2026-09-20 09:54:08 +08:00
hectorqin
86c2d8631e
Merge remote-tracking branch 'origin/main' into auto/library-browse-files-split-2e69
...
把 #48 的朗读条与快捷列改动合进本分支,两处冲突都在
`web/src/ui/reader-chrome.tsx`,都取 main 那一侧:
- 快捷列:main 删掉了 目录 / 行距 / 页边距 / 设置 四个入口(#48 的报告),
本分支新增的 `menu` / `line-height` / `indent` / `sliders` 随之作废。
留下 主题 / 字号 / 听书 三项,字号改用本分支新词汇表的 `text-size`,
听书改用 `volume`。
- 朗读条:main 拆成两行(控制行 + 进度行)并加了 `tts-stop`。本分支的单行
版本丢弃。图标名按本分支的词汇表回落:`backward-step`/`forward-step`
→ `step-backward`/`step-forward`,`xmark` → `close`。
为让两边都能编译,`icon-names.ts` 增加一层别名表 `ICON_ALIASES`
(`font`/`volume-high`/`bars`/`gear`/`xmark`/`magnifying-glass`/`books`
→ 已画的码点),另导出 `ICON_CODEPOINT_TABLE`。别名是**拼写**不是字形:
`icons.test.ts` 的借用名单断言只管 `paths.mjs`,新增两条断言钉住别名既
落在被否名单里、又必须指向字体真画了的码点;码点唯一性改按去重后的集合计。
2026-09-20 09:34:58 +08:00
hectorqin
7f4cc477c4
feat(web): 书库浏览页与文件管理拆成两个屏幕,图标改自绘
2026-09-19 23:24:34 +08:00
hectorqin
09a7d4df5d
fix(web): 书内目录链接当章节跳转,朗读条可停止,页面铺满且工具栏浮动
2026-09-19 23:04:03 +08:00
hectorqin
9ad0e64a4c
合并来自 auto/shelf-library-sort-icons-cc2f 的合并请求 #46
...
feat(web,server): 书架默认按最近阅读排,书库拆成预览与文件两页
PR-URL: #46
2026-09-19 22:24:58 +08:00
hectorqin
7f74baab68
fix(web): 插图收进版心,快捷列补齐并可滚动,点击区按布局几何
2026-09-19 14:46:15 +08:00
hectorqin
558f44e206
feat(web,server): 书架默认按最近阅读排,书库拆成预览与文件两页
2026-09-19 14:17:55 +08:00
hectorqin
4715704d84
合并来自 auto/reader-floating-chrome-de96 的合并请求 #45
...
fix(web): 工具栏改为浮层,并给章节子资源带上令牌
PR-URL: #45
2026-09-19 13:23:24 +08:00
hectorqin
354e7eeb6d
fix(web): 工具栏改为浮层,并给章节子资源带上令牌
2026-09-19 12:31:51 +08:00
hectorqin
f2701da703
fix(web,server): 继续阅读带上完整书对象,书库能上架,放大镜回到中线
...
三处 #40 后续:继续阅读卡片点了报「不在书架上」、书库的书加不回书架、
书架搜索框的放大镜高了一行。
2026-09-19 11:46:42 +08:00
hectorqin
69993d7b37
Merge remote-tracking branch 'origin/main' into auto/reader-paragraphs-layout-images-eb50
2026-09-19 09:13:54 +08:00
hectorqin
0551f21e4e
fix(web,server): 段落不再合成一块,插图重新画出来,工具栏对齐
2026-09-19 09:09:12 +08:00
hectorqin
d489c45cf3
Merge remote-tracking branch 'origin/main' into auto/shelf-library-pagination-d4fc
2026-09-19 08:49:21 +08:00
hectorqin
c7d065bd52
feat(web): 书架与书库拆成两个可分页的屏幕
2026-09-19 08:45:37 +08:00
hectorqin
b8fda56c34
fix(web,server): 底部滑杆量章节内页数,快捷列随工具栏收放,chapter-full 改名
2026-09-19 08:17:46 +08:00
hectorqin
29f7f6b56a
feat(web): 顶栏底栏按参照稿重做,沉浸态保留章节与页码
...
现状问题。上一轮把顶栏和底栏做成"一起收放"之后,两个方向都还差一截。显示时
顶栏是"返回箭头 + 书名 + 一个 ☰",底栏是一条 3px 的进度条加五行图标;参照稿
要的是顶栏一排等重的"图标在上、文字在下"按钮、底栏一行可拖动的滑杆加一行
"上一章 / 阅读进度 / 下一章"。收起时屏幕上什么都不剩:读者收起工具栏是为了
看正文,不是为了不知道自己读到哪儿。
方案依据。顶栏改成 `.top-button`——图标在上、名称在下的等宽按钮,`space-evenly`
排布,按钮只保留这个屏幕真能做到的返回 / 目录 / 设置(参照稿里"首页/书架/书源"
含阅读页内部走不到的目的地,一个点了没处的按钮比少一个按钮更糟)。底栏第一行
换成 `input[type=range]`:滑杆一个手势就能到章末,而进度条只能报告位置,读者
想到一章末尾得连点四十次;原生控件的拇指、拖动惯性、方向键和 `aria-valuenow`
都白得。第二行是两端的上一章/下一章和中间的进度文字。收起态新增
`.reading-indicator`:章节名钉在左上、页码与总进度钉在左下,两个读数
`pointer-events: none`,让点它们的手势落到下面的翻页区,且只在收起时绘制。
放弃方案。把书名留在顶栏:读者找标题时眼睛去的是正文角上,不是工具栏,而且
标题随工具栏一起消失正是当初要一个常驻读数存在的原因。收起态只留侧边快捷列:
那解决了"能操作",没解决"知道在哪",而后者是沉浸阅读里更常被问到的那个。
顺带修掉一个真 bug:`chevron-right` 的字形路径与 `chevron-left` 逐字节相同,
所以"下一章"按钮一直指向左边。改路径、重生成字体,并补两条断言——同名字形
不得共用路径、两个 chevron 必须镜像——它们此前一条都没有,所以这个 bug 能在
所有测试全绿时存在。
改动点。`reader-chrome.tsx` 新增 `TopButton`、重写顶栏与底栏、新增
`.reading-indicator` 与 `onScrub` 通道;`reader-screen.tsx` 新增 `scrubTo`;
`reader.css` 重写顶栏/底栏/状态药丸定位,新增滑杆与阅读指示样式;`paths.mjs`
修 `chevron-right`;`docs/ui.md` §3.8 补顶栏、底栏滑杆、阅读指示三节;ui:review
新增"保留章节与页码指示 / 阅读指示不吃手势 / 进度是可拖动的滑杆 / 有页码与
阅读进度读数"四条断言。
验证方式。`cd web && npm test`(371 条)、`cd server && npm test`(205 条)
全绿;`npm run build` 通过;`npm run ui:review` 全绿,16 条尺寸断言全过,截图
见 `docs/ui-review/`。曾把 `chevron-right` 的两条新断言逐条还原验证:路径改回
旧值时两条都红。
Ref: #34
2026-09-18 22:48:03 +08:00
hectorqin
7b9c786c83
feat(web): 纯文本排版改在客户端,并让顶栏底栏一起收放
...
现状问题。TXT 的段落和缩进原本由服务端决定:`chapter-html:<n>` 把章节渲染成
带 `<p>` 的片段,客户端只是把它塞进 shadow root。于是三件事同时不对。一是缩进
设定只对服务端已经渲染好的那份生效,windowed 路径拿到的 `chapter:<n>` 是纯字符
没有段落,同一个读者同一本书会因为走哪条传输而长得不一样。二是抓取来的文件里
用来当缩进的段前空格被原样留下,和读者的缩进叠加,恰好在最需要这个设定的文件
上看起来像没生效。三是这些设定是每台设备各自的阅读偏好,烧进响应就等于把每次
调整变成一次往返。另一件事是顶栏和底栏可以各自隐藏,读者收起导航后底栏仍压着
每一页的最后两行。
方案依据。把「一段是什么」收敛到客户端的唯一定义(`formats/segments.ts`),
本地加载、windowed、流式三条路都走它,服务端的职责缩小为交出完整章节和在原文
上断段。`ReaderView` 在渲染前对纯文本章节再过一遍这个拆分(按块读回、拼回空行
再拆,所以服务端断得对不对不影响观感),缩进、段间距、去掉段前空格因此无需请求
即可作用于两条路径。顶栏与前底栏由 `.reader-screen` 上的一个 `data-chrome` 统一
开关,收起时两条栏一起归零高度并滑出、正文长满整屏,同时右侧浮出一列快捷按钮,
否则收起工具栏会变成「必须离开才能操作」的状态。
放弃方案。把服务端的 `chapter-html` 直接删掉:`href` 同时是阅读位置的标识,且
`format: 'html'` 字段已是既有契约,删掉会让旧客户端与旧位置一起失效,所以保留
作为传输,只去掉它承担的排版职责。让缩进靠服务端属性(`data-indent`)继续驱动:
那正是「设定是响应的属性」这一问题的根源。收起态用两个独立开关:写法上就能只藏
一半,而这恰是读者报的那个 bug。
改动点。新增 `web/src/formats/segments.ts`(分段、去段前空格、HTML/节点两种形
式、跨 chunk 的 carry 判定);`reader-view.ts` 增加 `retypePlainText` 并去掉旧
排版表里读服务端属性的规则;`render/text.ts` 按 `carry` 拼接跨块段落;服务端
`text-html.ts` 去掉 `indent` 选项、不再截断;`reader-chrome.tsx` 增加侧边快捷列
与设置面板顶部的「快捷」一行;`reader.css` 增加两条栏的收放与按钮列样式;图标
集新增 `text-indent` / `text-type` / `spacing`;`docs/ui.md` 补 §3.7 的边界说明
与 §3.8 沉浸式阅读。
验证方式。`cd web && npm test`(369 条)、`cd server && npm test`(205 条)全绿;
`npm run build` 通过;`npm run ui:review` 全绿,并新增「收起工具栏: 顶栏与底栏一
起走 / 都不|不可见 / 正文占满整屏 / 右侧仍有快捷按钮」四条尺寸断言(截图见
`docs/ui-review/03-reader-no-chrome.png`),评审夹具改用全角空格缩进,使「段前
空格被去掉」在截图上可见。
Ref: #34
2026-09-18 19:09:51 +08:00
hectorqin
cdc95d8d27
fix(web,server): 阅读页可读性与面板、翻页、同步频率、事件层级
...
用户报的八条里,这一提交覆盖了除图标对齐之外的七条。每一条都先做成能复现的
测试或量出来的数字,再改。
## 2. txt 没有处理就渲染,没有默认样式,也无法调整
三层都缺,所以修了三层。
服务端新增 `chapter-html:<n>`,把纯文本渲染成带 p 的片段
(`indexer/formats/text-html.ts`)。空行断段;「上一行以句末标点结尾、下一行以
句子开头」也断段——抓取来的网文常常每行一段且没有空行,少了这条整本书是一块。
章节标题行提升成 h3,否则正文会变成「第二章 落雨雨来了。」。引用仍是
`chapter:<n>`(它同时是阅读位置的标识,改它会把现有读者打回第一章),新增
`format` 字段说明取哪一份;`chapter:` 的 256KB 截断也不再作用于这份渲染结果——
那个截断会把长章节切一半。
客户端新增 `TXT_STYLESHEET`,只在 `format === 'txt'` 时注入 shadow root,作用域
`.txt-body`,所以 EPUB 作者自己的样式一点不受影响。默认两字符首行缩进,用
`2em` 而不是 `2rem`:缩进要跟字号缩放。
阅读设置新增「正文」一节(段落缩进 / 段间距 / 编码),只在 TXT 上出现,前两项
是写在 stage 上的自定义属性,所以调整不需要重新注入章节。
## 3. 翻页异常,往前翻页还会来回跳
`previous()` 用 `scrollTop > 8` 判断「还有没有地方可退」,`next()` 用
`scrollTop < limit - 8` 判断「还有没有地方可进」,然后两边都以 0.9 屏步进。
这三个数字互不相容:forward 落到的位置,back 不认为那是边界。读者看到的就是
来回跳。
改成按**页序号**算术:整屏步进,页边界由滚动**范围**(`scrollHeight -
clientHeight`)而不是内容高度推出。这一个区别是页数和可翻次数对不上的原因——
2.31 屏的一章,按内容高度算 3 页并请求 offset 0/723/1446/2169,而 2169 会被
夹到 1674,也就是第 2 个边界,于是第四次按下的位置计数器不认。
`stepSection` 改成 await:它在跨章时是 fire-and-forget,而 `turnPage` 在
`previous()` 返回后就播放动画——动画演的是读者刚离开的那一章。
## 4. sync 接口会不停的重复渲染,sync 接口调用太过频繁
两处。`onPosition` 每个位置调三次 `patch()`,而 `patch` 会重绘整棵 chrome
树,滚动时每帧三次;合并成一次。
`flushProgress` 无条件 `syncNow()`,于是 1.5 秒一轮 POST+GET,读者只要在读就
一直发。`SyncEngine.schedule()` 去抖并**保证尾次**——没有尾次的话,读者翻完
最后一页就关书,那一页永远发不出去。
## 5/6. 布局样式,以及目录/设置改半屏
面板从 `inset: 0` 改成底部半屏 + 遮罩 + 把手,上半屏正文仍然看得见:选章节、
调字号都是对下面那一页的调整,全屏让读者为了做调整而离开那一页。
状态行从 flex 列里的一行改成浮在正文上的药丸。横条每出现一次就把正文推下去
一行再收回来,也就是把读者正在读的那句挪走。`ui:review` 有一条断言盯着它。
页脚改成两行不同职责:控制行居中、读数是三个固定位置的等宽数字。目录行改成
「序号 + 标题」,当前章高亮一次而不是每行都划线;当前窗口到不了的行标记出来
而不是静静地没反应。
## 7. 点击页面的事件处理层次
一次触摸只属于一个目标,最里面那个赢。两个真缺陷:
- 必须用 `composedPath()` 而不是 `event.target`。书的正文在 shadow root 里,
shadow 边界会**重定向**事件,所以 target 是 shadow host——用 target 写不出
「段落」和「同一个 shadow 树里的按钮」的区别。
- **阅读面自己是个滚动容器**。滚动模式就是让 shadow host 滚起来,于是「可滚动
的祖先拿走手势」这条规则让它吃掉了每一个手势:翻页层级完全正确,但永远
到不了。这是「点击页面没反应」的真正原因。
另外,按到控件的触摸不再被吞掉 click——之前工具栏按钮可见、可点、没反应。
面板打开时它拥有点击:旁边露出的那一条正文不翻页。
## 8. 端到端测试要做 UI 评审
新增 `web/tools/ui-review/`:真实 Chromium(390×844、2x)加载**生产产物**,
对着替身 API 逐屏截图并测量。截图给人看(「只是丑」不是测试失败,但是缺陷),
尺寸给流水线断言(面板不遮住整页、留出 ≥120px 正文、有遮罩和把手、状态提示
不推动正文、控件 ≥28px 且有名字、没有横向溢出)。接进 `client-ui-review`
流水线,只在界面相关文件变化时触发。
这套工具第一次跑就发现了四个真缺陷:`api.toc()` 返回形状不符时 `openStaged`
用 TypeError 打死整个 open(现在回退到 manifest 自己的条目);状态行一旦进入
loading 就再也清不掉(`hideStatus` 的守卫挡住了唯一的出口);翻页步进的 0.9 屏;
以及上面那条阅读面吃掉手势。
## 测试
web 351 项、server 206 项全过。新增:`gestures.test.ts`(事件层级优先级)、
`reader-paging.test.ts`(页序号算术,含子像素取整与滚动范围)、
`mount.test.ts`(单次渲染与卸载后丢弃更新)、`text-html.test.ts`(分段、
转义、标题提升、不截断)。
2026-09-18 12:39:45 +08:00
hectorqin
fd743f3a3f
fix(web): 修好图标与文字的对齐,并让字体度量不再说谎
...
界面图标显示正常但歪,实测每个字形都偏离所在盒子中心 0.496em(半个盒子),
也就是 9px 以上,且每一行歪的幅度还不一样。代价是三条互相独立的缺陷叠在
一起,而此前所有图标测试都只读 `cmap` 和字形名,一条都不覆盖这些位置。
一、`.icon` 把字体自己的行盒换成了一个方块(主因)
`line-height: 0; display: inline-block; width: 1em; height: 1em` 会得到
一个**锚在基线上、向下一整个 em** 的盒子,而字形是围绕基线画的。于是墨迹贴在
盒子上沿、盒子下方整片是空的。UI 里每个容器都在居中这个盒子,盒子确实居中了,
字形却差了半个 em——这就是「显示正常但不对齐」,也是每行幅度不同的来源:各行的
容器不同,锚法就不同。
改为用字体自己的行盒(`line-height: 1`,不再给高度),只保留 `width: 1em`
这个字体给不了的东西(可预测的前进宽度),并补 `vertical-align: middle` 给
正文里行内出现的图标。**刻意不用 `transform`**:它能在 flex/grid 里生效
(`vertical-align` 在那里是惰性的),但它改的是绘制期位置,会让「图标占的位置」
和「看起来占的位置」不再一致。
二、字体度量与字形不自洽(次因)
表格声明上升部 960 / 下降部 -64(基线靠近 em 底部),而字形是按
`(GRID/2 - y)` 围绕 em 中心画的。两者各自自洽、互相矛盾,而输的是盒子。
现在上升部与下降部各取半个 em:**对称**不是审美——UI 里每个容器都在居中盒子,
只有度量对称时「居中盒子」和「居中字形」才是一回事。
同时修掉三处字段错位(都会静默通过解析):
- `OS/2` 的 `sTypoAscender/Descender` 一直写在 panose 区域,真正的排印窗口是 0/0;
- `usWinAscent/Descent`(**无符号**)也写错了位置,读出来是 0 / 65506;
- `fsSelection` 原本声明 BOLD,`usWeightClass` 是 500——一个不粗的字体谎称自己粗,
引擎就会合成加粗,而合成出来的图标是糊的。改为 `REGULAR | USE_TYPO_METRICS`。
`head` 的每个字形包围盒补一格下限:`more` 的三个点是用 0.06 单位描边写的,
四舍五入后两个轴各只剩一个点,包围盒为空——空包围盒是栅格化器有权跳过的图形,
于是「更多」按钮画出来是白的,而 `cmap` 依然映射它的码位,所以读表的断言全都
满意。`settings` 的路径解析出了一组跑到网格外的坐标(y 到 24.8、x 到 0.8),
重画成真正的齿轮;`chevron-right` 与 `chevron-left` 共用同一个框却差一个单位。
三、图标按钮有两条构建路径
`manager-more` 是手搭的 `<button class="icon-button">`,而 `.icon-button` 的
对齐靠的是「里面有 `.icon`」这个约定——手搭的那条路径绕过了它。现在一律走
`IconButton`,`label` 必填、字形是名字而不是任意子节点;图标加文字的新组合收进
`IconTextButton`,因为那正是最容易歪、且每次都歪得一样的组合。
验证:新增 10 条断言,覆盖字体度量、字形包围盒与 `.icon` 规则本身;把改动逐条
还原后它们会红(已验证)。真浏览器验收:37 个字形逐个测量,墨迹中心与盒子中心
最大偏差 0.88px(改前是 9px+);书架与阅读器两屏在真实产物上截图核对。客户端
`typecheck` / 317 个测试 / `build`、`check-shell.sh`、`verify-web-assets.sh` 全过。
产物 259.68KB → 259.68KB,字体 16.34KB,CSS 24.54KB。
Ref: #34
2026-09-18 11:30:36 +08:00