Commit Graph

28 Commits

Author SHA1 Message Date
lc a88c4da361 docs: 添加视觉与动效全量升级实施计划 (15 个 task) 2026-07-08 11:29:45 +08:00
lc 53b57e8bd4 docs: 添加视觉与动效全量升级设计 spec
完整方案:深色模式 + 骨架屏 + 浮动 TabBar + 数字滚动
+ 全屏彩带 + 页面级转场 + 排名位移动画 + 空状态插画
零预算,不引第三方包,2 个工作日完成
2026-07-08 11:26:44 +08:00
lc fb435121e4 fix: packOptions忽略编辑器临时文件,防止上传报错
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 13:25:01 +08:00
lc 6af09e4324 fix: 防连点产生重复训练记录 + 补充遗漏的状态重置
- finishTraining 添加 _saving 防重入,避免1.5秒延迟期间连点
- _init 补充 _last30Signaled/_last10Signaled 重置,修复
  多次训练时语音提示丢失的bug

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 13:20:03 +08:00
lc 1e61ab4fba fix: _openid为空时也应允许add()创建云端文档
之前当getOpenid未部署时,_doPush直接return,永远跳过了
db.collection().add(),导致集合无法自动创建、数据不能上云。
改为跳过locate+update但允许add(),CloudBase会自动填充_openid。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 13:13:36 +08:00
lc 9578273906 fix: 移除榜单失败toast,避免无数据时误导用户以为云端故障
日榜无数据是正常业务场景(用户当天未训练),弹toast会
让用户误解为系统故障。本地fallback已提供足够的降级体验。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 12:30:51 +08:00
lc 46738dd7cd fix: 榜单云函数失败时仅无数据才提示toast,避免已有数据时误弹
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 12:29:33 +08:00
lc d817d5d012 fix: 13项数据存储与排行榜修复
致命修复:
- leaderboard: 日榜日期比较改用 dateOnly() 替代严格全等
- plan: 计划天数计算日期比较改用 dateOnly()
- cloud: pullAll 添加 orderBy('updatedAt','desc') 确保取最新文档避免数据丢失

严重修复:
- cloud: 新增 cancelPendingPush() 防止清除数据后被延迟推送复活
- settings: _doClearData 补充 user_profile 清除, 先取消推送再清除
- app: 云端同步改为时间戳比对策略, 支持跨设备拉取
- app: _restoreFromCloud 增加空数据保护和 profile 恢复
- storage: 记录ID改用时间戳+自增计数器确保唯一性

中等修复:
- cloud: init/_fetchOpenid 日志升级, 新增 syncStatus getter
- leaderboard云函数: 添加90天活跃过滤减少全表扫描
- storage: validateStreak 增加lastDate无记录时的通用修复
- leaderboard页面: 云函数失败时Toast提示用户而非静默降级

轻微修复:
- timer: onUnload 添加 voice.destroy() 释放音频上下文
- records: onLongPressDelete 委托给 onDeleteRecord 消除重复代码

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 12:22:54 +08:00
liucheng 6d1992869e fix: 修复排行榜数据自增长(冷启动重复创建云端文档)
每次小程序冷启动都会为同一用户创建新的云端文档,
导致 leaderboard 把同一份数据累加 N 次。

改动:
- app.js: onLaunch 改为 async,每次启动先 pullAll 填充 _openid / _docId 缓存
- utils/cloud.js: 引入 _ensureOpenid() 单次解析;_doPush 总是先按 openid
  定位现有文档;update 失败不再回退到 add()(避免创建重复文档)
