LegalAVA · 副产品
Vibe side project · Android + Mac
DailyNotesvibe 副产品
给 Obsidian 日记用的手机端:上半屏月历,下半屏 Markdown,断网照常看、照常写;一旦连上,改动几秒内出现在电脑上的 Obsidian 里。它和上面的法律 agent 产品线没有关系——是同一套「agent 驱动开发」的工作方式,顺手做出来的东西。
它长什么样
三张设计图对应三种状态:阅读、编辑、冲突。手机上只有这一屏,没有第二级页面。
20:14▲ ▮▮ ▮
8月26日 周三已同步
上午 客户开会-项目合同谈判
下午 一审开庭-施工合同纠纷
工时登记汇总
| 项目 | 工作内容 | 时长 |
|---|---|---|
| 客户 A | 合同谈判会议 | 2.5h |
| 案件 B | 一审开庭 | 3.0h |
整理开庭笔录
起草补充代理意见
Pasted image 20260826….png
双击进入编辑 · 上滑收起日历 · 左右滑切日
阅读(默认)完整渲染:表格、可点选的任务框、内嵌图片。今天是空心环,选中是实心,有日记的日子有一个点。
20:15▲ ▮▮ ▮
8月26日 周三待上传 完成
上午 客户开会-项目合同谈判
下午 一审开庭-施工合同纠纷
## 工时登记汇总
- [x] 整理开庭笔录
- [ ] 起草补充代理意见 1.5h
**合计:5.5h** ✅ 已提交
H2•☐**⊞⏱—⇥
编辑月历折成一行让位给键盘;标题、加粗、任务框在输入时即时套样式,语法符号仍在原处。停止输入 0.6 秒自动保存。
08:41▲ ▮▮ ▮
8月26日 周三冲突
上午 客户开会-项目合同谈判
下午 一审开庭-施工合同纠纷 3.0h
工时登记汇总
| 项目 | 工作内容 | 时长 |
|---|---|---|
| 客户 A | 合同谈判会议 | 2.5h |
整理开庭笔录
起草补充代理意见
离线 / 待上传 / 同步中 / 冲突 —— 都在那颗芯片上
冲突两端同时改同一行才会到这一步;不同段落的并发修改会被行级三方合并自动吃掉。永不静默覆盖。
设计理念
先在真实 vault 上量了一遍(229 篇日记、中位数 498 字节、表格与任务清单高频、附件与笔记同文件夹),设计是从这些数字里长出来的,不是从功能清单里。
只碰一种文件读写范围限定为
daily/YYYYMMDD/YYYY-MM-DD.md;同一文件夹里的 docx、pdf、子目录一概不列不写。图片只读。范围越小,越敢做原子写和乐观锁。本地是唯一真相源界面永远只读手机上的 SQLite,编辑先落本地;同步引擎在后台对账。断网不是异常状态,是默认状态。
服务器无状态Mac 上的服务不建库、不存缓存,真相就是 vault 里的文件本身;ETag 是文件内容的哈希。停掉服务、删掉代码,vault 毫发无损。
用文件系统的语义说话
PUT 带 If-Match,服务端 412 时把当前版本一并交回;写文件先临时后 rename,Obsidian 永远读不到半截。冲突不静默base / 本地 / 服务端三方行级合并;合不上就把两版都留下,让人选。日记是单人、追加为主的文本,绝大多数并发都能自动合并。
按 Obsidian 的规矩渲染
![[图片]] 按「同文件夹 → assets → 全库同名」解析;单回车即换行;[[wikilink]] 只显示不跳转;^块ID 隐藏但绝不改动。阅读为默认,双击才编辑打开就是排好版的一页,没有按钮;双击哪一段,光标就落在哪一段末尾。单击不做任何事,避免误触。
手势即导航上滑收起月历、左右滑切前后一天、顶部下拉刷新、点任务框直接改源码。单屏应用,导航都交给手指。
安全边界要实在一枚固定令牌,所有请求都要带;令牌不进仓库、不进配置文件,只在 Mac 上一个 600 权限的文件里。有了它,服务放在局域网还是公网,只是一个路由决定。
怎么同步
不用 WebDAV,不走 NAS,不引入任何新基础设施:一个 400 行的 Python 服务跑在 Mac 上,直接读写 vault。
手机 · Compose UI只读写本地库
⇄Flow
Room / SQLite唯一真相源content · base · etag · dirty
⇄HTTP · mDNS
SSE
SSE
daily-serverMac 上的 launchd 服务index / note / attach / events
⇄直接读写
vault 文件Obsidian 热重载Synology Drive 下游不动
| 服务端变了? | 本地改过? | 动作 |
|---|---|---|
| 否 | 否 | 无事 |
| 是 | 否 | 拉取,覆盖本地与合并基线 |
| 否 | 是 | PUT 带 If-Match;成功即更新基线 |
| 是 | 是 | 三方合并 → 干净就推送;否则进入冲突,等人 |
| 服务端没有 | 是 | 新建(服务端自动建文件夹) |
PUT 返回 412 | 对账后又被电脑改了:拿 412 里带回的最新版本再合并一次 | |
整个决策矩阵就是上面六行。它小到可以被完整测试,也小到出了问题一眼能看出在哪一行。
为什么叫 vibe 副产品
这个 App 从零到能用只花了一天,中间没有写过一行「手工」代码:agent 先实测 vault 的文件结构与 Markdown 用法,写出设计文档;人只做了四个决定——不走 NAS、默认只读双击编辑、图片要显示、仅限局域网;随后服务端、客户端、单元测试、launchd 配置一路落地,之后的十几次迭代全部由真机上的一句话反馈驱动。
- 09:00探测 vault:229 篇日记、附件规则、同步链路;设计方案 v1(走 NAS + WebDAV)
- 19:00四个决定,方案 v2:Mac 直连、自定义 HTTP、图片、只读优先
- 19:50服务端完成,29 项回归通过;发现 launchd 下的 FSEvents 与 macOS 隐私授权两个坑,改轮询、换解释器
- 20:13Android 编译通过,23 个单元测试;第一版 APK
- 20:20真机闪退(一个
setTag的资源 id 约束);加崩溃捕获,之后每个问题都带着堆栈回来 - 次日十三个构建:网络线程、状态栏配色、裁剪、单回车换行、手势、动画、下拉刷新、图片查看器、访问令牌
「vibe」不是不认真——恰恰相反,认真的部分被前移到了设计与约束上:范围写死、真相源唯一、冲突显式、语义跟随 Obsidian。有了这些,实现就可以放心交给 agent 快速试错,出了问题也知道该往哪一层看。这和 LegalAVA 做法律工具时的态度是同一个:先把边界与方法讲清楚,再谈自动化。
技术与状态
手机端Kotlin · Jetpack Compose · Room · Markwon(表格 / 任务清单 / 图片 / 编辑器)· kizitonwose Calendar · WorkManager · OkHttp SSE。minSdk 26,无原生库。
服务端Python 3.11 · aiohttp · zeroconf。六个接口、私网限制、原子写、2 秒轮询推送 SSE、
X-Token。launchd 常驻。测试服务端 29 项 curl 回归(含 412、路径穿越、SSE);客户端 23 个单元测试(三方合并、Obsidian 语法、API 客户端)。
状态个人自用中,每天在真机上跑。不上架、不分发;源码仓库暂为私有。