这是 ShuyoNote 项目文档里的《设计哲学》全文。它回答的是「这个软件为什么是这样」—— 凡是与它冲突的功能,都该先讨论哲学,而不是先改代码。
本文从需求(基础开发方案)、产品定位、竞品对比、各功能方案、路线图与设计系统中提炼出一份完整的设计哲学:它回答「我们为谁而做、数据如何被思考、产品如何取舍」。它是可执行的指南,指导后续每一个功能走向与「做 / 不做」的判断。 具体方案见
plans/;现状见roadmap.md;像素级设计见design/design-system.md。
1. 一句话哲学
页面是本源,属性是语义,数据库是透镜,文件夹是容器。
- 页面永远是「能被当作普通笔记编辑的一等公民」;
- 属性是叠加在页面上的可类型化语义,但保持松散;
- 数据库是同一份数据的投影(视图 + 成员规则),不是新的 schema;
- 文件夹同时是页面层级容器与文件库(网盘)。
一句话定位:「本地优先的 Notion 替代,叠加思源的块级引用与 Obsidian 的关系图。」
2. 出发点:我们为谁、为什么存在
2.1 要解决的问题(需求哲学)
我们构建一个「本地优先、类 Notion 块编辑 + 关系型知识工作台」的桌面 / 浏览器应用(桌面为主,同一前端经平台 driver 抽象也能在纯浏览器运行),针对三类用户:
- 重视数据主权与隐私、不愿把笔记交给云端的个人 / 极客;
- 需要 Notion 式块编辑与数据库,但希望离线可用、无订阅、数据自持;
- 接受桌面单机 + 自建同步、暂不需要多人实时协作的用户。
2.2 价值主张(一句话对用户)
如果你想要 Notion 的块编辑与数据库、思源的块级引用、Obsidian 的关系图,但不愿把数据交给云端、且希望离线免费开源——ShuyoNote 就是为此而做。
2.3 差异化组合(借鉴 + 自研)
我们站在两条竞品轴线的交汇处,借鉴成熟范式,但取舍方向明确:
- 块编辑器、属性 + 数据库视图:Notion / FlowUs / Wolai | 保留,但转本地
- 块引用 / 嵌入 / 块级反链:思源笔记 | 保留(块是一等公民)
- 关系图(局部 / 过滤 / 着色):Obsidian | 保留
- 属性面板(Properties 式):Obsidian / 思源 | 保留,合并成一套
- 自研:Tauri + SQLite + FTS5 | 轻量、离线、无 Go 后端 / 无 Docker
核心差异化:本地优先 + 内容寻址去重 + 自建同步 +(可)端到端加密 + 开源(AGPL-3.0)。这不是「云端竞品的本地简化」,而是「私有、去重、无限速、可加密」的另一种价值。
3. 根本心智模型
- 页面 = 行:一条记录 | 标题 / 正文是本体;属性是「元」
- 属性 = 列:字段 | 带类型(text/number/date/checkbox/select/multi/tag/ref/formula/rollup),值按类型解释
- 数据库 = 透镜:查询 / 投影 | 视图(表格/画廊/看板/列表/日历/时间轴/目录)+ 成员规则(手动 ∪ 查询)
- 文件夹 = 容器:项目空间 | 组织页面 + 承载文件(本地 / 去重 / 可加密的网盘)
- 标签 / 引用:语义连接 | 催生关系图与反链;数据一次,处处呈现
- 空间(workspace):顶层隔离容器 | 比文件夹更高一层:页面树 / 标签 / 数据库 / 图谱 / 回收站 / 同步 / 加密各自隔离
关键洞察:视图是呈现,不是数据。数据只存在于「页面 + 属性(+ 块)」。改一处,所有视图 / 关系图 / 反链同步。这是「透镜」模型最值钱的地方——数据一次,处处呈现。
4. 数据层哲学:分层与「数据 vs 派生数据」
4.1 一页一文档(文档模型)
- 每页是一个 Lexical 文档,块 = 文档根级节点;页面嵌套用页面树(
parent_id),而非内联块。 - 不是「每块一个编辑器」——那会编辑器爆炸、光标与跨块粘贴出问题。
4.2 属性 / 数据库分层
attr_defs(共享属性定义 / 共享 schema)
→ page_props(页面值,按类型解释的 TEXT)
→ database_columns(库列)
→ db_views(保存的视图配置)
→ 前端渲染
- 属性名全局唯一、跨页共享同一属性定义——保证「状态」列在所有库里语义一致(同一套选项 / 颜色)。
- 标签是内置属性:
tags/page_tags仍作物理存储(不破坏既有看板 / 属性面板 / 关系图),tag类型的属性值只是它的读写视图。
4.3 「数据」与「派生数据」
这是最容易犯错、也最关键的一条:
- 真数据:
pages、attachments(本体)、page_props、workspaces、changes(outbox)。 - 派生数据:
page_fts、blocks、backlinks、db_views、db_rule——都是重建即可的索引 / 缓存。 - 派生数据不参与同步 outbox:
blocks/page_fts/backlinks在保存时重建,随页面快照走,不单独同步。 - 结论:能重建的绝不当作事实源,避免「一对多的同步一致性」灾难。
4.4 属性值按类型解释的 TEXT
- 值统一存 TEXT,牺牲少量查询性能,换「无 schema 迁移成本」。排序 / 过滤按 type 解释;复杂数值区间在 Rust 侧解析。慢即是快。
5. 核心设计信条
5.1 数据库是透镜,不是 schema
- 笔记应用里,「数据库」不把页面降级为行、不让页面归顺它。
- 正确心态:数据库叠加在页面之上,把一行「展开」成页面、把页面「折叠」成一行,应无缝。
- 成员 = 手动集合 ∪ 查询规则(
状态=进行中 且 标签=工作)。取「手动 + 规则」混合,避免自动收页冲掉手动内容。 - 视图 = 声明式配置:每视图一套
{列, 排序, 过滤, 分组},一个库可多个视图、可命名切换。
5.2 灵活页面 + 共享 schema
- 页面属性松散(
page_props),保持笔记自由;库列引用共享属性定义,保证同名属性跨页 / 跨库语义一致。 - 页面永远是页面,可随时当普通笔记编辑;结构化只是叠加视角。
5.3 块是一等公民
- 每个顶层块有稳定
blockId(随文档持久化),可被((blockId))引用、{{blockId}}嵌入、反链精确到块、关系图呈现块级图层。 - 引用 / 嵌入是只读镜像(目标更新经缓存刷新,双向不互相污染)。
- 目标删除 → 显示「失效引用」,不崩、不悬空。
5.4 文件夹是容器,不只是层级
- 文件夹既是页面树层级,也是文件库(网盘)——一个项目文件夹 = 文档 + 素材(PDF / 图片 / 表格 / 压缩包)在一个地方。
- 文件是一等公民:文件本身存在于文件夹(可搜索 / 预览 / 下载 / 移动),页面可引用关联(页面附件模型并存)。
- 差异化 = 本地优先 + 内容寻址去重 + 自建同步 +(可)端到端加密。你的文件在你的设备本地存储;去重省空间;无云容量 / 限速 / 配额;加密后自建服务器不可读——是「私有、去重、无限速、可加密的网盘」,而非公网网盘的替代。
5.5 多空间是隔离容器,不是文件夹
- 空间 = 顶层隔离容器:页面树、标签、数据库、块引用 / 嵌入 / 反链图、回收站、属性、附件引用、同步版本线、E2EE 密钥、空间级设置各自独立。
- 物理隔离(M15 已实现):每空间一个独立 SQLite 库(
meta.db管理空间清单,spaces/<ws_id>/每空间库)+ 全空间搜索跨库合并 + 跨空间复制 + 单空间导出/导入。附件字节保持全局内容寻址(跨空间去重),以「空间级附件子集导出」实现单空间可搬移——这是「去重」与「可独立搬移」的取舍。 - 红线:块引用 / 反链图必须留在空间内,否则删除一个空间会产生悬空引用;跨空间块引用推迟。
- 不要把空间做成文件夹:两者职责不同,避免冗余。
5.6 模板是结构预设,不是内容本体
- 模板定义「从头建一个页面 / 数据库长什么样」,而非内容本身。
- 建页即填充:从模板建页时把结构深拷贝进新页内容 / 属性 / 标签;建成后模板与新页解耦(改模板不影响已建页)。
- 来源分层:内置(全局)→ 我的模板(每空间一份)→ 导入共享。
5.7 本地优先与隐私优先
- 数据与附件在本机;附件按内容哈希存储、跨页 / 夹去重;可选自建 shuyonote-sync-server(outbox + LWW + 内容寻址增量)。
- E2EE(M2):默认关,开启后服务端不可读;密钥 / 盐仅存本机;每空间独立密钥(隔离才成立,而非「一把钥匙开所有门」)。
5.8 扩展性:安全分层
- L0 命令 → L1 磁盘命令插件(受限运行时 + 白名单 API)→ L2 UI 插件(沙盒 WebView + postMessage 桥)→ L3 市场。
- 扩展点最小稳定面是「命令注册表 + 启停持久化」。
- 安全是门槛:插件代码会执行,最大风险是读写文件 / 任意命令 / 网络 / 改应用状态。必须用受限运行时 + 白名单 API,别让插件直接进 renderer——这是「本地优先」应用的红线。
- UI / 市场在沙盒与信任体系成熟后再上,避免过早铺开复杂度。
5.9 灵活务实,勿过度设计
- 单人 / 小团队开发,避免过度工程(YAGNI)。
- 优先级按「价值 ÷ 工程量」排序;先做「真的能用」(MVP),再谈深度 / 市场竞争 / 平台级。
- 冲突粒度、加密作用域、主题作用域等,都按需升级,不一次性到位。
6. 稳定性与正确性信条
6.1 不丢数据
- 自动保存(防抖 + 失焦 / 退出 flush)+ WAL + 原子写 + fsync;"无保存按钮"。
- 版本历史、回收站、整库备份兜底。
6.2 同步:outbox + LWW + 墓碑
- 写入 = 本地事务内同时写
pages+ 一条changes(outbox),保证「写入 = 待同步」。 - 冲突模型:页面级 LWW(客户端
updated_at,同刻以device_id字典序决胜);删除永远是墓碑(软删),物理 GC 待所有设备确认 + 超保留期。 - 冲突粒度按需演进:页级(当前,最简)→ 字段级(把
title与content_json拆开比较)→ 块级 → CRDT(@lexical/yjs多人协作)。payload设计为可降级到字段级,避免未来重写协议。 - 时钟:用客户端时间戳比较(而非服务端接收时间),避免时钟偏斜;同刻以
device_id决胜。
6.3 派生数据与同步解耦
- FTS / blocks / backlinks / db_views 都是「重建即可」的派生物,不参与 outbox,随页面快照走(见 §4.3)。
7. 体验与视觉信条(克制与反 AI 味)
7.1 内容优先
- 界面退到背景,让笔记内容成为视觉重心;装饰只服务于层级与导航。
7.2 本地优先的「安心感」
- 保存状态、同步状态、备份状态要可感知、可信任(清晰的成功 / 失败反馈)。本地优先不是「看不见进度」,而是「让你看见一切都在本机、稳定、可回滚」。
7.3 一致的节奏
- 一套间距、圆角、阴影、动效 token 贯穿所有界面,避免「每个组件各写一套」。
7.4 克制的色彩
- 中性灰负责结构;品牌蓝只用于「焦点 / 行动」;多彩色只用于「分类 / 状态」。
- 标签 / 看板列用「实色圆点 + 文字」,避免大面积高饱和色块;选中态才铺浅底。
7.5 可及性内建(WCAG AA)
- 所有交互支持键盘,焦点可见(永不
outline:none),对比度达 AA;状态不只靠颜色;动效尊重prefers-reduced-motion。
7.6 反 AI 味(灵魂法则)
- 别用 emoji 当功能图标:用 1.6–1.8px 描边 monoline SVG(
design-system.md有完整 token)。 - ~80% 成熟模式 + ~20% 独特选择:一个大胆视觉、有性格微文案、一个记忆点微交互、一个只有用过的人才会放的细节。拒绝「每个组件 / 每个界面都均匀无特色」的 AI 生成感。
8. 安全信条
- IPC 最小暴露面:前端只调用语义化命令(
save_page、search、list_pages),不接触 SQL;命令最小集 + 参数校验;不暴露任意 SQL / 任意文件路径。 - 插件:受限运行时 + 白名单 API(红线,见 §5.8)。
- 钥匙:E2EE 密钥 / 盐仅存本机;多空间独立密钥。
- 删除是破坏性大操作:软删(墓碑)+ 导出提醒 + 二次确认;不要把「删除空间」「清空」做成普通删除。
9. 工程 / 架构取向(ADR 摘要)
- Tauri 2 而非 Electron:体积 / 内存大幅降低、Rust 后端安全高性能 | 需维护 Rust 层
- Lexical 而非 TipTap / ProseMirror:节点模型契合块编辑、官方 React 绑定与 Yjs 协作 | 较新、生态相对小
- 一页一文档,而非每块一编辑器:性能与光标正确性;块间独立引用用自定义节点补足 | 需自制块 / 引用节点
- Rust command 封装 SQLite,不用
tauri-plugin-sql:收口 SQL / 事务 / 迁移;前端零 SQL,类型安全 | 后端职责更多 - FTS5 trigram 而非 Meilisearch:零额外进程、单文件备份;中文任意子串检索 | 索引略大(量级内可忽略)
- 附件内容寻址文件系统,而非 BLOB:大文件不撑爆库、天然去重、备份友好 | 需维护文件名 / 路径映射
- 同步用 outbox + 页级 LWW,而非 CRDT:单用户多设备 LWW 足够、行为可预测、实现简单 | 同页并发编辑会覆盖(按需升级字段级 / CRDT)
- 平台 driver 抽象(
src/lib/platform/),桌面 + 浏览器:同一前端跑 Tauri 桌面与浏览器 Web,换平台 = 换 driver | Web 侧无真实文件系统 / 系统对话框,部分能力降级
里程碑排序原则:按「价值 ÷ 工程量」收紧;平台级(协作 / AI / 移动端 / 插件市场)延后,不虚构进度。
10. 边界(它不是什么)
- 不是关系型数据库:不强 schema、无复杂外键 / 事务约束;页面属性灵活。
- 不是公网网盘:无分享 / 配额 / 转存 / 云盘会员生态;聚焦私有、本地、去重、可加密。
- 不是协作平台:共享 / 权限 / 实时多人是另一个能力,早期不混入。
- 不是云端 AI 工作流依赖:AI 是可选增强,且倾向本地接入(避免数据外泄),不成为卖点依赖。
11. 可记忆的口诀
- 数据一次,处处呈现。
- 页面是本源,属性是语义,数据库是透镜,文件夹是容器。
- 数据库是透镜,不是 schema。
- 文件夹是容器,不只是层级。
- 空间是隔离容器,不是文件夹。
- 你的文件,你的设备,你的私密。
- 视图是配置,不是页面。
- 能重建的,绝不是事实源。
- 模板是种子,建成即解耦。
- 别用 emoji 当图标;80% 成熟 + 20% 灵魂。
12. 从哲学到做法(对照索引)
- 页面是本源、属性是语义、库是透镜、夹是容器:design-philosophy §3、properties-database-plan、database-lens-plan
- 本地优先 + 数据主权 + 离线:positioning、local-first-note-app-plan §7
- 差异化的「私有网盘」:folder-netdisk-plan、design-philosophy §5.4
- 块是一等公民(引用 / 嵌入 / 反链 / 图谱):block-reference-plan
- 多空间隔离 + 红色边界(物理化):per-workspace-storage-plan、multi-workspace-plan §3
- 模板 = 结构预设 + 深拷贝解耦:template-plan §3
- 扩展分层 + 插件安全红线:plugin-plan §3、§4
- 不丢数据 + LWW + 冲突粒度演进:local-first-note-app-plan §7.3–7.4
- 体验克制 + 反 AI 味 + 可及性:design-system §1
- 架构取向(ADR):local-first-note-app-plan §8
Comments 0
No replies yet — say something.