Skip to content

Releases: LMG-arch/work-time

v3.17.44 — 通知可靠性修复(ID 重复)+ 通知自检升级

Choose a tag to compare

@LMG-arch LMG-arch released this 13 Sep 23:53

本次修复:通知到时间不响

排查方式(无真机也能验证)

在浏览器注入 Capacitor 插件 mock,完整跑通 Android 调度分支(真机实际执行的那条路径),断言渠道创建 / 调度条数 / exact / allowWhileIdle / extra.kind。

结论:v3.17.43 的调度逻辑本身是正确的(0 错误、3 个渠道、56 条通知、exact:true)。

发现并修复的真实缺陷

通知 ID 重复:旧算法 = 秒级基准 + 自增计数 + 随机数(0~999),计数与随机数会互相抵消。

  • 实测批量调度 56 条时出现重复 ID
  • 重复 ID 会让后一条覆盖前一条 → 部分提醒静默不响
  • 已改为「秒级基准 + 自增序号」,复测 56 条 ID 全部唯一

自检升级(重点)

真机看不到 console,所以「设置 → 🔍 诊断通知问题」现在直接给出完整证据,一次复制即可定位:

===== 上班日历 · 通知自检 =====
版本: 3.17.44 (versionCode 81)
环境: Android (Capacitor)
精确闹钟模式(本会话): ✅ 精确 exact / ⚠️ 非精确 inexact
提醒数据: 4 条(启用 4 条)
✅ LocalNotifications 插件已加载
✅ 通知权限: granted
❌ 精确闹钟权限: denied → 系统设置→应用→上班日历→闹钟和提醒,开启后重启 App
  渠道 clockin-reminders: importance=5 sound=default
📋 待发送通知: 56 条(clockin:56)
最近调度[clock-in]: 2026/9/14 07:48 生成56 调度56 丢弃0 剩余配额397

怎么读:

  • 待发送通知: 0 条 → 系统里没排上闹钟(打开 App 保持前台 10 秒会自动重排 14 天)
  • 精确闹钟权限: denied → 只能走 inexact,Doze 下会「到点不响」→ 按提示开启后重启 App
  • 通知权限: denied → 系统设置里开启
  • 渠道 importance<5 或 sound=null → 通知渠道被系统/用户降级,需在系统设置里改回

若仍不响,问题在设备侧

请把上面的自检报告发来。最常见的三项:

  1. 系统设置 → 应用 → 上班日历 → 通知:允许通知
  2. 系统设置 → 应用 → 上班日历 → 闹钟和提醒:开启(精确闹钟)
  3. 系统设置 → 应用 → 上班日历 → 电池:改为「不受限制」+ 允许「自启动」(国产 ROM 必须)

提示:升级后旧版本的崩溃记录仍会弹一次(存在手机 crashlogs 目录里),点「清除并关闭」即可。

v3.17.43 — 紧急修复 v3.17.42 崩溃 + 通知调度迁移

Choose a tag to compare

@LMG-arch LMG-arch released this 13 Sep 04:03

⚠️ 紧急修复:v3.17.42 崩溃

v3.17.42 精简 window 垫片时删除了 isCapacitorPlatform 等绑定,但多个模块仍以裸名调用(foo() 等价于 window.foo()),导致真机报「未处理的 Promise 拒绝: isCapacitorPlatform is not defined」。

受影响链路:通知调度、会话恢复、统计导出图片、更新检查。请尽快升级。

修复内容

崩溃修复(P0)

  • 修复垫片裁剪导致的裸名未定义崩溃(通知调度 / 会话恢复 / 统计导出 / 更新检查)
  • 顺带修复:renderer.js 启动就绪门仍在等待已删除的 renderCalendar → v3.17.42 每次启动空等 5 秒才加载数据
  • 顺带修复:点击通知跳转、确认打卡后刷新 的死引用改用 Vue 钩子(__vueActivate / __refreshCalendarGrid)
  • 顺带修复:reminderNotifTimer 从未声明(严格模式下抛错,Web 端轮询兜底实际从未生效)

