数友社区 · 帖子

ShuyoNote 设计哲学

这是 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
📥 存为 ShuyoNote 笔记

评论 0

还没有回复,来说两句吧。

板块:数据主权

← 返回首页