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)
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.
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.
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.
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'
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.
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
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