来源:
notes/documents/网页版多模块设计:外壳契约、页边档号与日程模块.md网页版多模块设计:外壳契约、页边档号与日程模块
背景与范围
2026-09-22,用 frontend-design 给网页版做了一次「多模块化」的设计:外壳怎么承载未来会不断增加的
模块,笔记与日程两种阅读模式怎么长在同一副骨架上。这一轮只出设计,不写代码。
触发这次设计的两个前提:一是未来要把 daily log 以日历形式搬上网页,网页版必须按多模块来设计,
不能做成「笔记查看器 + 以后再加一个页面」;二是上一轮的体检与实施计划(网页版前端首轮体检与优化实施计划)
只解决了坏掉的部分,没有回答「它该长成什么样」。
2026-09-22 当天把三处待拍板定了:正文用宋体(限定在阅读页)、默认素纸、第三第四模块是 任务与材料/来源。
设计依据的是真实数据,不是假设:事件表 285 条、覆盖 87 天(2025-11-18 至 2026-09-20),
最忙的一天 18 条;类型分布 life 206、reflection 30、note 25、investment 16、task 6、
meeting 2;只有 16 条有结束时间,duration_minutes 与 location 全空。笔记侧构建产物 156 篇:
文档 109 / 来源 25 / 书籍 11 / 专题 6 / 项目 4 / 知识库 1(产物里没有 notes 这一类)。
2026-09-22 订正:本文原写的「documents 71 / notes 34」「已完成 67 条」「14 本书与 3 个项目」 是设计时的旧数,已按构建产物与 SQLite 实测值改掉;原型与后续实现以订正后的数为准。
题材与读者
- 题材:一个人的私人档案室。产品叫 Folio(对开本、卷册、页码),数据全是「有编号的东西」—— 笔记有 Note Path,日记有日期。这不是 SaaS 控制台,是给自己每天翻几眼的册子。
- 读者:一个人,长期重复访问,不是新用户。因此不要欢迎语、不要「空状态邀请你开始」、不要营销首屏。
- 主任务有两个且时间尺度不同:笔记是无时间的长文(要能久读),日程是有时间的流水(要能扫读)。 多模块设计的难点就在这里,两种阅读模式必须长在同一副骨架上。
色彩
五个角色,两套主题同源:
| 角色 | 亮色 | 暗色 | 用途 |
|---|---|---|---|
| 纸 | #F1F3F4 素纸 |
#14171A 夜纸 |
底色,冷灰白,不是米白 |
| 墨 | #22303C 靛墨 |
#E6E8EA |
正文与标题 |
| 淡 | #556470 淡墨 |
#9AA5AE |
时间、计数、次要信息 |
| 青 | #2F5D8C 靛青 |
#7FA8D4 |
可交互与「当前位置」,唯一功能色 |
| 朱 | #B4442F 朱砂 |
#E0705C |
只用于标记今天与当前项,面积不超过千分之一 |
暗色不是反色滤镜,是同一组角色的另一套值——这样「亮色下白字白底」那类问题从 token 层面不可能出现。
淡墨原写
#5C6B78,在素纸上实测只有 4.93:1。2026-09-22 决定压深一档到#556470(5.48:1),保住下面那条 5:1,而不是把标准降到 4.5。
默认取素纸:这套视觉的论点就是「册子的纸」,默认压成暗色等于自己拆掉论点;同时系统级
prefers-color-scheme: dark 直接切到夜纸,夜纸同样是完整一套,不是附属品。排版度量(34 字一行、
各档对比度阈值)一律在素纸上校准,夜纸照同一套阈值验收。
字体
- 正文与标题:思源宋体(
Source Han Serif SC→Songti SC→serif)。中文长文用宋体是这套设计里 最刻意的一个选择:绝大多数中文界面默认黑体,而读 150 篇笔记的场景该有书页的质感。 - 界面与元数据:思源黑体(
Source Han Sans SC→PingFang SC→sans-serif)。导航、日期、列表、 档号都用它,保证小字号清楚。 - 数字不用等宽字体,改用
font-variant-numeric: tabular-nums,日历与计数列同样对齐。 - 字号:正文
17px / 1.9,行宽 34 个汉字(约 640px);h1 32/h2 24/h3 19; 元数据13;档号12。
宋体的适用范围:只用在阅读页的正文与标题。索引、月历、任务行、档号、按钮一律黑体——小字号 在宋体下发虚,那里不需要书页感,需要清楚。先不引入中文 web font(思源宋体动辄数 MB,为一个人 的站点不划算),靠系统栈回落;因此规定正文最小 17px。若实测在 Windows 回落到 SimSun 后仍不可接受, 退路是把正文也换黑体——外壳契约与页边节律不受影响,这也是把这套设计建立在结构而非字体上的原因。
布局:外壳契约
一个外壳,四件事固定:脊(模块)→ 索引(选什么)→ 页(看什么)→ 页边档号(这是什么)。 任何新模块只允许替换「索引」和「页」两处,外壳、路由形态、导航位置一律不动。
┌──────┬──────────────────┬────────────────────────────────────────────┐
│ │ │ │
│ 笔记 │ documents │ 档号 documents/06_dg板卡X5_… │
│ 日程 │ ──────────── │ ──────────────────────────────── │
│ 任务 │ documents 109 │ │
│ 材料 │ 来源 25 │ 正文,一行三十四个汉字,宋体,行高 1.9 │
│ │ 书籍 11 │ │
│ │ 专题 6 │ 次级标题 │
│ │ 项目 4 │ │
│ │ 知识库 1 │ 再往下是段落、清单、表格、代码 │
│ 64 │ 300 │ 余下留白 │
└──────┴──────────────────┴────────────────────────────────────────────┘
↑ 脊 ↑ 索引 ↑ 页(左侧一条 72px 的页边 gutter)
整套设计里会被记住的那个元素是页边 gutter 的垂直节律:模块名、索引行、正文头上的「身份戳」 全部对齐在同一条竖线上。笔记模块这条线上是档号(路径),日程模块是日期,任务模块是状态。 它不是装饰,是每样东西的身份证;同时它让两个模块看起来是同一本书的不同分册,而不是两套界面。
模块一:笔记
- 索引按分类分组(文档 / 来源 / 书籍 / 专题 / 项目 / 知识库),行内只有标题与 页边戳;没有卡片、没有 hover 动画,当前项左侧一个朱砂墨点。
- 页内正文用宋体,34 字一行。现在详情头那行
path · 大小 · 更新时间的中缀元数据取消, 拆成 gutter 里的两行戳(上:档号;下:更新日期)——顺带解决体检里那条「中缀加小灰字」的模板感。 - 搜索是外壳级能力,不是模块:它替换索引的内容,不新开页面。本期它只搜笔记,所以搜索框只长在笔记索引头;要不要让任何模块都能搜笔记,留着当一条决定。
模块二:日程(2026-09-22 已实现,见文末「实现后记」)
数据怎么进 web 已经定了:本地 SQLite 是权威源,Neon 是只读投影,web 读投影(ADR-0011); 第一期不做「在网页里写日记」。
数据决定了形态:一天最多 18 条、71% 是 life、几乎没有时长信息,所以周视图时间网格是错的
(大片空白),正确形态是月历定位加当日流水。
┌──────┬──────────────────┬────────────────────────────────────────────┐
┌──────┬──────────────────┬────────────────────────────────────────────┐
│ 笔记 │ 有记录的天 │ 2026 年 6 月 78 条 / 19 天 │
│ 日程 │ ──────────── │ 日 一 二 三 四 五 六 │
│ 任务 │ 3 日 7 条 │ 1 2 3 4 5 6 │
│ 材料 │ 4 日 16 条 │ 19:10 开始返程通勤 │
│ │ 5 日 1 条 │ 20:00 通勤中问点 │
│ │ 6 日 6 条 │ 20:29 通勤节点 │
│ │ 7 日 1 条 │ 还有 4 条 │
│ │ 8 日 10 条 │ 7 8 9 10 11 12 13 │
│ │ 9 日 3 条 │ 11:15 生活事件 │
│ │ ... │ ───────────────────────────────── │
│ │ │ 档号 2026-06-04 │
│ │ │ │
│ │ │ 6-4 周四 16 条 │
│ │ │ 08:21 早晨通勤开始 │
│ │ │ 09:40 早晨通勤结束 │
└──────┴──────────────────┴────────────────────────────────────────────┘
三条由数据推出的规则:
- 默认不给
life上色,只给少数类型(投资、任务、会议、回顾、笔记)一个不显眼的记号。 71% 是默认值,给它上色等于给噪声上色。 - 月历格子里放「时间 + 标题」,每格最多 3 条,第 4 行是「还有 N 条」;格子约 120px,所以 日历必须占满「页」的宽度,塞不进索引列。索引列因此改成「有记录的天」——一天一行(日期、 当天的第一条、条数),点某天,页里下半段才是当天流水(时间在 gutter,文本在页)。
- 跨模块的桥:
note类事件指向当天写下的笔记,点过去就是笔记模块里那一篇。这是两个模块之间 唯一需要的通道,也是这套外壳值得存在的理由。第一期不做:note_reference现有 285 条 全空,没有数据支撑,等有了写入口再单独定义。
模块三:任务
由 tasks/ 三个文件定义:todo.md、in_progress.md、done.md。真实体量很小——活跃的只有
5 条(待办 4、进行中 1),已完成 13 条,字段是固定的四个:目标、下一步动作、截止时间、状态时间线。
- 索引的分组换成状态(待办 / 进行中 / 已完成),与笔记模块「按分类分组」是同一手法; 已完成默认折叠,13 条历史不该占据首屏。
- 页里一条任务就是那四个字段加一条状态时间线(开始时间、本次启动时间、完成时间), 页边 gutter 放截止或完成日期。
- 因为体量小,这个模块不需要任何新控件:不加看板、不加拖拽、不做进度环。5 条任务配一个看板 是给自己演杂技。
┌──────┬──────────────────┬────────────────────────────────────────────┐
│ 笔记 │ 待办 4 │ 截止 2026-06-08 │
│ 日程 │ ──────────── │ ──────────────────────────────── │
│ 任务 │ 写技术方案 ● │ 目标 完成今天需要提交的技术方案 │
│ 材料 │ 安装 kimiclaw │ 下一步 对照 PRD 逐段 review 时间配置口径 │
│ │ ──────────── │ 时间线 06-04 11:22 开始 │
│ │ 完成 13 │ 06-08 16:43 重新启动 │
│ │ (默认折叠) │ 关联笔记 → 笔记模块 │
└──────┴──────────────────┴────────────────────────────────────────────┘
模块四:材料与来源
由 notes/sources/ 定义:共 19 个来源(14 本书 / 研报、2 个项目、3 个记录),每个来源下有
原始文件(PDF / EPUB / MOBI,共 120 MB)与转换出的全文(pdf_full_text.md / epub_full_text.md)、
有时还有转写(*.funasr.transcript.txt)。
这个模块要回答的不是「存在哪」,而是「谁写的」:材料里是别人写的原文,笔记里是我写的解读。
现在这两类混在同一个平铺列表里分不出来——而 25 个 sources/**/*.md、5.53 MB 全文其实已经进了
构建产物,只是没有自己的门。所以这个模块的第一期不是「把材料搬上网」,是「把它标出来并隔开」。
- 索引按来源分组(书名 / 研报 / 项目),每个来源下列出它有的产物类型:原文、转换全文、转写。
- 页里是转换全文(可能很长,需要页内目录),页边 gutter 放来源与产物类型。
- 硬约束:120 MB 的原始 PDF / EPUB / MOBI 不进构建产物,函数包和仓库都不该扛这个。 页里只给原始文件的本地路径与说明,不在网页里提供原文二进制。
- 同时把已上线的全文全文的体量当成需要交代的事:5.5 MB 正文压在一个 JSON 里,随每次部署重发。 真要优化,是把材料正文改成按需加载,而不是全量塞进首屏载荷。
窄屏
小于 900px 时:脊变成底部固定条(模块名横排、四五个字直接可见,不用汉堡菜单);索引收成页顶的 可折叠抽屉,默认只显示当前项;页边 gutter 降级为正文上方的一行档号。
自评:砍掉的默认款
对着计划又走了一遍,砍掉这些,每条都写了改成什么:
- 「近黑底加一个亮强调色」——当下最典型的 AI 味配色,改成素纸默认、靛墨为墨。
- 卡片套件(所有内容切成同样的圆角卡片加同一个阴影)——全部去掉,分隔只靠留白与细线, 圆角只留给按钮(2px)。
path · 大小 · 日期这种中缀元数据行——拆进 gutter 的戳。- 「小字号加字距的灰色标签」(中文里全大写 eyebrow 的等价物)——模块名与标签改用正常 字号字重,靠位置表意。
- 「大数字加小标签」的统计头——不要,计数并进索引头当普通文字。
- 小数据用等宽字体——改为
tabular-nums。 - 每张卡片的 hover 过渡与入场淡入——删,只留「当前项墨点」,并尊重
prefers-reduced-motion。 - 渐变——零。
质量底线
正文对比度至少 12:1,淡墨至少 5:1,靛青至少 4.5:1,朱砂只做非文字记号(至少 3:1);键盘焦点 在两种主题下都要可见(体检里实测通过的那条焦点环要保住);亮暗两套主题各自成体系。
已定的三处
- 正文用宋体,但只限阅读页的正文与标题(索引、月历、任务行、档号仍用黑体;先不引入中文 web font,靠系统栈回落;正文最小 17px)。
- 默认素纸,系统偏好为暗色时切夜纸,两套都是完整主题;度量一律在素纸上校准。
- 第三、第四模块是任务与材料/来源,外壳按四个模块定版,脊宽从 56px 调到 64px。
与实施计划的关系
- 这份设计回答「该长成什么样」,
网页版前端首轮体检与优化实施计划回答「现在哪里坏了、怎么修」。 - 设计里的色彩与字体 token 正好覆盖实施计划 P0(亮色主题下不可见文字)与 P2(品牌面): P0 的修法就是把这五个角色落成变量,P2 的方向由这里的配色与「档号」语言给出。
- 后续的落地文档(2026-09-22 接上):
网页版多模块改版 spec(.scratch/web-multimodule-shell/spec.md) 把这份设计翻成了 39 条用户故事与 11 张 ticket;外壳契约与路由记在 ADR 0009 / 0010, 日程的数据链路(SQLite 权威源 → Neon 只读投影 → web)记在 ADR 0011。设计里有歧义的地方以 spec 为准。 - 已经拍板但本文没写全的两处:日程的月历采用原型里的 C 变体(日历在页里、索引列是「有记录的天」);
淡墨压到
#556470。理由都在 spec 与上文的订正里。 - 新发现的一条待办(不属于原体检范围):材料模块那 5.53 MB 已进构建产物的正文,以及它和解读类 笔记在同一个列表里分不出来的问题,需要在做这个模块时一并处理。
实现后记(2026-09-22)
这份设计已经落地:外壳四段、笔记模块、日程模块都装订完了(提交 c0e3e30 … ccfd8b9),
设计里的四个模块有三个从「形态」变成了「能打开的页面」(任务与材料仍是诚实的占位,等各自的 spec)。
落地后与本文有出入的地方,以这里为准:
- 日程模块不再是「未来做」:月历在页、索引列是「有记录的天」一天一行(日期、类型记号、 当天的第一条、条数),点某天在月历下方出流水,时间在页边、文本在页。数据走 Neon 投影(ADR-0011), 不做构建期产物。本文那段 ASCII 里的「3 日 7 条」那类索引行,落地后多了类型记号。
- 今天与当前项是两个记号:今天 = 月历格子的日期数字用朱砂;当前项 = 索引行左侧朱砂墨点 + 格子底色。同时出现时互不顶掉(素纸下朱砂落在选中底色上是 4.45:1,仍在「朱砂是非文字记号 ≥3:1」 这条之内,记在 ticket 09 的 Answer 里)。
- 窄屏只降级三处,月历格子照旧:本文只写了脊 / 索引 / 页边三处降级,实现时确认月历格子里 仍然显示事件(窄屏竖排、字号收到 10/9px),因为「格子里就是当天的事件」是这个模块的论点。
- 本文没写、实现时必须补的一条:笔记正文里的图片。
notes/documents/assets/下的图原来 既不在产物里也不在页面上(全是 404),现在构建期拷进/note-assets/(5 篇笔记、33 张、5.1 MiB), 渲染侧只放行同源这些路径与https:。 - 数据链路已通到真机:2026-09-22 跑完真机迁移与首次投影(
cd67238),285 条事件落地 Neon, 2026-06 是 78 条 / 19 天、06-04 是 16 条,幂等重跑零变更;本地日志里一条历史 bug 写坏的 end 时间戳被丢弃(事件照常落地)。月历与当日流水在本地夹具里读的就是这份真数据。 - 还没定的一件:「搜索是不是每个模块都能用」——本期搜索只搜笔记,搜索框只在笔记索引头。
日程模块的第二轮:网页与飞书日历同源(2026-09-22)
触发是一条具体的错位:网页日程里 09-19 之后什么都看不到,飞书日历上却有一片。查下来不是投影坏了,
是两个目的地各写了一套「什么算一条日程」的规则——网页那份还多一条 sensitivity 过滤,且根本没有笔记线。
- 取合规则只有一份了:本地视图
calendar_entries(scripts/calendar_entries.py)把事件线与笔记线 统一成同一形状,kind区分daily/note;飞书日历与网页都是它的兄弟投影,见工作区 ADR0010。 条目范围 = 现在合规 ∪ 已经发出去过(台账target_id非空)。后半句是这次的要点: 日历上的内容是「写出去的历史」,不是「现在推导出来的集合」——本地 2026-06 那 78 条事件calendar_enabled现在全是 0,只按「现在合规」取,网页永远对不上。 - 网页现在两条线都显示:月历格子上笔记条目有自己的记号(
mk-note,与投资/任务/会议/反思并列), 日流里笔记条目带来源标记「笔记」、点它跳到笔记模块(?n=<Note Path>)。图文形态见.scratch/web-calendar-parity/assets/07/。 - 历史条目回来了:老账
config/feishu_calendar_sync.json里的event_id一次性回填进台账 (224 条命中),带target_id的行从 39 涨到 263,2026-06 那一批因此重新出现在网页上。 - 同源是可以证明的:
python3 scripts/calendar_parity_report.py只读地拉一次日历对差。 2026-09-22 实测:本地 372 / 日历 386 / 对上 371(台账 306 + 内容 64 + 描述键 1), 「只在日历」15 条全部有解释(11 条白名单 + 4 条重复),「只在本地」1 条是本地重复行。 - 两笔已知账:65 条老账的
event_id已经过期(记录还在日历上,只是事件被重建、id 换了), 报告单列成id_stale;19 条「本地 00:00 / 日历 12:00」是旧脚本对「没有时刻」的条目的兜底, 这一轮不统一(改了就是改历史)。两者都不影响网页上看到的内容。 - 一条口径决定:笔记线只投
active,不做sensitivity过滤——109 篇里 106 篇是private, 而笔记正文本来就整篇进构建产物(OAuth 之后),网页与博客两侧口径一致;日线仍然保留active + 非 private。 - 不撤回:条目一旦发出去就留着(用户 2026-09-22 定)。这一轮不实现任何删除日历的规则,
除了「
withdrawn的笔记不算在集合里」。