09 · 分发
怎么把它交到用户手里,以及为此要付出什么。
这一份跟 07 §6 的分工:那边讲流水线怎么搭(签名怎么配、 公证怎么开、站点怎么发),这边讲走哪条路、代价是什么。
1. 三条路
| 路径 | 门槛 | 现状 |
|---|---|---|
| GitHub Release(直接分发) | Developer ID 证书 + 公证(99 美元/年) | ✅ 已跑通:签名 + 公证 + 装订 |
| Mac App Store | 上面那些 + App Sandbox 改造 | 🟡 2026-08 转好 —— 原来那条架构级冲突(插件系统 vs「不得下载并执行代码」)随 ADR-0006 消失,剩下的是工程量,见 §2 |
| 包管理器(Homebrew / winget / AUR) | 只需要一个稳定的 Release | 前提已满足(v0.1.0),但先等真实下载量说话 |
结论:先做直接分发。 它的门槛低一个量级,而且不受沙盒限制 —— 现有的文件访问模型原样能用。拿它收集真实用户反馈(M1 就欠着的那一步), 再谈商店。
2. Mac App Store:不是打磨问题,是架构冲突
MAS 强制 App Sandbox。而沙盒的文件访问模型是「用户在系统对话框里选中什么, 就只给什么,且不跨进程重启」,这跟当前实现有三处正面冲突:
2.1 会话恢复(ADR-0005 的核心卖点之一)
session.ts 存的是裸路径字符串(folder: string | null、tabs: string[]), 下次启动直接拿去 open。沙盒里重启之后这些路径一律无权访问。
正确做法是存 security-scoped bookmark: dialog.showOpenDialog({ securityScopedBookmarks: true }) 拿到 bookmark blob, 随会话一起持久化,启动时 app.startAccessingSecurityScopedResource()。 Electron 有这套 API,但会话持久化那一层要重做。
2.2 图片粘贴写 assets/ —— 这条绕不过去
用户走「打开文件」时,沙盒授予的作用域就是那一个文件,不包含它的父目录。 而 saveAttachment 要在 <文档目录>/assets/ 下 mkdir 再写文件 —— 直接被系统拒。
走「打开文件夹」那条路没问题(作用域是整棵子树),但单文件流程下这个功能在 MAS 版里是废的。要么改成每次弹保存对话框(难用),要么接受「单文件模式不支持 粘贴图片」。这是产品决策,不是能绕过去的工程问题。
2.3 文件监听
fs.watch 盯着已打开的文件,重启后同样丢作用域,得跟着 bookmark 一起恢复。
2.4 顺带澄清:path-guard 不是沙盒
main/path-guard.ts 那层白名单是我们自己的,防的是渲染进程被打穿之后去读 ~/.ssh/id_rsa(01 §5)。它不给我们任何系统权限。上了沙盒是在它旁边再加 一层 bookmark 的存取,不是替代。
好消息:PDF 导出(临时文件 + 隐藏窗口)、崩溃草稿(userData)、 shell.openExternal 都在容器内或走系统服务,沙盒下没问题。
2.5 审核层面的风险
2.5.2「不得下载并执行代码」与 M5 插件系统直接冲突。这条冲突到 2026-08 消解了 —— 不是解决了,是冲突的一方不做了: ADR-0006 决定 v1 不做开放插件 系统,不加载任何第三方代码。MAS 的可行性因此实质变好:剩下的门槛(§2.1–§2.3) 是工程量,不是架构冲突。反过来也要说清楚:「保持 MAS 这条路走得通」是那个决定的理由之一,不是它的 副产品。 哪天要重开插件,这条冲突原样回来。
名字。 Mosu 与 Typora 同类目、名字接近,README 又明说借鉴了 它的交互范式。代码与资源是独立实现,但商标是另一回事(5.2.1 / 5.2.5)。 上架前值得做一次商标检索。
2.6 真要做的话,优先级
不是补功能,是把文件访问改成 bookmark 模型 —— 它会反过来改会话恢复、 文件树、附件三处的设计,越晚做越贵。
2.7 AI 带来的两条新审核项
插件那条走了,AI 又带来两条。都不是冲突,是要提前准备的材料:
- 沙盒下要声明网络权限(
com.apple.security.network.client)。本身没问题 —— 检查更新已经在发请求了。但沙盒版还得确认net.fetch在容器内的行为跟现在一致。 - 隐私披露。 App Store 要求填数据收集清单。BYOK 在这里是优势而不是麻烦: 我们没有服务端、不收集任何数据、文档内容只发往用户自己配置的 Provider。 这件事要在应用内也说清楚(10 §5),不能只写在商店页面上。
顺带一条不是审核而是商业模型的:BYOK 意味着我们没有推理成本,所以 §3 那笔 定价的账不受 AI 影响。真要代付推理,那是另一个产品 —— 见 00 §3 的非目标。
2.5 检查更新:只做「告诉你有新版」,不做自动更新
装了安装包的人没有任何办法知道有新版本 —— 这是 0.1.0 之后最直接的缺口。 0.2.0 补的是手动检查:菜单里一项,查一次 GitHub 的 latest release, 有新版就弹一个框给下载页。
入口按平台放,且只放一处:macOS 在应用菜单里紧跟「关于」(那是 macOS 的 老规矩),Windows / Linux 在「帮助」里。同一个动作出现在两处,用户会以为 它们是两件事。
三条边界
这是这个应用第一个主动发出去的网络请求,而 Mosu 的立身之本是本地优先。 所以:
- 只在用户按下那一项时才发。 没有启动时自动检查,没有后台轮询。 e2e 里有一条专门盯着它:启动之后一个请求都不发。
- 一个匿名 GET,不带任何标识。 没有 token、没有机器 id、没有用户名, 也不上报当前版本 —— 版本比较在本地做,服务端不需要知道你是谁、装的哪版。
- 设置里能整个关掉,而且拦截发生在 main:关掉之后处理器直接返回, 请求根本不会发出去。不是靠渲染进程自觉不调。
用 net.fetch 而不是 Node 的 fetch
前者走 Chromium 的网络栈,于是系统代理设置、企业根证书这些东西自动跟着走。 用 Node 的话,一台配了公司代理的机器上这个功能会莫名其妙地连不上, 而用户完全看不出为什么。
「查不到」和「已是最新」必须分开说
把连不上显示成「已经是最新版本」是在骗用户 —— 他会以为自己装的是最新的, 而实际可能落后好几个版本。失败分四种,各说各的:连不上、被限流、还没有已发布 版本、在设置里关掉了。
版本比较自己写,而且必须单测
规则就那么几条,但用字符串比大小会错得很安静:'0.10.0' < '0.9.0' 在 字典序下成立,于是发了 0.10.0 之后所有 0.9.x 的用户都会被告知「已是最新」, 没有任何迹象。apps/desktop/test/updates.test.ts 头一条就是它。
草稿 Release 不会被看见
发布流程建的是草稿 Release,而 GitHub 的 /releases/latest 按定义排除草稿 与预发布。也就是说「打完包但还没决定发」的那段时间里,用户不会被通知到一个还 不存在的版本 —— 这是想要的行为,不是巧合,改发布流程时别把它弄丢了。
为什么不做自动更新(electron-updater)
不是工作量问题,是一条已经做过的决定挡在前面:Windows 刻意不签名 (07 §6.2)。electron-updater 在 Windows 上靠发布者签名校验下载下来的包, 不签名就只能关掉校验 —— 那等于给自己开一条没有验证的更新通道,比没有自动更新 糟得多。Linux 那边 AppImage 能更新,deb / tar.gz 不能。
所以自动更新仍在 M6,且前置条件是先解决 Windows 签名,不是「以后有空再做」。
3. 定价
还没到定价的时候(见 §4),但把判断记下来,免得将来重新推一遍。
3.1 收费的账
Apple Developer Program 99 美元/年。小企业计划下分成 15%,0.99 美元那档到手 约 0.84 美元。
| 定价 | 回本账号费需要卖 |
|---|---|
| $0.99 | 约 120 份/年 |
| $14.99 | 约 8 份/年 |
也就是说,若目标只是「别倒贴」,1 美元反而是风险更高的那个选择 —— 收费的固定成本(账号、审核、退款、差评、支持邮件)摊不平。
3.2 定价是产品说明书的一部分
对标是 Typora($14.99 一次买断,国区 ¥89)。标 1 美元,用户读到的不是「便宜」, 是「这东西大概只值 1 美元」—— 他会拿它跟贴纸包比,而不是跟 Typora 比。 1 美元是个既不像免费、也不像商品的价格,中间那块没有可站的地方。
3.3 MIT 开源 + 收费的张力
代码在 GitHub 上,MIT。在商店收费完全正当(卖的是签名、打包、开箱即用与后续 维护),但有两个后果:
- 价格天花板被压低 —— 愿意付钱的是「懒得自己编译 + 想要更新」那批人;
- MIT 允许别人原样上架。 真有人拿源码去交一个同名应用,商店未必拦得住。 这个风险跟定价无关,但要知道。
3.4 结论
先免费。真要收费再定 $9.99–$14.99 一次买断,理由不是「我们值这么多」, 而是低于这个数会让人怀疑。想降低门槛就做限时首发折扣,不要做低标价。 只想让人表达支持的话,商店允许「打赏」型 IAP,比把主应用标 1 美元干净得多。
4. 为什么现在还不该收费
版本 0.1.0,M1 就欠着的「找 5 个人各写一篇长文」到今天还没做过。 macOS 的 PDF 空白页追了七轮 CI 才定位(06 §3.3),而它只在一个平台、 只对中文文档发作 —— 这类缺陷在真实用户进来之后只会更多,区别是那时每一个都 带着一星评价和退款请求。
收钱会把「已知的粗糙」变成「用户觉得被骗了」。 README 里那种「不藏着掖着」 的坦白,在免费软件里是加分项,在付费软件里是减分项。
顺序应该是:直接分发(免费)→ 真实用户试用 → 自己愿意拿它当主力用一个月不想 切回去(M3 的验收标准原话)→ 再谈上架与定价。