重构 S4.2 · 通知调度迁移(调度算法零改动)

  • 新增 src/lib/notifications.js:打卡/待办预调度、精确闹钟配额守门、测试通知、诊断
  • src/reminders/reminders.js 733 → 48 行,只保留数据函数

全模块显式 import

  • notifications / stats / updater / social / renderer 补齐显式导入,彻底摆脱 window 垫片
  • supabase/client.js 对 getProfile 用动态 import 破除循环依赖

验证

  • vite 构建通过
  • 通知调度回归:scheduleReminderNotifications() / scheduleTodoReminders() 均正常、0 错误(此前必崩)
  • 日历 / 打卡 / 好友圈 / 统计 / 设置 视图遍历 0 错误
  • 新增裸名审计(barename-audit.cjs)并纳入回归矩阵,后续裁剪垫片必须零残留

v3.17.42 重构阶段4 · 经典层下线

Choose a tag to compare

@LMG-arch LMG-arch released this 11 Sep 04:22

v3.17.42 重构阶段 4 · 经典层下线

依据 docs/重构计划-2026-09-11.md 执行 S4.3/S4.4/S4.5/S4.6(S4.1 已随 v3.17.41 发布)。

改动一览

文件 前 → 后 说明
src/index.html 341 → 143 行 删除 .app 死壳(含 59 个 id 的 DOM)与 .toolbar(已被 body.life-mode 永久隐藏,Vue 外壳侧边栏是唯一导航)
src/renderer.js 689 → 190 行 只留三类职责:启动引导 / syncToWindow 桥 / 原生事件(onReminderConfirmed、onDataChanged);60 处死壳 DOM 绑定 → 0
src/shims.js 148 → 65 绑定 按 stage4b-audit.cjs 的真实消费者扫描逐条裁剪;window.refreshAllData 改绑 dataService(social.js 裸名消费方零改动)
src/calendar/calendar.js 260 → 37 行 删除 renderCalendar/createDayCell/openDetailPanel 等渲染函数,保留 getHolidayInfo/loadAllData/loadHolidays
src/todos/todos.js 106 → 41 行 保留 loadTodos/getTodosForDate/isTodoDone 数据内核
src/settings/settings.js 190 行 → 删除 全部为死壳 DOM 代码(SettingsPage.vue 已有本地实现)

验证(agent-browser 实测)

  • 冷启动:life-app 挂载、Vue 外壳 571 个可见元素、0 未捕获错误
  • 6 视图遍历:日历 35 格 / 打卡 24 提醒元素 / 社交 66 元素 / 统计 / 设置 / 生活工作台 全通过
  • 数据链路:saveDay → store → UI 同步(单元格即时显示「班」);getHolidayInfo('2026-10-01')=国庆节
  • 全局 window.onerror = 0

阶段 5 收尾(下一步)

通知调度迁入 src/lib/notifications.js(S4.2,算法零改动)→ social.js 渲染函数清理 → updater 弹窗组件化 → shims 最终退役评估。

v3.17.41 重构阶段3b/4a · 数据服务迁移

Choose a tag to compare

@LMG-arch LMG-arch released this 11 Sep 03:15

v3.17.41 重构阶段 3b/4a · 数据服务迁移

依据 docs/重构计划-2026-09-11.md 推进(S4.1 + S3.1c)。

改动 说明
src/lib/dataService.js 新增 Vue 侧数据刷新唯一入口:拉取日历/待办/提醒/记账数据 → 写入 Pinia store → 触发纯重渲染钩子 → 过渡期继续发布 window 镜像(经典层兼容)
calendarStore 不再从 window.allData 灌数据;syncFromWindow() 改为直接从原生桥重新拉取
刷新钩子 __refreshCalendarGrid / __refreshStats 改为纯重渲染(不再隐式读 window 镜像;保存后 store 自身状态已最新)
设置页 6 处 window.refreshAllData 调用改走 dataService.refreshAllData()(同步/下载/导入/恢复路径全覆盖)
诊断出口 新增 window.__dataService.refreshAllData(),便于排查"同步后不刷新"类问题

