Commit Graph
7 Commits
Author SHA1 Message Date
hectorqin 7f4cc477c4 feat(web): 书库浏览页与文件管理拆成两个屏幕,图标改自绘 2026-09-19 23:24:34 +08:00
hectorqin 558f44e206 feat(web,server): 书架默认按最近阅读排,书库拆成预览与文件两页 2026-09-19 14:17:55 +08:00
hectorqin c7d065bd52 feat(web): 书架与书库拆成两个可分页的屏幕 2026-09-19 08:45:37 +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 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
hectorqin 3c3e461a49 feat(web): 界面图标换成自带字体,补 UI/UX 规范并统一设计令牌
按钮里放的是 Unicode 字符——`☰` `⚙` `✕` `⟨` `⟩` `+` `🗂` `📁` `⮝`——
和 emoji。八个字形来自八个字族,光学重量各不相同;`🗂` `📁` `⏮` 在部分平台
被渲染成彩色的,而一个彩色 emoji 出现在单色工具栏里,是「没有设计过」最直接的
信号。不同平台的字体回退还不同,同一个界面在 Android 和桌面浏览器上长得不一样。
此外样式表里所有按钮都没有 `:focus-visible` 规则,键盘走一遍看不到焦点在哪。

所以这次做三件事:图标改成自带字体、把缺失的交互状态补齐、把散落的字面量收成
设计令牌,并把规范写成文档。

图标用字体而不是内联 SVG,理由是**一致性可以被强制**:字体把字形变成字符,它
继承 `color` 和 `font-size`,没有自己的几何,因此没有可以漂移的地方;而三十个
内联 SVG 组件里,颜色、尺寸、线宽每一项都是单个字形可以漂移的位置。字体是
bundle 里的一个 16KB 相对路径文件,`file://` 下能加载,与「一份产物两个宿主」
的既有约束一致。

字形由 `web/tools/icons/` 下的脚本从描边路径编译:路径 → 折线 → 圆头矩形 →
精确布尔并(Clipper)→ TTF。先做过一版「栅格化 + 边界追踪」,失败了:笔画宽度
只有一个栅格单元,而逐单元的边界规则无法表达「边界在一格宽的图形上左右交替」,
结果是一个圆被追踪成 58 段碎片。精确布尔没有这种情形。

放弃的方案:内联 SVG(前面说的漂移问题)、把图标做成 React 组件并逐个手绘
(每个都是独立的几何,光学重量必然不一致)、从 CDN 引用图标字体(本产品必须
在无外网环境下工作)。

界面改动:
- `Icon` 组件 + 37 个字形,码位表由生成器写出,测试直接解析字体 `cmap` 核对
- `IconButton` 的 `icon` 是字形名而非任意 `children`,传错名字是编译错误;
  `label` 必填,字形始终 `aria-hidden`
- 补上全部按钮的 `:focus-visible`(本次之前完全缺失)
- 间距/字号/动效收成令牌;`prefers-reduced-motion` 通过折叠时长令牌实现,
  而不是给每条动画各加一个媒体查询
- 悬停包进 `@media (hover: hover) and (pointer: fine)`,避免触屏上按钮停在悬停态

验证:`web` 336 个测试通过(新增 9 个图标契约测试);`typecheck`、`build`、
`check-shell.sh`、`verify-web-assets.sh` 全过;字体已确认被打进 Android assets。
真浏览器(Chromium + 真实服务端)实测了书架、书库管理、阅读器三个屏幕的浅色与
深色主题,以及键盘 Tab 下的焦点环。产物 259.5KB → 259.5KB(gzip 81.4KB),
新增 16KB 字体、CSS 减到 24.4KB。

Ref: #29
2026-09-18 07:08:02 +08:00