Five high-impact improvements across the stack:
1. Pre-push hook: svelte-check gate (.husky/pre-push)
Runs `pnpm check --fail-on-warnings` before every `git push`.
Blocks pushes with type errors or warnings so we never drift
back to 418 errors. Takes ~15s on warm cache — acceptable for
push frequency. Skip with `--no-verify` if needed.
2. getUserFromToken: map name/image/twoFactorEnabled
The JWT payload carries these three fields (from Better Auth's
user profile + 2FA enrollment) but getUserFromToken() only
extracted sub/email/role/tier. The Settings page, onboarding
ProfileStep, and TwoFactorSetup all read these via
`authStore.user?.name` etc. and got undefined. Now mapped from
both top-level claims and user_metadata (legacy layout).
DecodedToken type extended to match.
3. Body × TimeBlocks integration
startWorkout() now creates a TimeBlock (kind='logged',
type='body', sourceModule='body') so workouts appear in the
calendar, timeline page, and DayTimelineWidget. finishWorkout()
stamps the TimeBlock's endDate so the calendar shows duration.
deleteWorkout() cascades the TimeBlock deletion. Added
`timeBlockId?: string` to LocalBodyWorkout.
4. Sync pull() silent-failure surfacing
Symmetric with the push() fix from the SYNC_DEBUG commit:
pull() now logs a console.warn + emits telemetry for both
the unknown-appid and no-token failure paths instead of
silently returning. Same diagnostic value as the push fix —
the SYNC_DEBUG runbook's Schritt C now surfaces pull failures
too.
5. Unit tests for contacts, chat, calendar (3 new test files)
Same fake-indexeddb + MemoryKeyProvider harness as body/nutriphi.
- contacts: create+encrypt PII, soft-delete, toggleFavorite (4)
- chat: create+encrypt title, archive, pin/unpin, delete (4)
- calendar: create with defaults, soft-delete, setAsDefault (3)
Total test count: 37 passing across 5 suites.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces a tiered access control system so apps can be released
gradually (founder → alpha → beta → public) without extra infrastructure.
Users are gated at the AuthGate level based on their tier vs the app's
requiredTier. All apps remain deployed and reachable, but only users
with sufficient tier can enter.
- Add accessTier enum + column to users schema (default: 'public')
- Add tier claim to JWT payload in better-auth config
- Add requiredTier field to ManaApp interface + all 25 apps
- Add hasAppAccess(), getAccessibleManaApps(), ACCESS_TIER_LABELS
- Update AuthGate with tier check + access denied screen
- Update getPillAppItems + Home page to filter by user tier
- Update all 22 app layouts to pass user tier to PillNav
- Add admin API: GET/PUT /api/v1/admin/users/:id/tier
- Document access tier system in CLAUDE.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>