缘起
番茄钟、喝水提醒、拉伸提示,这些年装过不少,最后都关在同一个地方:通知权限。
不是它们提醒得不准。是它们在该安静的时候响了——改一个 bug 改到一半,全屏弹窗盖住代码;跟人共享屏幕讲方案,桌角突然冒出一只宠物、或者屏幕正中炸开一个"该喝水了"。健康提醒这个品类的失败,多数不是漏提醒,而是它自己变成了新的干扰源。
另一件事是我不太愿意为"多喝水"注册账号。想把这套事凑齐——喝水、起身、走动、看看远处——得装三个应用,每个都要登录,打卡数据全在别人的云上。为了记得喝水,把每天坐在电脑前的生活节奏上传出去,这笔交换不划算。
所以想做的东西很具体:一只住在桌角的宠物,把这几件事一起承担下来;该安静的时候它真的安静,数据一步都不出这台机器。
背景
把桌面健康提醒这个赛道在 2026 年横向看了一遍(Stretchly、LookAway、Time Out、Shui、Viraam 这一类),有个结论比功能清单更重要:这个品类的下限已经变了。"定时弹窗 + 跳过/推迟"不再够用,自动暂停、休息引导、常驻入口、统计,至少得占两项。
对照下来是三个缺口:
- 免打扰基本只做到"静默时段":能设"晚上十点后别吵",但不知道你现在在开会、在演示、还是根本不在座位上。而用户口碑里最被认可的恰恰是这一块。
- 响过之后没有下一步:提醒弹出去了,但"起身"这件事本身没人管。用户点了"知道了",然后继续坐着。
- 要么是单点计时器,要么是移动端记录本:桌面端没有谁把"喝水 / 起身 / 走动 / 护眼"这类多域编排做在一起,更没有人给它一个能长期看的情感载体。
这三条决定了产品要有哪些能力,也决定了它不该长成什么样——不做摄像头监测、不做强制锁屏的"严格模式"、不做云端统计。补基础能力的时候很容易补成"又一个 Stretchly",那就没意义了。
v1.1 补的是第四个缺口,它不在上面三条里,是做完前面三轮才显出来的:固定排程接不住"你现在到底坐了多久"。每小时提醒一次,可能你刚站起来接完水回来它又响了;也可能你已经连着坐了九十分钟,而下一条提醒还在四十分钟之外。时间表是死的,人是活的。
方案
按一个人在工位上的一天来组织:
装完就能开始。 首次打开只问三件事:你的工作时段、最想解决的两件事、挑一套现成计划。五个内置模板(久坐办公、会议密集日、备考冲刺、居家办公、护眼优先)都是确定性内容——不调用模型、不需要密钥、不联网。选中即复制成一份属于你的计划,之后在计划页逐条改间隔、时段与每日目标。想要更个性化的,再走"用一段话描述处境 → 生成整套计划"那条路。
提醒由一个角色承担,而不是一串弹窗。 桌角一只小宠物常驻,头顶一株芽按今天的完成率分档生长,当天生长、次晨归零。有待处理时它进等待姿态并冒出气泡;提醒清空后庆祝一下;休息时它陪着你倒计时。一眼扫过去就知道"今天做得怎么样",不需要打开主窗口。
它会数你坐了多久,而不是只看表。 一个连续的用机时钟,切应用不清零——在三个编辑器之间来回切不算休息,得真的离开十五分钟以上才归零,锁屏也算休息。坐满一小时、而十五分钟内又没有正式站立提醒,它才开口一次:每段久坐最多两次、每天最多五次、两次之间至少隔半小时。真去休息过的,接下来十分钟内到点的站立提醒会静默跳过,不算错过也不进待处理;锁屏超过五分钟、且这次已经坐了半小时以上,解锁时自动算你完成一次。提醒还是你的计划,久坐感知只做加减法——不另起一套计时器,也不推翻你自己定的间隔。
它说话不好听,但每句都留台阶。 这一版把宠物人格从"温和陪伴"改成了"毒舌健康搭子",默认就是损友式的那一档:坐满时会说"椅子快以为你长在上面",周统计变差时会说"椅子很高兴,你的腿未必"。硬边界写在内容里:只吐槽久坐行为与统计,不评价外貌、智力、家庭与健康状况,不做健康判断,每句话后面必须跟一个能执行的台阶(起来走两步、再坐十分钟、今天先这样)。嫌难听随时能换——设置里三档(毒舌 / 活泼 / 温和),任何气泡里点一下「别这样说」就切成温和并记住,还有「今天先这样」让它当天闭嘴。
该安静的时候真的安静。 六路信号:连续几分钟没碰键盘鼠标、锁屏或休眠、会议应用在前台、全屏应用、系统专注、日历正在忙。每一路都能单独关。暂停期间到点的周期性提醒顺延不补发——恢复后按原间隔重新计时,而不是把错过的十来条一次性糊到脸上。
共享屏幕时不被看见。 演示模式一键进入:暂停提醒、隐藏宠物与遮罩,并在系统支持时把它们排除出屏幕共享与录屏。会议或全屏持续几秒后,宠物会自己提议进入;靠建议进入的,在会议结束后自动退出,手动进入的等你手动退出。退出后不补弹演示前的待处理。
响过之后要有下一步。 站立、走动、护眼三类提醒到点前一分钟先预警,到点进入倒计时覆盖层,配一条与类型匹配的动作:护眼是远眺加呼吸,站立是肩颈拉伸,走动是起身走动。倒计时结束自动记完成;提前结束可以选"算完成"或"跳过"。喝水、如厕这类保持简单打卡,不硬塞一套流程。
错过的要看得见,但不补弹。 一次性提醒("明天下午三点交材料")独立于场景,关掉某个习惯场景也不会让它消失;错过了记成"已错过",留在首页,不再响。
进度要有据可查。 每条提醒都能看到今日进度与连续达标天数;首页一屏读完待处理、已错过、今日进度与下一次触发的倒计时;久坐页能看到今天一共坐了多久、最长一口气坐了多久、主动起来几次,最近七天可以往回翻。
技术架构
| 模块 | 技术选型 |
|---|---|
| 桌面运行时 | Wails v3(beta.23),WebView2 / WKWebView |
| 后端 | Go 1.25 + GORM + SQLite(纯 Go 驱动,交叉编译不带 CGO) |
| 前端 | Vue3 + Vite + Element Plus + Pinia,hash 路由 |
| 提醒引擎 | 纯时间计算 + 调度循环 + 一次性提醒全局扫描三层分离 |
| 久坐感知 | 全局连续时钟 + 分级空闲判定(5 / 15 分钟两档)+ 与提醒计划的加减法 |
| 管道契约 | 错误码 / 分页 / 事件三类常量在 Go 单一真相,前端只做镜像 |
| 工程化 | Makefile 统一入口 + 架构护栏 + 依赖登记制 + OpenSpec 治理 |
| 交付 | macOS 美化 dmg / Windows NSIS 安装器,CI 覆盖 macOS 与 Windows |
几个值得说的做法:
- 单进程交付:根包用
//go:embed把前端产物嵌进二进制,用户拿到的就是一个可执行体;frontend/dist不入库,make test会先检查它存不存在并给出修复命令,而不是让你对着pattern all:frontend/dist: no matching files found猜。 - 分层由护栏强制:绑定方法层(
app.go)不许 import gorm 或 database,必须经 service;model 层是叶子。越界会在go test阶段变红,不靠 code review 记性。 - 一次性提醒复用了
reminders表:加一列once_at(0 表示周期项)、scenario_id置空、按绝对时刻全局扫描。没有另建一张表加一套状态机——完成/跳过/推迟/去重的逻辑写两遍,迟早漂移。 - 久坐时钟是全局的,不是按应用的:切窗口不重置计时,只有满十五分钟的空闲才归零,锁屏单独作为休息信号。否则"我在三个窗口之间来回切"会被读成"我休息了三次",而这个错误的表现是它从此再也不提醒你——不报错、不崩溃,只是默默失效。
- 同一句话只有一处出处:休息预警在 60 秒里每一秒都会被读一次,剩 8 秒还说"1 分钟后"就是假话,所以措辞按剩余时间分三档(1 分钟 / 半分钟 / 马上)。分档只改措辞、不改投递时刻,且整仓只有一张表和一处函数,宠物气泡与菜单栏共用同一份快照,越界副本由护栏拦住。
- 六路免打扰信号只读本机信息:空闲时长、锁屏状态、前台应用身份、全屏几何、系统专注与日历忙闲的布尔值。不读屏幕内容、不读窗口标题、不读日历事件详情、不碰摄像头麦克风。
- 窗口捕获排除用系统能力兜底而不是更聪明的检测:macOS 走
NSWindow.sharingType = .none,Windows 走SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE);Linux 是 no-op,界面如实降级,不假装支持。 - 宠物气泡出现时临时扩窗:底边不动、向上与两侧扩,以脚底所在屏幕的工作区夹取;退出时持久化的是基础几何而不是扩出来的矩形——否则下次启动宠物会凭空下移一段。
- 开发态有一条给 Agent 用的通道:
make dev-mcp会在本机回环上开一个 MCP 服务,能调绑定方法、读 DOM、等事件。它只能在显式开启时存在,默认构建里一行代码都没有,由源码层、机制层、产物层三道检查共同保证。
成果
- 能交付,不是能跑 demo:macOS 能打出带拖拽引导的美化 dmg,Windows 能打出免管理员的 NSIS 安装器;目标机器不需要 Go、Node 或数据库。两个版本都打了 tag(v1.0.0 / v1.1.0)。
- 产出是可数的:从空目录到可打包安装,21 次提交、65 张实现票、224 个源文件共 45,036 行(Go 33,286 + 前端 11,750);394 个测试函数、12 份架构决策记录、7 份能力规格。
- 自用中:现在每天由它管喝水、起身与久坐。它唯一让我愿意多点一下"完成"的地方是那株芽——进度从"一个数字"变成了"一个会长大的东西"。而久坐感知上线之后,最直接的变化是我开始在意"最长一口气坐了多久"这个数字。
- 没对外发布:目前只有我自己在用,没有用户数可以报。这一栏我不打算填一个好看的假数字。
体验
源码在 github.com/Benny66/morrow,README 里有构建步骤与截图。它是本地优先的桌面应用,没有可点的网页版 demo——想用起来,要么装一个安装包,要么照 README 从源码构建一次。
想验两件事的话:
- 把喝水提醒的间隔设成两分钟,然后锁屏。等锁屏结束回来,你不会看到一串补发的弹窗——它们被顺延了,不补发。
- 什么都不做,连坐一小时。如果这一小时里没有正式的站立提醒,桌角那只小水滴会开口损你一句,然后等你选"起来走走"。
