| name | zepp-os-best-practices |
| description | Use when improving the quality of a Zepp OS project itself rather than implementing a feature - debugging a Zepp OS app (there are no breakpoints, so logging strategy per runtime), catching otherwise-silent lifecycle errors (errors in onInit / build / onDestroy are swallowed with no source map), wrapping lifecycle hooks so a crash actually surfaces, structuring src/ for readability and unit testing, localizing strings via .po / gettext / i18n, or migrating a legacy 1.0 hm* / hmSensor / hmUI app to the modern @zos/* API. Use even when the question sounds like a generic JS / architecture / testing / i18n question if the context is Zepp OS. Do not use for the implementation itself of a feature - use the topic-specific skill (zepp-os-sensors for sensor reads, zepp-os-ui for widgets, etc.). |
Zepp OS Best Practices
Context: all runtimes (cross-cutting).
Prereq: the contexts and modules these touch — see zepp-os-fundamentals.
Debugging — there are no breakpoints
Zepp OS tooling has no breakpoint debugger; logging is the primary tool. Use the right logger per context:
| Context | Logger |
|---|
| Device App | @zos/utils → log.getLogger(name) (.log/.warn/.debug/.error/.info — filter by level) |
| Side Service / Settings App | console.log |
import { log } from '@zos/utils'
const logger = log.getLogger('page')
logger.log('page created'); logger.error('boom')
Simulator shows separate consoles for Device App, Settings App, and Side Service (each with level/string filters). Real-device logs require Developer Mode in the Zepp App.
Error capture — the framework won't catch lifecycle errors
Errors thrown in lifecycle hooks are swallowed, and logs are truncated with no source-map. So:
- Wrap lifecycle bodies (
build, onInit, …) in try/catch.
- Print the stack line by line (truncation otherwise hides it):
Page({
build() {
try {
} catch (e) {
console.log('LifeCycle Error', e)
e && e.stack && e.stack.split(/\n/).forEach((i) => console.log('error stack', i))
}
},
})
Don't repeat this in every page — wrap the Page/App constructors once (see the PageAdvanced pattern in the showcase sample; URL in references/error-catch.md).
Code organization — separate structure / style / behavior
Split a page so each file does one thing — this also makes logic unit-testable without the simulator:
index.page.js — UI rendering + event dispatch only
index.styles.js (or *.layout.js) — style objects (px()-scaled, see zepp-os-ui)
*.class.js / utils/ — pure logic in classes/functions
Pure-JS logic doesn't depend on the runtime, so run it under Jest for fast iteration. Favor pure functions in utils/ for testability.
i18n — .po files + getText
import { getText } from '@zos/i18n'
const label = getText('calories')
# page/i18n/en-US.po (filename = locale / country-code abbreviation)
msgid "calories"
msgstr "Calories"
defaultLanguage is set in app.json (see zepp-os-fundamentals); full i18n module in zepp-os-device-system.
Migrating 1.0 → 2.0
- The 2.0
@zos/* API is a superset of the 1.0 hm* globals — every 1.0 capability maps to a 2.0 equivalent.
- Only the Device App changes (Settings App + Side Service are unchanged from 1.0).
- 1.0 exposed capability via globals (
hmUI, hmApp, hmSensor, …); 2.0 uses ES-module imports (@zos/ui, @zos/app, @zos/sensor, …).
- Use the official migration CLI
@zeppos/zmt to automate the bulk of it.
- After migrating, target an
API_LEVEL — backward compatibility is then guaranteed (see zepp-os-fundamentals).
Common Mistakes
- Waiting for a debugger → there isn't one; add structured logs early.
- Trusting a truncated stack trace → split
e.stack by line and log each.
- Letting lifecycle errors vanish → wrap lifecycle (ideally the
Page/App constructor) in try/catch.
- Putting all logic in
page.js → extract pure logic to classes/utils so you can Jest-test it without the simulator.
- Hand-porting a 1.0 app line by line → run
@zeppos/zmt first, then fix the remainder.
error-catch validation loop
When a lifecycle hook misbehaves silently:
- Wrap the lifecycle body — ideally the
Page/App constructor once (the PageAdvanced pattern) — in try/catch.
- In
catch, log e, then split e.stack by line and log each (truncation hides single-line stacks).
- Reproduce, read the line-by-line stack, fix the cause.
- Re-run; repeat until no swallowed errors surface. See
references/error-catch.md.
Reference