版本更新
最近的产品改动按时间倒序记录。
2026-06-05 · Mac build 12:补 Window 菜单重开主窗口 —— Apple G4 第三次驳回
背景:mac 1.1.0 build 11 在同一天再被 Apple MAS 驳,理由有三:① 浏览器登录 ②账号删除 ③主窗口关闭后无法重开。
- ①② 跟 iOS 是同源问题,build 11 是旧架构(web 跳转 + WebSheet 删除),新架构(ASWebAuthenticationSession + in-app DELETE)的代码已在 build 12 中
- ③ 是 build 11 也存在但 reviewer 之前没指出的真问题:单 Window scene + applicationShouldTerminateAfterLastWindowClosed=false 让 app 留后台跑(为了 ⌃Space 全局快捷键),但关闭主窗口后没有 menu item 召回
整改
- CalendarApp.swift
AppDelegate加applicationShouldHandleReopen返 true,Dock 图标点击在无可见窗口时重开主窗口 - CalendarApp.swift
.commands加CommandGroup(after: .windowList)含「啵灵日程」按钮(⌘0),@Environment(\.openWindow)取出来召回主窗口;若 NSApp.windows 仍有 "main" identifier 实例则优先makeKeyAndOrderFront,否则openWindow(id: "main")让 SwiftUI 重建 - Info.plist CFBundleVersion 11 → 12
iOS 同步:iOS build 12 → 13(Info.plist),iOS 代码已含 in-app 删除,无 G4 窗口问题
2026-06-05 · iOS/mac 删除账号入口改 in-app —— Apple 5.1.1(v) 第二次驳回的对策
背景:App Store Review 第二次驳回 iOS submission(ID f3affd6e-...,build 12)。reviewer 反馈"删除账号需要额外创建账户或添加密码"。实际原因:原入口跳 auth.boling.fun/me?from=calendar,SFSafariViewController 是未登录态,弹出的登录页只暴露邮箱+密码 + "立即注册"链接,reviewer 主观感受等同"创建额外账号"。
整改:删除账号入口改为 in-app 一键直达,无需跳浏览器、无需重新登录、无需添加密码。
客户端
- iOS:
SettingsSheet中"删除账号 ↗"改成 destructive Button + 二次确认 Alert + loading overlay + 错误 Alert;成功后 AppState 把 phase 切回signedOut,RootView 自动切到 LoginView - mac:
SettingsView同款改造,行内 link 改成"立即删除",复用 SwiftUI.alert+.overlay - 共用:
AuthService加leaveCurrentApp(),调DELETE https://auth.boling.fun/api/me/apps/calendar;AppState加deleteCalendarAccount(),编排"调 auth → 清 LocalStore → 回登录页"流程 - 「管理账号 ↗」入口保留,供用户做"注销整个啵灵账号"等更全面操作
法律文件
- 《隐私政策》第 6 节用户权利「删除」条目改成"删除日历账号(应用内一键完成)"+"注销整个啵灵账号"两档;§7 数据保留期同步措辞
- 《用户协议》§8.2 服务停止方式更新
后端协调
- 给 auth 团队的接口需求:
docs/auth-spec-account-delete.md - 日历后端
/internal/boling-webhook通道不变,复用现有"user.purged"事件 + HMAC 签名
已知不在范围
- auth.boling.fun 后端
DELETE /api/me/apps/{appId}接口由认证中心团队实施,客户端等接口上线后联调 - 30 天宽限期 / 软删除恢复机制不在本次客户端逻辑内,由 auth 中心自行决定是否保留
- iOS build 13 / mac build 9 上线后回复 Apple App Review
2026-05-27 · 上架前收尾 A1:AI 功能合规披露 + 开关
为 macOS 公开发布做准备的第一波。代码层无 feature 新增,合规层把 v1.0 baseline 之后引入的 AI 解析(M5 系列)正式纳入对外披露与用户可控范围。
法律文件
- 《隐私政策》升 v1.1(2026-05-27):
- 新增第 2.3 节「AI 自然语言识别功能传递的内容」,明确传输范围(仅当次输入文本)、接收方(杭州深度求索)、存储位置(境内)、是否用于训练(否,账户层已关闭)、AI 评估日志的字段构成与保留期(180 天)
- 第 3 节补述「不用于训练我们自有或第三方模型」+ 已关闭 DeepSeek 平台层训练用途开关
- 第 5 节共享对象表格新增 DeepSeek 条目
- 第 6 节用户权利新增「关闭 AI 解析」入口;「拒绝自动化决策」补充说明 AI 解析必经预览确认
- 第 7 节数据保留期补充 AI 评估日志条目
- 《用户协议》升 v1.1(2026-05-27):
- 第 2 节服务说明显式列出「AI 自然语言事项识别(可选功能)」
- 第 9.3 节新增对 AI 识别准确性的免责声明:识别结果不保证完全准确,必经用户预览确认,用户对最终保存内容负责
客户端
- AppState 加
aiParseEnabled(UserDefaults 持久化,默认开启),quickAddParse在关闭时直接跳过/parse路由,整句输入作为标题落到今天,不向 DeepSeek 发任何文本、不写 AI 评估日志 - SettingsView 加「智能」section,含「AI 自然语言识别」开/关切换 + 一行小字说明 + 隐私政策章节引用
已知不在范围
- DeepSeek 平台账户层「数据用于优化体验」开关的实际切换状态由 Chris 在 platform.deepseek.com 控制台核对(A1 仅在文档与代码层承诺已关闭,需运营动作落实)
- 客户端版本号 / Info.plist bump 与 changelog 内的版本字样改动留给 A2
2026-05-27 · 上架前收尾 A1 续:数据隔离审计 + AI /parse 配额
接着 A1 同日完成两件:
数据隔离审计(无代码改动)
- 逐文件核
routes/items.mjs/calendars.mjs/me.mjs全部读写都带WHERE user_id = $req.userIdInternal /parse/:trace_id/confirm通过外部 trace_id 寻址,显式做了 user_id 二次校验,跨 user 一律 404- SQL 全参数化,schema 上
items/calendars/ai_traces都有user_idFK + ON DELETE CASCADE - 共享 DeepSeek API key 是账户层付费 / 限流单位,不可能引起业务数据跨用户冲突
- 结论:数据隔离干净,无需修复
AI /parse 配额基础设施
防御场景:一个滥用账号能把共享的 DeepSeek 账户余额烧干,同时让其他正常用户的 /parse 请求 429 / 慢。
- migration 010:
users表加ai_parse_quota_per_minute/ai_parse_quota_per_day两列,NULL 表示用全局默认(避免现存行回填);非空时 CHECK > 0 backend/src/ai-quota.mjs新文件:checkAiParseQuota(userIdInternal)基于ai_traces表做窗口 count(分钟 / 24h rolling),两层任一超限抛RateLimitError/api/items/parse路由顶部接配额检查(在 parseItemText 之前 —— 不触发就不消耗 DeepSeek 钱、不写 trace),超限返 429 +Retry-After头 +{code: rate_limited, scope, retry_after_sec, limit}- env 加
AI_PARSE_QUOTA_PER_MINUTE/AI_PARSE_QUOTA_PER_DAY默认 10 / 200(正常人日均 5-20 次,留 10 倍 headroom) - 客户端无需改动:429 通过现有 catch 走到 title-only fallback 路径
配额语义说明
- 计数包括失败 trace(DeepSeek 超时 / invalid_json 等也算)—— 防止刷失败请求绕过 cap;失败也消耗了 DeepSeek 并发额度
- 当前版本所有用户共用全局默认;架构已支持 per-user override,未来给「付费用户更高 cap」「滥用用户更严限制」铺路,不需要再迁移 schema
- 并发 in-flight 请求可能小幅超过 cap(先 check 后写 trace 之间的 race),当前规模无所谓;未来要严格可改成"先 INSERT pending trace 再调 DeepSeek"
Chris 需要在 platform.deepseek.com 控制台动作
- 确认账号设月度消费上限(建议先 ¥100/月 兜底)
- 确认「数据用于模型训练 / 体验优化」开关已关闭(隐私政策第 3 节已明示)
2026-05-26 · M5 v3-1 收尾三件套(trace 孤儿 + TodoView recurring + reminder Picker)
延续当天 M5v3-1 trace 埋点的收尾批:把 M5v2 留的两个 known-simplification 推进一格,把 trace 孤儿堵掉。
v3-1.1 · /parse fallback trace 孤儿修复
之前 /parse 失败时服务端写了 error trace 但 trace_id 没回客户端,客户端走 title-only fallback 后那条 trace 永远停在 preview_action=NULL —— 做 eval 时分不出「AI 失败但用户接受 fallback」vs「AI 失败用户走掉」。
- migration 009:
ai_traces.preview_actionCHECK 扩到(accept | cancel | fallback) - 服务端
/parse错误响应里也带trace_id(之前 fire-and-forget 写 trace,现在 await 拿 id) - 服务端
/confirm路由接action='fallback' - 客户端
APIError.parseTraceId从错误响应体里抠trace_id(私有 ErrorEnvelope 加字段,避免改 APIError 枚举的入侵) - 客户端
quickAddParsefallback 路径用action='fallback'回填 trace;空 items 防御分支也走同一兜底 confirmParseTrace签名从accept: Bool改为action: String,老的 accept / cancel 两个 callsite 同步改
v3-1.2 · TodoView 对 recurring 显示「下一次」occurrence
M5v2 留的简化:TodoView 直接展示 seed 那行(date 可能在过去)。现在替换为「下一次未来 occurrence」的 virtual item,更符合待办语义。
LocalStore.todoItemsWithNextRecurring(includingCompleted:):非 recurring 全量 + 每个 recurring seed 从今天起向后查最多 365 天的第一个 RRULE match,生成 virtual(id = seed.id,date 改成 next occurrence)- TodoView.reload 改调新方法
- 操作语义保持 v2 简化:toggle / edit / delete 仍落到 seed(跟 Week/Month 一致,因为 virtual 共享 seed.id)。占位:occurrence-level 完成态后续 v3-2 再说
v3-1.3 · PreviewList reminder 改 Menu 可切换
M5v2 留的简化:reminder chip 只有 chip + X(要改只能整条删重输)。reminder 5 档枚举边界模糊(用户说"提前一会儿" → 模型选 5 还是 15?),AI 抽错频率高。改成 Menu 一键切,包含「无提醒」清除项。
ReminderMenuChip新组件:bell icon + 当前档文本 + chevron 触发 Menu,6 项(无 + 5 档枚举),当前选中带 ✓- 第三行 chip 行从「rrule 或 reminder 任一非空才显示」改为「reminder 永远显示让用户可发现」
- RRULE chip 维持现状(修改 RRULE 仍然只能整条删重输 —— UI 工作量大但场景频率低,留 v3-2)
为后续 eval / few-shot bank / profile 注入打数据底座。每次 POST /api/items/parse
落一条 ai_traces,PreviewList 落地(accept/cancel)回调 /confirm 路由回填用户行为
- 字段级 diff。不影响主流程:trace 写失败仅 warn,客户端 confirm 失败完全静默。
后端
- migration 008:
ai_traces表,三类列(输入 / 模型层 / 用户行为)+error_code错误轨;用 JSONB 存原始模型输出和 normalize 后的 items,方便日后 SQL 切片 backend/src/ai-parser.mjs:导出PROMPT_VERSION常量(v2.1);parseItemText返回里 附带promptVersion+model,给 trace 写入用backend/src/routes/items.mjs:/parse成功 / 失败两条路径都写 trace;成功路径把trace_id塞进响应。新增POST /api/items/parse/:trace_id/confirm路由 收{action: accept|cancel, final_items?},accept 时算computeUserEditsDiff落user_edits_diff列。幂等(已 confirmed_at 的二次调用不覆盖),鉴权按trace.user_id == 当前 user,跨 user 一律 404
客户端(macOS)
APIClient.confirmParseTrace完全静默实现(trace 是埋点不是主流程,任何错误 只 print 不抛)AppState.quickAddPreviewTraceId跟 preview state 同生命周期,quickAddConfirm/quickAddCancelPreview走Task.detached不阻塞 UI 回填ParsedItem改Codable(原来只Decodable),confirm 路由要把 final_items json-encode 回服务端
已知 trade-off
/parse失败 fallback 到 title-only 时(AppState.quickAddText),错误路径 服务端写了 trace 但 trace_id 没返给客户端,所以那条 trace 永远不会被 confirm。 M5v3 做 eval 时这部分样本"用户后来怎么处理的"是个 hole,可接受- TodoView 对 recurring 仍按 seed 显示一次(M5v2 留的简化,未在本次范围内)
2026-05-25 · M5 AI 自然语言识别新建事项
DeepSeek V3 接入,QuickAddPanel 输入自然语言一句话直接抽出结构化事项走预览 → 落地。当天 v1 + v2 一起做完。
v1 范围(拍板):title + date + start_time 三字段,单事项,必经 preview 确认。
v2 范围(v1 完成后当天追加):多事项数组、recurring(iCal RRULE 子集)、reminder 偏移枚举。
后端
backend/src/ai-parser.mjs:DeepSeek V3 (deepseek-chat) 封装,env 配置DEEPSEEK_API_KEY/AI_MODEL/AI_BASE_URL。prompt 明确要求{items:[...]}数组结构,给出 RRULE 输出样例和 reminder_rule 严格枚举(ON_TIME / 5_MIN_BEFORE / 15_MIN_BEFORE / 1_HOUR_BEFORE / 1_DAY_BEFORE)- normalizeItem 兜底:title 必非空才进,date/time/RRULE/reminder 不合规一律置 undefined。normalizeRRule 只接受 FREQ ∈ {DAILY,WEEKLY,MONTHLY},BYDAY 检星期字母合法,BYMONTHDAY 检 1..31
POST /api/items/parse路由,返回{ok, data: {items: [...], ai_raw_text, ai_extracted_payload}}- migration 007:items 表加
recurrence_rule列 - smoke 脚本扩到 9 样本覆盖多事项 + recurring + reminder,全部通过
客户端(macOS)
- QuickAddPanel 输入直接调
/parse,结果走 PreviewList overlay 必经确认(不做 confidence 分支,Chris 原话「极低容忍度,能慢一点但不要出错」) - PreviewList 替换原单事项 PreviewCard,支持一次落地多条
Util/RRule.swift新建:RRULE 子集 expander,跟后端对齐- ReminderScheduler 重写为三互斥路径:(1)
reminder_at绝对时间 (2)reminder_rule算偏移 (3)recurrence_rule + reminder_rule按 RRULE 展开未来 5 次发生逐个调度。60 天窗口,单 item cap 5 occurrence 控通知配额 LocalStore.expandedItemsInRange:recurring 事项按 RRULE 在范围内展开成 virtual 实例,WeekView / MonthView 接入。TodoView 暂按 seed 显示一次(v2 范围简化)- 客户端本地 SQLite items_local 表补
recurrence_rule列(v3 migration),之前漏给客户端补 schema 导致视图 reload 直接崩
2026-05-24 · v1.0 基线后第一波打磨
刚冻结的 v1.0.0 之后接着补的几项,没破基线 schema/API。
关键 fix · 客户端时区接通
SettingsView 那个时区下拉 1.0 阶段一直是摆设:能 PATCH 到后端,但客户端任何视图都没读出来用 —— isoDateString 硬编码 Asia/Shanghai,Calendar.current 默默走系统时区。结果用户改成纽约时区,所有"今天/明天/这周"还按系统时区算。
新增 UserTimeZone 全局 holder(AppState 在 me 变化时同步),WeekView / MonthView / TodoView / ItemEditor / ReminderScheduler 里所有日期边界、DateFormatter、DatePicker 统一接进去。SettingsView 下拉真正生效。
feat · 外观三选项
设置页加「外观」一行:跟随系统 / 浅色 / 深色。
- 持久化走 UserDefaults,不进 profile / 不同步后端 —— 外观是「这台设备」偏好
- 跟随系统 = 沿用 1.0 行为;强制 =
.preferredColorScheme()覆盖整窗 - Tokens 里的 dynamic NSColor 已按 effectiveAppearance 自动 resolve,无需 fork 主题
polish
- icon 再换一版(极简进度环风格),源图 2048px 已归档
docs/design/icon-source.png,appiconset 自动重生成 - 顶栏菜单 app 名 / 「退出 X」改走
CFBundleName = 啵灵日程,之前会露英文 "Calendar" - 待办视图加双击空白快速新建(与周/月视图行为对齐),默认日期 = 今天
2026-05-24 · v1.0.0 基线冻结
第一个稳定版本基线,git tag v1.0.0。客户端 / 后端 / 公开网页所有版本号
统一对齐到 1.0.0,App icon 接入 Asset Catalog(10 个尺寸自动从 2048px 源生成)。
完整能力清单见 docs/release-notes/v1.0.0.md。
接下来的迭代起点:
- 多日历分类(工作 / 生活 / 学习等)—— schema 预留
calendar_id字段 - AI 接入(自然语言事项识别)—— schema 预留
ai_*字段 - 衍生产品 fork —— 共享设计系统 + 同步引擎 + 认证流
2026-05-24 · 1.0 发布前修复清单
ship 前一次性清掉 review 出的 8 项。
安全 + 合规:
- 隐私政策修正:令牌存储描述从「Keychain」改为实际的「沙箱容器 0600 + FileVault」(之前是政策表述跟代码实现不一致)
- marked 关掉 raw HTML 透传,防止未来加用户提交 markdown 时存储型 XSS
/internal/*内部接口鉴权用crypto.timingSafeEqual替代普通 === 比较(防 timing attack)- nginx 给
/internal/加 IP 白名单(只允许 auth.boling.fun 服务器和本机) - CORS 启动时强制非通配
*(配 credentials: true 时*是安全风险),生产 .env 已正确配置
稳定性:
- 登录 deep link 加防重入 guard(用户连点两次「登录」开两个浏览器 tab 不会乱序)
- code 长度上限 256 字符防止异常 URL 注入
体验:
- 全局错误 banner 移到主区顶部(之前藏在侧栏底部,侧栏隐藏时看不到);用户可手动关闭,下次同步成功自动清
- TodoView / MonthView / WeekView 各处吞错的 catch 都补上写
lastSyncError,让用户知道哪里失败 - WeekView / MonthView 空状态:本周/本月无任何事项时叠加引导文字「⌘N 新建 · ⌃Space 快速添加 · 双击空白也可以」
2026-05-24 · 待办视图重构 + 拖动排序体验改进
- 拖动 drop 区从 6pt 扩到 12pt,命中范围加倍,不用小心翼翼瞄准
- drop strip 内置 1pt 分隔线,替代独立 ThemedDivider,间距更紧凑
- 待办视图默认改为平铺所有未完成事项,行右侧显示相对日期(今天 / 明天 / 5/26)
- 顶部新增「按日期分组」开关;开启后按日期降序分组(最近一天在上,越久远越靠下)
- 日历视图(周视图)拖动排序也享受新的命中范围
- 修日历视图排序不生效 + 跟待办不联动的 bug(LocalStore SQL 之前漏了 sort_order 字段)
2026-05-24 · 快捷键 + 全局 ⌃Space 快速添加
菜单快捷键:
⌘1周视图 ·⌘2月视图 ·⌘3待办视图⌘T回到今天⌘N新建事项 ·⌘,设置 ·⌘W关闭窗口(系统默认)
全局快速添加(类 Spotlight 浮窗):
- 默认
⌃Space在任何前台 app 下都能召出居中浮窗 - 输入文字 + 回车 → 创建到今天的事项,浮窗自动关闭
Esc/ 失去焦点 → 关闭- 设置页加快捷键 Recorder,可自由改绑
- AppDelegate 拦截 last-window-close,窗口关掉 app 留后台,热键继续生效(
⌘Q才真退出)
2026-05-24 · Dark Mode
macOS 客户端 + 公开网页(主页 / 文档中心 / 协议页)全面支持暗色模式,跟随系统外观自动切换。
- 客户端:
Tokens.swift颜色全部走 dynamic NSColor,light / dark 两组 RGB 内部自动选 - 公开网页:CSS 加
@media (prefers-color-scheme: dark) - 设计原则:暗色基调保留温度感(背景反相成暖中性近黑 #1C1B19 而非纯黑),正文不用纯白用暖灰 #E8E5DD,强调色橄榄棕略提亮
- 不再强制
.preferredColorScheme(.light),跟系统走
2026-05-23 · 公开发布候选
主页和文档中心上线;客户端核心交互打磨完成。
- 主页:杂志风排版 + 情绪化表情角色(眼神跟随鼠标 / hover 假装看别处)
- 法律协议页:用户协议和隐私政策合并到二级页面,左侧切换 / 右侧目录锚点
- 文档中心:版本更新列表
- 日历视图支持拖动事项跨日期移动(周视图、月视图同款)
- 滚动条样式细化(细、矩形、淡色,跟全局调性一致)
- 顶栏布局精简,日历视图顶栏更紧凑
2026-05-23 · v0.7.0 · 端到端 SSO
把"在浏览器登录 → 回到 app 重新输密码"折叠成一次跳转。
- macOS 客户端注册
bolingcalendar://URL scheme - 浏览器完成 login / register / connect 后 302 deep link 回跳 app
POST /api/auth/exchange用 one-time code 换 access / refresh token- 登录页加合规同意提示,设置页加协议入口
2026-05-23 · v0.6.2 · 首次开通走 connect 中间页
认证中心不再隐式 lazy provision。首次用户和退出后重新加入走同一条中间页确认链路。
/auth/verify新增user_not_authorized错误码(区分于user_left_app)- 客户端 connect URL 统一带
?return=bolingcalendar://auth-callback参数 - 修复后端把任何非
user_left_app的 403 默认归到user_disabled的隐藏 bug
2026-05-22 · v0.6.1 · 啵灵 webhook 接入
后端订阅 user.disabled / user.deleted / user_left_app 等用户生命周期事件,
本地软删用户业务数据。
2026-05-22 · Sprint 2 骨架
- macOS 客户端骨架(XcodeGen + SwiftUI + GRDB)
- 后端
/api/items/sync增量同步端点 - 同步协议文档化