验证(agent-browser 实测)

  • 冷启动:Pinia daysData 有数据、Vue 网格正常渲染、0 错误
  • 保存链路:打卡 → 状态写入 → store 与界面同步更新(纯 tick 刷新)
  • dataService.refreshAllData() 端到端:before/after 数据一致、单元格状态正确、0 错误

死壳审计结论(决定阶段 4 策略)

.app 死壳 59 个 id 中 48 个仍被经典脚本引用 → 删除 DOM 必须与经典渲染层删除同批进行,已记入计划 §9。

下一步

经典渲染层分模块退役 → 同批删除 .app 死壳 → 收窄 shims.js → 阶段 5 收尾。

v3.17.40 重构阶段3(第一批) · Vue 去 window 数据镜像

Choose a tag to compare

@LMG-arch LMG-arch released this 11 Sep 01:29

v3.17.40 重构阶段 3(第一批)· Vue 去 window 数据镜像

依据 docs/重构计划-2026-09-11.md 阶段 3 执行(第一批)。

步骤 改动
S3.1a todoStore:初始状态不再取 window.allTodos(改 ref([]),数据由 loadTodos() 经原生桥加载);isDone() 改查自身 todos;deleteTodo()/updateTodo() 改为先更新自身状态再发布到 window(过渡期兼容)
S3.1b reminderStore:reminders/reminderRecords 初始为空;refreshFromWindow() / refreshReminderList() / refreshReminderHistory() 增加类型守卫,不再把 window 当数据源
S3.2a TodoModal:农历换算改为 import { lunarToSolar } from '../utils.js';修复隐患——文件内原有同名本地包装函数,直接 import 同名会造成自我递归,改为 import 原名 + 本地薄封装 lunarDateToSolar

原则:Vue 不再读 window 镜像;仍保留写(经典层未下线,属过渡期兼容,阶段 4 一并移除)。

验证(agent-browser 实测)

  • window.allReminders 仍被 store 加载后发布(4 条),过渡期兼容未破坏
  • 打卡页:喝水 8 杯、提醒相关元素 24、待办行 4 → 列表渲染正常
  • 日历页:Vue 网格 35 格(+ 隐藏经典 35 格);记账页占比条 4 段;总览时间线 5 行
  • 全流程 window.onerror = 0

顺序修订(重要)

执行中发现 S3.1c 反向依赖 S4.1:window.allData 由经典层 calendar.js loadAllData() 产出,只有先把数据服务迁到 Vue 侧才能去掉读取。故 S3.1c + S3.2b + S3.3 与 S4.1 合并推进(详见计划文档 §9)。

下一步(阶段 3 第二批 / 阶段 4 起点)

src/lib/dataService.js(迁移 loadAllData/refreshAllData)→ 去掉 calendarStore/StatsPage 对 window.allData 的读取 → CalendarView/StatsPage 直连 holidays → src/lib/bridge.js 统一 calendarAPI → exportStatsAsImage 迁移 → 删除 .app 死壳与经典渲染层 → 收窄 shims.js。

v3.17.39 重构阶段2 · lifeEngine 稳健化

Choose a tag to compare

@LMG-arch LMG-arch released this 11 Sep 01:21

v3.17.39 重构阶段 2 · lifeEngine 稳健化

依据 docs/重构计划-2026-09-11.md 阶段 2 执行。

S2.1 统一 DOM 写入助手