- utils/storage.js: getTodayRecord 按日期部分比较,修复一天第二次训练
2026-06-21 22:56:51 +08:00
lc 47f718a129 feat: 训练开始时添加语音播报 2026-06-11 11:51:14 +08:00
lc 2e2594d154 feat: 修复清除数据、语音缓存、排行榜限制,新增项目配置文件
- 修复 cloud.clearAll 静默失败导致重启后数据恢复
- 清除数据弹窗改用自定义 ui-modal 替代 wx.showModal
- TTS 语音合成增加 fileID 持久化缓存,重启不再重新合成
- 排行榜限制前15名,maxRank 参数化到 config.js
- 新增 config.js 统一管理版本号/更新日期/开发者/排行榜限制
- progress-ring 消除 getSystemInfoSync 弃用警告
- voice.js 增加 InnerAudioContext 错误监听
- storage/util 完善用户资料、打卡日期精度、记录日志
2026-06-11 11:30:47 +08:00
lc 1d55df72b3 feat: v1.5 — plan editor, voice prompts, UI polish
Major features:
- Training plan editor: edit preset days/duration per plan (in-place
  override via custom_plans storage; preset ids preserved so existing
  records stay valid)
- Voice prompts at 4 fixed points during training (halfway / 30s /
  10s / done) via Tencent Cloud TTS (cloudfunctions/tts + utils/voice.js)
- Free-training button: switched from outline (transparent) to ghost
  variant (theme-tinted) for visual weight

Bug fixes:
- 4 functional pages: SVG data URIs failed to render in WeChat because
  '\#' in colors was parsed as a data-URI fragment delimiter. encode the
  whole SVG via encodeURIComponent in utils/icons.js build().
- Old records (saved before id field was added) could not be deleted
  (data-id was empty, triggered defensive guard). Backfill stable
  legacy-<month>-<index>-<duration> ids in getRecords() and persist.
- settings.js plan-row editor button event was bubbling up to the row's
  bindtap (which also fired onSelectPlan). Wrapped in catch:tap.

UI:
- Settings page: 3 hardcoded plans replaced with dynamic buildPlansList
  that overlays custom_plans on top of presets
- Plan editor: bottom-sheet modal in settings page (regular view, not
  ui-modal — WeChat custom component root element drops position:fixed
  in this runtime)
- Free-training button: rounded pill style, full-width, 24rpx gap from
  primary action; bottom sheet uses max-height: 88vh + internal scroll
- Version bumped to v1.5, last updated 2026-06-10

Removed:
- Daily reminder section (dailyReminder / reminderTime) — replaced by
  the 4 voice prompts which cover the same user need without requiring
  long-term scheduling that WeChat mini-programs can't actually do

Misc:
- utils/plan.js refactored to formula-driven: presets declare
  totalDays/startTarget/increment/cycleDays, days[] is generated. Same
  formula applies to custom plans.
- Timer _remind() guards each prompt on minimum duration so short
  free-mode sessions don't fire 'last30' at the start
- Cloud storage cloud_plans field added to data push payload; restored
  on first install via _restoreFromCloud
- .gitignore added for local AI tool caches (.reasonix/, reasonix.toml,
  .codegraph/daemon.pid)
2026-06-10 17:23:13 +08:00
lc 90e0e64156 fix: correct cloud upsert — query then update/add per user
Before: add-first-then-update caused duplicate docs on each push
because add() always succeeds once collection exists.

Now: query {_openid} first → update if found, add if not.
Each user always has exactly one document in plank_data.

Also removed debug console.log — only production-scale errors remain.
2026-06-04 17:41:29 +08:00
lc 6f33dd54c0 debug: add console logs to cloud.js for diagnostics 2026-06-04 17:37:01 +08:00
lc fa8d8e7b4b fix: seed existing local data to cloud on first launch
Previously cloud sync only triggered on writes. If the user already
had training records before cloud was enabled, they'd never be pushed
— leaving the database empty until the next training session.

Now onLaunch handles both cases:
  - Local empty → restore from cloud (recovery after deletion)
  - Local has data → check cloud; if cloud empty, push existing data up

Also extract _restoreFromCloud to reduce nesting.
2026-06-04 17:29:13 +08:00
lc ec52eb7b0d fix: auto-create cloud collection on first push, remove manual setup
The collection 'plank_data' no longer needs to be created manually.
db.collection().add() auto-creates the collection on first write.
Changed push strategy from 'query-then-add' to 'add-first':
  - add() succeeds → collection created, data stored
  - add() fails (duplicate) → fall back to query + update
  - .get() on non-existent collection now caught gracefully

