From f9b83990c615d1ff46676da584d5f00685e50986 Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 9 Apr 2026 17:00:13 +0200 Subject: [PATCH] refactor(mana/web): consume shared AI Zod schemas via z.infer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the hand-written MealAnalysisResult / AnalyzedFood / NutritionData interfaces in nutriphi/{api,types}.ts and the IdentifyResult interface in planta/api.ts. They are now type aliases that re-export the inferred types from @mana/shared-types — same types the backend validates against at the boundary, so frontend and backend can no longer drift. Net result is end-to-end type safety: a field rename in the shared schema lights up red in both apps/api routes and apps/mana/apps/web consumers in the same tsc pass. No more interface duplication, no more manual sync. Storage shapes (LocalMeal, LocalGoal, LocalFavorite) stay module-local because they compose the shared NutritionData / AnalyzedFood with storage-specific BaseRecord fields (id, userId, _fieldTimestamps, deletedAt, etc.) that have no place in the wire format. Tests: 29/29 nutriphi + 20/20 planta still green — the shapes are identical, only the source of the type aliases changed. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../apps/web/src/lib/modules/nutriphi/api.ts | 21 ++++------------ .../web/src/lib/modules/nutriphi/types.ts | 25 +++++++------------ .../apps/web/src/lib/modules/planta/api.ts | 15 ++++------- 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/apps/mana/apps/web/src/lib/modules/nutriphi/api.ts b/apps/mana/apps/web/src/lib/modules/nutriphi/api.ts index 99b64a778..a721bc6f5 100644 --- a/apps/mana/apps/web/src/lib/modules/nutriphi/api.ts +++ b/apps/mana/apps/web/src/lib/modules/nutriphi/api.ts @@ -9,7 +9,11 @@ import { authStore } from '$lib/stores/auth.svelte'; import { getManaApiUrl } from '$lib/api/config'; -import type { NutritionData } from './types'; +// Wire format is the single source of truth in @mana/shared-types — +// the backend validates AI responses with these same Zod schemas. +import type { MealAnalysis } from '@mana/shared-types'; + +export type MealAnalysisResult = MealAnalysis; export interface UploadMealPhotoResult { mediaId: string; @@ -18,21 +22,6 @@ export interface UploadMealPhotoResult { storagePath: string; } -export interface AnalyzedFood { - name: string; - quantity?: string; - calories?: number; -} - -export interface MealAnalysisResult { - foods?: AnalyzedFood[]; - totalNutrition?: NutritionData; - description?: string; - confidence?: number; - warnings?: string[]; - suggestions?: string[]; -} - async function authHeader(): Promise> { const token = await authStore.getAccessToken(); return token ? { Authorization: `Bearer ${token}` } : {}; diff --git a/apps/mana/apps/web/src/lib/modules/nutriphi/types.ts b/apps/mana/apps/web/src/lib/modules/nutriphi/types.ts index b06b02c9b..fcec12449 100644 --- a/apps/mana/apps/web/src/lib/modules/nutriphi/types.ts +++ b/apps/mana/apps/web/src/lib/modules/nutriphi/types.ts @@ -1,28 +1,21 @@ /** * NutriPhi module types for the unified app. + * + * NutritionData and AnalyzedFood are re-exported from @mana/shared-types + * because they double as the AI wire format (same Zod schema lives in + * packages/shared-types/src/ai-schemas.ts and is used by the backend + * generateObject() validator). Module-local types like LocalMeal compose + * those shared shapes with storage-specific BaseRecord fields. */ import type { BaseRecord } from '@mana/local-store'; +import type { NutritionData, AnalyzedFood } from '@mana/shared-types'; + +export type { NutritionData, AnalyzedFood }; export type MealType = 'breakfast' | 'lunch' | 'dinner' | 'snack'; export type InputType = 'photo' | 'text'; -export interface NutritionData { - calories: number; - protein: number; - carbohydrates: number; - fat: number; - fiber: number; - sugar: number; -} - -/** A single food item identified by Gemini Vision in a meal photo. */ -export interface AnalyzedFood { - name: string; - quantity?: string | null; - calories?: number | null; -} - export interface LocalMeal extends BaseRecord { date: string; mealType: MealType; diff --git a/apps/mana/apps/web/src/lib/modules/planta/api.ts b/apps/mana/apps/web/src/lib/modules/planta/api.ts index 10e130c34..01d1560f2 100644 --- a/apps/mana/apps/web/src/lib/modules/planta/api.ts +++ b/apps/mana/apps/web/src/lib/modules/planta/api.ts @@ -8,6 +8,11 @@ import { authStore } from '$lib/stores/auth.svelte'; import { getManaApiUrl } from '$lib/api/config'; +// Wire format is the single source of truth in @mana/shared-types — +// the backend validates AI responses with the same Zod schema. +import type { PlantIdentification } from '@mana/shared-types'; + +export type IdentifyResult = PlantIdentification; export interface UploadPhotoResult { storagePath: string; @@ -16,16 +21,6 @@ export interface UploadPhotoResult { plantId: string | null; } -export interface IdentifyResult { - scientificName?: string; - commonNames?: string[]; - confidence?: number; - healthAssessment?: string; - wateringAdvice?: string; - lightAdvice?: string; - generalTips?: string[]; -} - async function authHeader(): Promise> { const token = await authStore.getAccessToken(); return token ? { Authorization: `Bearer ${token}` } : {};