新增(lifeEngine 模块内):needEl / $el / setText / setHtml / setProp / setStyle / classOp / focusFormField
—— 宿主缺失时只告警一次([life] 缺少元素 #id(写入已跳过)),绝不抛错。

S2.2 裸写全量替换(69 处 → 0)

教训:首版用正则 ([^;]+) 匹配赋值表达式,被箭头函数体内的 ; 截断(recentRecords 一行),导致 vite/rolldown 构建报 PARSE_ERROR,而 node --check 未捕获。改为括号/字符串深度感知扫描器后一次通过;并新增 .mjs 语法校验(模块目标解析,比 node --check 严格)。

类型 处数
textContent / innerHTML / value / disabled / hidden / checked / src / href 66
style.* 2
classList.* 1
合计 69

S2.3 失败隔离

  • renderAll():10 个渲染函数各自 try/catch,单模块异常不再中断整条渲染链
  • init() 绑定阶段:bindForms / bindEvents / initFormPanelToggles 各自隔离
  • 弹层自动聚焦改走 focusFormField()(原先 setTimeout 内裸取 .elements.xxx.focus())

验证(agent-browser 实测)

  • 7 个生活视图(总览/记账/习惯/减脂/周计划/家务/书影音/档案)遍历:0 未捕获错误、0 告警
  • 失败隔离注入测试:强行移除 #homeList #habitProgress #moneyFilter #archiveList 后触发两次 renderAll
    → 4 条精确告警 + 0 未捕获错误 + 其余模块照常渲染(时间线 5 行、饼图占比 4 段、Hero 文案正常)
    (改动前:任一元素缺失即抛错并中断整条渲染链、弹致命浮层)
  • 构建:vite build ✓ 14.6s;ESM 语法校验 ✓

下一步

阶段 3(v3.17.40):Vue 侧去 window 数据镜像与能力直连(holidays/lunar/supabase/原生桥)。

v3.17.38 重构阶段1 · 存储与 API 单真值

Choose a tag to compare

@LMG-arch LMG-arch released this 11 Sep 01:09

v3.17.38 重构阶段 1 · 存储与 API 单真值

依据 docs/重构计划-2026-09-11.md 阶段 1 执行。

步骤 改动 结果
S1.1 主题单一真值:appStore 在创建时应用主题(接管冷启动);删除经典 settings.js setTheme/loadTheme、shims.js 两处桥接、renderer.js 的 loadTheme 调用块 主题唯一写入者 = appStore(键 calendar-theme)
S1.2 导航设置单一真值:删除 renderer.js 经典导航设置 IIFE(55 行,目标按钮位于死壳内) 唯一真值 = calendar-nav-items(LifeWorkbench / SettingsPage 管理)
S1.3 lifeEngine.loadState 存储读写配对:优先 __storage.get(与 saveState 的 set 成对),旧数据回退 getRaw → localStorage 既有数据不迁移、不丢失
S1.4 reminders.js 裸全局 showToast(...) → window.showToast?.(...) 与统一提示入口对齐

验证(agent-browser 实测)

  • 冷启动主题:写入 calendar-theme=purple → 重载 → body.dataset.theme=purple;区域 A --accent = 区域 B --plum = #7e57c2;0 错误
  • 主题持久化:切回 cosmic 后重载保持
  • 生活数据加载(新 .get 路径):records 24 / habits 5 / 总览时间线 5 行;saveText=已自动保存
  • 导航设置(IIFE 删除后):calendar-nav-items 去掉 stats → 重载 → 子导航 日历/打卡/好友/设置(无统计)、侧边栏 [data-work-sub=stats] display:none
  • 记账回归:占比条 4 段 + 图例百分比(购物66%/娱乐19%/吃饭13%/交通2%)
  • 全流程 window.onerror 捕获 0 条

附带产出

  • docs/重构回归清单.md:R1–R12 用例、通用探针、冒烟铁律(含双网格陷阱:必须限定 .calendar-view .day-cell)、阶段 0 基线快照
  • 基线标签 refactor-base(= v3.17.37)

下一步

阶段 2(v3.17.39):lifeEngine 稳健化 —— 统一 DOM 写入助手 + 69 处裸写分区替换 + 渲染函数失败隔离。

v3.17.37 两区域融合:提示/主题/数据/渲染统一

Choose a tag to compare

@LMG-arch LMG-arch released this 11 Sep 00:46

v3.17.37 两个区域融合:提示 / 主题 / 数据 / 渲染统一

融合项(已完成 5 项)

# 融合项 融合前 融合后
1 提示机制 两套实现、两个宿主(#toast / #legacy-toast),配色/位置/时长各异 唯一实现 + 唯一宿主 #toast;utils.showToast 委托 window.__lifeToast;统一样式(--toast-bg 随主题 / bottom 92px / 2400ms)
2 主题令牌 生活区固定暖纸色,仅 dark/cosmic 被覆盖 .life-app 令牌全部由主题令牌派生,15 套主题下两区域同色系联动
3 喝水记录 Vue 留空容器、经典脚本注入并直写 water-records(双写入者) ClockinPage.vue 响应式自渲染,reminderStore 唯一写入者;删除经典 4 函数 + 4 处 window.* 桥接
4 日历渲染 9 处 Vue 组件反向调用经典 renderCalendar()(重复渲染隐藏 DOM) 全部移除,统一走 Vue 自身 __refreshCalendarGrid
5 失效调用 收口后遗留 5 处 renderClockinView() 调用(ReferenceError 风险) 改为 Vue 刷新钩子(renderer ×3 / reminders / calendar)

验证

  • 提示:showToast() → #toast、无 #legacy-toast、bg=主题色、bottom 92px/radius 20px/z-index 1200
  • 主题:green 下区域 A --accent = 区域 B --plum = #2e9e4f;cosmic 无回归
  • 喝水:8 杯渲染、点 + → 1/8 杯、water-records 落库、0 错误
  • 日历:35 格渲染、标记「上班」→ store work + 格子显示「班」、0 错误
  • 发布包校验:__lifeToast 存在、legacy-toast 为 0、water-cups 存在、renderClockinView 为 0

全项目审查

报告见 docs/融合与一致性审查报告-2026-09-11.html:P0 3 项(经典死壳 DOM / lifeEngine 约 98 处无守卫写入 / 经典-Vue 双向耦合)、P1 5 项(双网格、存储双写 ×2、存储 API 不配对、图表配色)、P2 4 项(死函数与注释、裸全局调用、样式遗留),并给出三阶段调整顺序。

v3.17.36 根治删除记录崩溃(showToast 误删 #toast 宿主)

Choose a tag to compare

@LMG-arch LMG-arch released this 11 Sep 00:21

v3.17.36 根治:删除记录时 "Cannot set properties of null (setting 'textContent')"

完整调用链(已逐帧核对 v3.17.32 的 bundle)

文字记录:
Uncaught TypeError: Cannot set properties of null (setting 'textContent')
  at nM (vue-main-Drr0pX4a.js:410:33536)   ← toast()
  at sM (…:410:35149)                      ← deleteRecord()
  at HTMLDocument.<anonymous> (…:410:76219) ← 文档级 click 委托(a==='delete'&&sM(i))

按行列号切片核对确认:nM = lifeEngine toast();sM = deleteRecord();第三帧 = [data-action="delete"] 委托分支。

真正的根因:两套 toast 互相踩踏

src/life/markup.html 里生活工作台的提示宿主是:

<div aria-live="polite" class="toast" id="toast" role="status"></div>

而工作模块(待办 / 提醒 / 好友 / 设置 / 备注…)走 src/utils.js 的另一套 showToast(),原实现是:

const existing = document.querySelector('.toast');   // ← 命中的正是 life 的 #toast(class 同为 toast)
if (existing) existing.remove();                     // ← 把它永久删除
const toast = document.createElement('div');
toast.className = 'toast';                           // ← 自建元素没有 id
document.body.appendChild(toast);
… setTimeout(() => toast.remove(), 300)              // ← 2.1 秒后自建元素也消失

于是:工作模块弹过一次提示后,#toast 就从 DOM 里彻底消失;此后生活工作台任何带提示的操作(删除记录等)调用 getElementById('toast') 得到 null → 直接写 textContent → 崩溃并弹出致命浮层。

这也解释了截图现象:底栏停在「工作」(刚在工作模块触发过提示,如「打卡成功 ✓」),弹出的却是生活工作台操作引发的错误。

逻辑自洽性验证:deleteRecord() 在 toast() 之前会先跑 renderAll(),而 renderAll() 对 #brandAvatar 等元素是无保护的写入。既然错误发生在 toast() 而不是 renderAll(),说明 markup 整体完好、只有 #toast 这一个元素被摘掉了——与上述机理完全一致。

修复

文件 改动
src/utils.js showToast() 只清理自建的 #legacy-toast(并给它加 id),不再用 querySelector('.toast') 误删其他宿主
src/life/lifeEngine.js 新增 ensureOverlayHost():#toast/#confetti 缺失时自动重建(挂 .life-app,无则挂 body + 内联样式)——即使宿主被外力删除也能自愈
src/life/lifeEngine.js 文档级 click 委托整体 try/catch([life] action failed:),单点异常不再升级为致命浮层
src/life/lifeEngine.js initLife() → initWhenReady() 有限重试(8×120ms),界面未就绪不再崩

验证(agent-browser 实测)

  1. 调用 window.showToast('测试提示') → life 的 #toast 仍在,只产生 #legacy-toast;3 秒后 legacy 自动消失、#toast 完好
  2. 完整复现原路径:先 showToast('打卡成功 ✓')(工作模块)→ 切到生活工作台 → 新增并删除一条记录 → 提示仍显示「记录已删除」、0 未捕获错误、无致命浮层
  3. 移除 #toast 后删除记录 → 宿主自动重建、提示正常
  4. 移除 #confetti 后庆祝路径 → 彩带自动重建
  5. 处理器内部异常(故意移除 #brandAvatar)→ 仅 [life] action failed: 告警

安装提示

崩溃日志的 bundle 为 vue-main-Drr0pX4a.js,对应 v3.17.32 安装包。本版(v3.17.36)同时包含 v3.17.33 / v3.17.34 / v3.17.35 的全部修复。

v3.17.35 崩溃修复:删除记录时 textContent of null

Choose a tag to compare

@LMG-arch LMG-arch released this 11 Sep 00:06

v3.17.35 崩溃修复:删除记录时 "Cannot set properties of null"

问题

安卓端(v3.17.32)删除一条记录后弹出致命错误浮层:

Uncaught TypeError: Cannot set properties of null (setting 'textContent')
  at nM (vue-main-Drr0pX4a.js:410:33536)   ← toast()
  at sM (…:410:35149)                      ← deleteRecord()
  at HTMLDocument.<anonymous>              ← 文档级 click 委托

根因

deleteRecord() 末尾调用 toast('记录已删除'),而 toast() 原实现无任何空值保护:

function toast(message){const el=document.getElementById('toast');el.textContent=…}

#toast 取不到时直接写 textContent 抛错,被全局错误处理器渲染成致命浮层。同类隐患还有 celebrate() 的 #confetti,以及 init() 中大量无保护的 getElementById(...).textContent。

修复(src/life/lifeEngine.js)

位置 改动
新增 ensureOverlayHost(id, class, fallbackStyle) 宿主元素缺失时动态创建并挂到 .life-app(无 .life-app 时挂 body 并注入内联样式),返回 null 前不抛错
toast() 走 ensureOverlayHost,宿主建不出来时降级为 console.warn 而不崩溃
celebrate() 同上(#confetti 自建)
文档级 click 委托 整体包 try/catch:单个动作失败 → console.warn('[life] action failed: …'),不再升级为未捕获错误/致命浮层
initLife() 改为 initWhenReady(0):markup 未就绪时有限重试(8×120ms),最终未就绪则明确告警并跳过,避免 getElementById 返回 null 抛错

验证(agent-browser 实测)

  1. 删除记录 + 移除 #toast(复现用户场景)→ 提示宿主自动重建、文案「记录已删除」正常显示、0 未捕获错误、无致命浮层
  2. 移除 #confetti + 勾选任务(庆祝路径)→ 彩带自动重建(16 片)、无错误
  3. 无 .life-app 的降级路径 → toast 挂到 body + 内联样式,仍可见、无错误
  4. 处理器内部抛错(故意移除 #brandAvatar 后删除)→ 仅 [life] action failed: … 告警,无浮层

说明

崩溃上报的 bundle 为 vue-main-Drr0pX4a.js,对应 v3.17.32 的安装包;本版同时包含 v3.17.33(主题颜色统一)与 v3.17.34(消费结构占比条)的修复,建议直接安装 v3.17.35。