Also switched updatedAt to db.serverDate() for consistent timestamps.
2026-06-04 17:22:51 +08:00
lc 9b67cc2e22 chore: configure CloudBase env ID cloudbase-d1g56kl2q8f4f7d8a 2026-06-04 17:17:57 +08:00
lc a04c017e37 feat: cloud sync with WeChat CloudBase — data survives app deletion
Problem: All data stored in wx.StorageSync is permanently lost when
user deletes the mini-program or clears cache.

Solution: Sync data to WeChat CloudBase (wx.cloud.database).
- Graceful degradation: if cloud env isn't configured, all local
  storage works exactly as before.
- On app launch: if local records are empty, pull from cloud.
- On writes: push to cloud (debounced 2s) after saveRecord,
  deleteRecord, saveSettings, updateStreak, setTheme.
- Clear data in settings also clears cloud.

Files:
- utils/cloud.js: cloud sync wrapper (init, pullAll, pushAll, clearAll)
- app.js: cloud.init() + restore on fresh install
- utils/storage.js: cloud.pushAll() after every mutation
- utils/theme.js: cloud.pushAll() after setTheme
- pages/settings/settings.js: cloud.clearAll() when clearing data
- project.config.json: cloudfunctionRoot added

Setup required (one-time in WeChat DevTools):
  1. Open 'Cloud Development' panel → enable CloudBase
  2. Create environment → copy env ID
  3. In app.js, change wx.cloud.init() → wx.cloud.init({ env: 'YOUR-ENV-ID' })
  4. Create 'plank_data' collection in cloud database
  5. Set collection permission to 'Only creator can read/write'
2026-06-04 17:13:35 +08:00
lc a5c591eaf3 fix: navigation bar color not updating on per-page basis
Root cause: _setNavBarColor used _lastNavColor (a module-level cache)
to skip wx.setNavigationBarColor when the color appeared unchanged.
But wx.setNavigationBarColor is per-page — each tab page has its own
navigation bar and needs an explicit call. The cache prevented
subsequent pages from ever receiving the call.

