Privacy-first iOS app for the Xiaomi Mi Body Composition Scale 2 (XMTZC05HM; label variant XMTZCOSHM treated the same). Replaces Zapp Lite: BLE weigh-ins on-device only, results into Apple Health, optional Coach via a shared Grok Worker, on-device Apple Intelligence when eligible, and a 0.5B Metal polish sidecar on other compatible iPhones.
| Β | Β |
|---|---|
| Version | 1.0.97 (build 192) |
| Device | iPhone 15 (physical; BLE + HealthKit) |
| Xcode / SDK | Xcode 27, iOS 27 SDK |
| Deployment | iOS 26.0, iPhone only |
| Bundle id | app.thescale.ios |
| Team | Human Analog Limited (XHVW66YM39) |
No accounts. No analytics. Weigh-ins never leave the phone except into Apple Health. Users never paste an API key.
PrivacyInfo.xcprivacy ships in the app bundle (no GPS / no tracking)TheScale/docs/operations/legal-url-hosting.mdTheScale/docs/operations/app-store-metadata.mdTheScale/docs/operations/storekit-humananalog.md + Config/Products.storekitTheScale/docs/operations/feedback-supabase-resend.mdITSAppUsesNonExemptEncryption = false (HTTPS only)TheScale/docs/operations/app-id-entitlements.md)GROK_API_KEY empty for store IPAs; Worker holds XAI_API_KEY. Set GROK_APP_SECRET via gitignored Secrets.xcconfig to match Worker APP_SHARED_SECRET (fail closed if missing).ScaleLegal.appStoreReviewNotesMIBFS / service data 0x181B)bodyMass + bodyFatPercentage (ranges 1Wβ1Y; default 2W)GoalPaceGuard / TargetFeasibility). Too-fast calendars are refused; the revision sheet pins Use this date under a compact wheel.HKMetadataKeyWasUserEnteredUserDefaultsBGAppRefresh)FM ready / FM offHKObserverQuery + enableBackgroundDelivery (+ BG refresh/processing backups) so digest/trigger notifications can land without opening the app (iOS still throttles)Eight first-launch pages, one instruction each: Language β Units (kg/lb) β Name β Age/sex β Height & weight β Dream weight + date β Food (optional) β Legal. Units are chosen before any mass dial. Food is diet chips + optional avoidances/city (Skip for now if empty). Dream date must pass the safe-pace gate. After Start weighing, a 3-screen send-off (βGo weigh yourself. Now.β) then home.
app_feedback (source=coach_reply) with live model, on-device model, app/device, reply excerpt.the-scale-grok.alexhuther.workers.dev; requires APP_SHARED_SECRET; IP + device rate limits; KV weekly Free/Plus/Pro caps (5/28/120). iOS sends device id + plan + credit; fail closed without GROK_APP_SECRET in Secrets.xcconfig.fatnag-data-export.json. All Settings Developer/QA chrome deleted (plan override, force Monday, force review, sample notifications, calibration presets, DEBUG commerce).app_feedback via Edge Function submit-feedback β Resend to dev@humananalog.ai. Soft star sheet after 6 successful weigh-ins; system requestReview at most once per install. Sandbox IAP checklist: Agent Store docs/feedback-rating-sandbox-handoff.md + TheScale/docs/operations/storekit-humananalog.md.humananalog.github.io/fatnag. Consumer copy scrub (Diet/Location not set; Settings web-link footnote).utsname only (no MainActor UIDevice). ScaleDebugLog and Foundation Models runtime gate use @unchecked Sendable lock boxes for Swift 6.promptTemplateNotFound spam). Device host failures trip a process-lifetime breaker and fall back to algorithmic / Metal polish copy.UnitFormat.Packages/ScaleOnDevicePolish.weekStartKg / weekStartDate realign when they do not match. Anchor date is always local Monday 00:00, never today, dream weight, or first mid-week weigh.Keel Β· π© drill / body Go drop a π© and use FATNAG after!; Time Sensitive unchanged. Loss delta shows clear minus (-650g) + βYouβre a winnerβ on hero, home, Progress, History.ProgressView(value:total:); Settings/Paywall stay on ScaleBoundedProgress; Progress ACTION gauge remains custom. Home-gauge success logs stay silent; DEBUG FitnessDigest / Coach digest / soft-fail prints throttled via ScaleDebugLog (45s) so observer wakes cannot flood Console. Regression script scripts/assert_no_determinate_progressview.py.650g (metric) or oz (imperial) via UnitFormat. Opacity floor + always-arm entrance preserved. Reduce Motion stays calm..background with solid base fill (LaunchBackground never shows through as dead void); night ink contrast hardened; Progress entrance always arms (no opacity-0 chrome on void); splash 3.5s failsafe; WeeklyGoalAtmosphere.safeFallback.Design/ScalePaletteUniverse.swift; applied to home/Progress haze + Coach/paywall chrome continuity.Mi Scale 2 (BLE ads)
β
βΌ
FATNAG (iPhone)
β
βββ HealthKit ββ Apple Health
βββ UserDefaults (profile, cal, memory, prefs)
βββ Foundation Models (on-device; Apple Intelligence)
β
βββ HTTPS (opt-in Coach / fitness)
β
βΌ
Cloudflare Worker (grok-proxy)
β
βΌ
xAI Grok
Depth: docs/architecture.md Β· docs/coach-and-ai.md Β· docs/health-and-notifications.md
cd /Users/alexclaw/Projects/project-zero
git checkout main && git pull origin main
open TheScale/TheScale.xcodeproj
Standing rule: Xcode 27 + latest iOS. Point CLI tools at Xcode if needed:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
Preferred: xAI key stays on the Worker (never in the IPA). App authenticates with a separate shared secret.
cd workers/grok-proxy
npx wrangler secret put XAI_API_KEY # paste at prompt; never commit
npx wrangler secret put APP_SHARED_SECRET # openssl rand -hex 32; same value β Secrets.xcconfig
npx wrangler deploy
Canonical build config: TheScale/Config/TheScale.xcconfig (Debug + Release).
xcconfig https:// footgun: Xcode treats // as a comment. Never write a bare https:// URL.
# WRONG β becomes https:
GROK_PROXY_URL = https://the-scale-grok.alexhuther.workers.dev
# RIGHT (empty $() splice)
GROK_PROXY_URL = https:/$()/the-scale-grok.alexhuther.workers.dev
GROK_API_KEY =
GROK_APP_SECRET =
Gitignored override: copy Secrets.example.xcconfig β Secrets.xcconfig and set GROK_APP_SECRET. Empty secret β fail closed (offline Coach). Never bake XAI_API_KEY into the IPA.
Worker also rate-limits IP/device and enforces Free/Plus/Pro weekly caps in KV. Details: workers/grok-proxy/README.md.
Health check: curl -s https://the-scale-grok.alexhuther.workers.dev β ok, auth: shared_secret, weekly_limits.
Packages/ScaleOnDevicePolish. Settings shows download / Polish ready.Simulator does not replace on-device BLE + HealthKit verification.
TheScale/TheScale.xcodeprojapp.thescale.iosUnit tests (math only; not a BLE substitute):
cd TheScale
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
xcodebuild test -scheme TheScale -destination 'platform=iOS Simulator,name=iPhone 17'
Smoke-build without a phone:
cd TheScale
xcodebuild build -scheme TheScale -sdk iphoneos -configuration Debug -destination 'generic/platform=iOS'
| Data | Where |
|---|---|
| Scale weight / impedance | In memory on iPhone |
| Profile, persona, calibration, Coach memory, prefs | UserDefaults on device |
| Health reads / writes | HealthKit on device |
| Foundation Models | On-device only |
| Grok requests | Opt-in; short digest + memory β Worker (or build-time key) |
| Network | None by default |
HealthKit write: bodyMass, bodyMassIndex, bodyFatPercentage, leanBodyMass
HealthKit read: those plus fitness digest types for monitoring / Coach context
Muscle, bone, water %, visceral index, raw ohms: in-app only.
Shown once in onboarding and again under Settings β Legal only. Never in Coach chat, Progress roast, or notification copy.
Mi Scale 2 broadcasts; no pairing required for a live reading.
Bytes (0x181B service data) |
Meaning |
|---|---|
| 0 | Unit (bit0 lbs; else kg) |
| 1 | Flags: impedance / stabilized / weight removed |
| 2-8 | Timestamp |
| 9-10 | Impedance ohms (LE uint16) |
| 11-12 | Weight raw (LE uint16): Γ·200 β kg |
Composition numbers are reverse-engineered estimates, not clinical lab values. Not medical advice.
Quirks: name usually MIBFS; ignore unstabilized / weight-removed frames; impedance 0 or β₯ 3000 invalid; socks / wet feet β weight-only.
Sources: ESPHome xiaomi_miscale, Theengs XMTZC05HM, openScale / MIBCS reverse-engineering.
TheScale/
TheScale.xcodeproj/
Config/ # TheScale.xcconfig, Secrets.example.xcconfig
TheScale/ # SwiftUI app (BLE, HealthKit, Coach, FM, UI)
TheScaleTests/
workers/grok-proxy/ # Cloudflare Worker (XAI_API_KEY secret)
scripts/validate_decode.py
docs/ # Architecture, Coach/AI, Health/notifications
| Doc | Contents |
|---|---|
| docs/architecture.md | Planes, data flow, layout |
| docs/coach-and-ai.md | Grok Worker, streaming, FM hybrid, memory |
| docs/health-and-notifications.md | HealthKit, Progress, alerts, FM polish |
| TheScale/docs/operations/app-store-metadata.md | ASC listing copy, keywords, review notes |
| TheScale/docs/operations/asc-screenshots/README.md | Promo mocks sized per iPhone slot |
Current: 1.0.97 (build 192).
Bump marketing + build together when shipping code:
scripts/bump_version.sh # patch +1, build +1
scripts/bump_version.sh 1.1.0 # set marketing, build +1
scripts/bump_version.sh 1.1.0 120 # set both explicitly
Docs-only commits may leave the number unchanged.