feat: top-5 ROI improvements — CI gate, auth fields, body×timeblocks, sync pull, tests

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>
This commit is contained in:
Till JS 2026-04-10 18:17:32 +02:00
parent 04ce8e5d6f
commit 0f7ab60397
9 changed files with 387 additions and 9 deletions

View file

@ -74,11 +74,19 @@ export function getUserFromToken(token: string, storedEmail?: string): UserData
email = storedEmail;
}
// Name + image can live at top-level (Better Auth default) or
// inside user_metadata (legacy/custom JWT layout). Check both.
const name = payload.name || payload.user_metadata?.name || undefined;
const image = payload.image || payload.user_metadata?.image || undefined;
return {
id: payload.sub,
email: email || 'user@example.com',
role: payload.role || 'user',
tier: payload.tier || 'public',
twoFactorEnabled: payload.twoFactorEnabled ?? undefined,
name,
image,
};
} catch (error) {
console.error('Error extracting user from token:', error);

View file

@ -31,8 +31,16 @@ export interface DecodedToken {
app_id?: string;
is_b2b?: boolean | string | number;
subscription_plan_id?: string;
/** Display name from Better Auth user profile. */
name?: string;
/** Avatar URL from Better Auth user profile. */
image?: string;
/** Whether 2FA is enrolled — Better Auth sets this on the session. */
twoFactorEnabled?: boolean;
user_metadata?: {
email?: string;
name?: string;
image?: string;
};
app_settings?: {
b2b?: {