Example trace:
  index onShow → _setNavBarColor(#FF6B35) → _lastNavColor=#FF6B35 ✓
  settings onShow → _setNavBarColor(#FF6B35) → cache hit, return ✗
  User picks green → setTheme → _setNavBarColor(#43A047) ✓
  index onShow → _setNavBarColor(#43A047) → cache hit, return ✗
  → Index still shows orange!

Fix: remove _lastNavColor guard entirely. Keep setTimeout debounce.
2026-06-04 17:08:52 +08:00
lc 4338501671 fix: update last updated date to 2026-06-04 2026-06-04 17:04:58 +08:00
lc 6dc7d2da1c chore: bump version to v1.2.0, add last updated date 2026-06-04 17:04:28 +08:00
lc fb3bc6c045 fix: CSS custom properties not resolving from inline style
Root cause: WXSS compiler resolves var(--primary) at compile time
against page{} declarations in app.wxss. The orange defaults
(--primary:#FF6B35 etc.) were being baked into compiled WXSS,
so the inline style theme override on .container was ignored.
Records and settings pages always rendered orange regardless of
the user's theme choice.

Fix:
1. Remove --primary, --primary-light, --primary-bg, --primary-rgb
   from page{} in app.wxss, forcing WXSS to resolve var() at
   runtime from inline style on .container
2. Expand BASE_VARS in theme.js to include default orange theme
   vars, so the initial render (before onLoad) still has valid
   CSS custom properties — no flash of unstyled content
3. Export BASE_VARS from theme.js
4. Change all 4 pages + custom-tab-bar initial data.themeStyle
   from '' to themeMod.BASE_VARS, eliminating the empty-string
   gap between first render and onLoad
2026-06-04 17:03:09 +08:00
lc 8079242daf fix: theme not applying to training/records pages
Root cause: themeStyle was on .container only, but modals (picker,
day-detail) were siblings of .container in the WXML tree. CSS custom
properties set via inline style on .container don't cascade to
sibling elements — those elements fall back to app.wxss page-level
defaults (always orange). Users saw modal buttons in orange and
concluded the whole page didn't follow theme.

Fixes:
- index.wxml: Move picker modal inside .container so it inherits
  themeStyle CSS variables (position:fixed is viewport-relative,
  so nesting doesn't affect modal positioning)
- index.js, records.js: Add applyThemeToPage in onLoad alongside
  onShow, preventing a flash of default orange before onShow fires
- timer.js: Add onShow with applyThemeToPage as lifecycle safety
  net; extract _init from onLoad for clean separation
2026-06-04 16:53:53 +08:00
lc e2186b2502 fix: theme persistence — error handling, backup, stale highlight
Bug 1 (critical): setTheme lacked try/catch around wx.setStorageSync.
  If storage write throws (full / permission), the theme silently
  fails to persist but UI has already changed → on next cold start
  getCurrentTheme falls back to orange.
  → Wrap in try/catch; still apply UI change even if storage fails
    (better to see the right color now than revert immediately).

Bug 2 (display): settings onShow called applyThemeToPage (updates
  'theme' object) but never refreshed 'currentThemeId'. After tab
  switching, the radio highlight showed the wrong theme.
  → Add setData({ currentThemeId }) in onShow.

Redundancy: theme now also saved as themeId in user_settings.
  getCurrentTheme falls back to user_settings.themeId if app_theme
  key is unexpectedly missing, providing a recovery path.
2026-06-04 16:43:32 +08:00
lc 5d155e41fe fix: tab bar icons invisible due to CSS variable isolation
Root cause: WeChat miniprogram components have style isolation by default.
Changing tab bar hardcoded #888/#666 to var(--text-secondary) broke
because --text-secondary was only defined on page{} in app.wxss and
couldn't cascade into the custom-tab-bar component.

Fix:
1. theme.js: inject BASE_VARS (--text, --text-secondary, --border, etc.)
   into themeStyle string so they're available via inline style everywhere
2. Add styleIsolation: apply-shared to all 3 component JSONs so page-level
   CSS variables cascade into components
2026-06-04 16:39:17 +08:00
lc 89d93c8155 refactor(ui): 12-point UI polish and bugfixes
Bug fixes:
- #1 Remove duplicate month label from calendar-heatmap (shown twice in records)
- #2 Extract shared .modal-overlay to app.wxss (was duplicated in index/records)

UX improvements:
- #3 Add long-press hint to history list in records page
- #4 Add drag handle + catchtouchmove to picker modal
- #5 Extend timer auto-navigate-back from 1.5s to 3s
- #6 Add clear data option in settings with confirmation dialog

Visual/code polish:
- #7 Remove 4 unused @keyframes (shimmer, spin) and 5 unused .icon classes
- #8 Tab bar inactive colors: hardcoded #888/#666 -> var(--text-secondary)
- #10 Split progress-ring observer to avoid redundant setData on size/color changes
- #12 Remove meaningless Math.min(planDay, 99) cap
2026-06-04 16:34:22 +08:00
lc 8c7f633541 refactor: ES6+ modernization + 2 bugfixes
- var→const/let, function→arrow/class across all 13 JS files
- Timer: prototype→class with ES6 getters
- plan days: IIFE→Array.from declarative generation
- storage/plan: unified formatDate via util.js
- Bugfix: getPlanDay now filters by planId (plan switch accuracy)
- Bugfix: getTodayRecord searches all months (cross-month boundary)
- WXML/WXSS unchanged; public API unchanged
2026-06-04 16:29:25 +08:00
lc ff2700c067 backup: snapshot before optimization 2026-06-04 16:25:43 +08:00