refactor: rename nutriphi module to food (Essen)

Complete rename across the entire monorepo pre-launch:
- Module, routes, API, i18n, standalone landing app directories
- All code identifiers, display names, logo component
- German user-facing label: "Essen" (English brand stays "Food")
- Dexie table nutriFavorites -> foodFavorites
- Infra configs (docker-compose, cloudflared, nginx, wrangler)

Zero residue of nutriphi remains. No data migration needed (pre-launch).

Follow-up: run pnpm install, update Cloudflare DNS
(food.mana.how), rename Cloudflare Pages project.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-14 15:30:07 +02:00
parent f5cb833b04
commit 53b3746b98
196 changed files with 863 additions and 719 deletions

View file

@ -34,7 +34,7 @@ export enum CreditOperationType {
AI_RESEARCH_QUICK = 'ai_research_quick',
AI_RESEARCH_DEEP = 'ai_research_deep',
// NutriPhi - Food analysis
// Food - Food analysis
AI_FOOD_ANALYSIS = 'ai_food_analysis',
// Cards - AI deck generation
@ -218,7 +218,7 @@ export const OPERATION_METADATA: Record<CreditOperationType, OperationMetadata>
name: 'Analyze Food Photo',
description: 'Analyze nutrition from a food photo',
category: CreditCategory.AI,
app: 'nutriphi',
app: 'food',
},
// Deck Generation

View file

@ -66,7 +66,7 @@
news: ['Nachrichten, kuratiert für dich', 'Quelloffen & unabhängig', 'Privat by Design'],
arcade: ['Spiele direkt im Browser', 'Quelloffen & unabhängig', 'Privat by Design'],
skilltree: ['Dein Fortschritt, sichtbar', 'Quelloffen & unabhängig', 'Privat by Design'],
nutriphi: ['Ernährung bewusst leben', 'Quelloffen & unabhängig', 'Privat by Design'],
food: ['Ernährung bewusst leben', 'Quelloffen & unabhängig', 'Privat by Design'],
wisekeep: ['Wissen bewahren & teilen', 'Quelloffen & unabhängig', 'Privat by Design'],
memoro: ['Sprache wird zu Wissen', 'Quelloffen & unabhängig', 'Privat by Design'],
};
@ -99,7 +99,7 @@
news: ['News, curated for you', 'Open-source & independent', 'Private by design'],
arcade: ['Games right in your browser', 'Open-source & independent', 'Private by design'],
skilltree: ['Your progress, visualized', 'Open-source & independent', 'Private by design'],
nutriphi: ['Mindful nutrition tracking', 'Open-source & independent', 'Private by design'],
food: ['Mindful nutrition tracking', 'Open-source & independent', 'Private by design'],
wisekeep: ['Preserve & share knowledge', 'Open-source & independent', 'Private by design'],
memoro: ['Voice becomes knowledge', 'Open-source & independent', 'Private by design'],
};

View file

@ -36,8 +36,8 @@ const wisekeepSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fil
// Moodlit icon (colorful gradient circle)
const moodlitSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><rect x="103" y="103" width="819" height="819" rx="409.5" fill="white"/><g filter="url(#filter0)"><circle cx="611" cy="250" r="314" fill="#D10000"/></g><g filter="url(#filter1)"><circle cx="231" cy="393" r="314" fill="#D17A00"/></g><g filter="url(#filter2)"><circle cx="735" cy="625" r="314" fill="#FFEA00"/></g><g filter="url(#filter3)"><circle cx="409" cy="844" r="314" fill="#0033FF"/></g></g><rect x="107" y="107" width="811" height="811" rx="405.5" stroke="white" stroke-opacity="0.2" stroke-width="8"/><defs><filter id="filter0" x="-2" y="-363" width="1226" height="1226" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="149" result="effect1"/></filter><filter id="filter1" x="-382" y="-220" width="1226" height="1226" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="149" result="effect1"/></filter><filter id="filter2" x="122" y="12" width="1226" height="1226" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="149" result="effect1"/></filter><filter id="filter3" x="-204" y="231" width="1226" height="1226" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="149" result="effect1"/></filter><clipPath id="clip0"><rect x="103" y="103" width="819" height="819" rx="409.5" fill="white"/></clipPath></defs></svg>`;
// Nutriphi icon (nutrition/heart with gradient)
const nutriphiSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#nutriGrad)"/><path d="M512 760C512 760 280 600 280 420C280 340 344 280 424 280C472 280 512 308 512 308C512 308 552 280 600 280C680 280 744 340 744 420C744 600 512 760 512 760Z" fill="white"/><path d="M512 280V200" stroke="white" stroke-width="24" stroke-linecap="round"/><path d="M512 200C512 200 560 160 600 180" stroke="white" stroke-width="24" stroke-linecap="round"/><defs><linearGradient id="nutriGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#10b981"/><stop offset="1" stop-color="#059669"/></linearGradient></defs></svg>`;
// Food icon (nutrition/heart with gradient)
const foodSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#foodGrad)"/><path d="M512 760C512 760 280 600 280 420C280 340 344 280 424 280C472 280 512 308 512 308C512 308 552 280 600 280C680 280 744 340 744 420C744 600 512 760 512 760Z" fill="white"/><path d="M512 280V200" stroke="white" stroke-width="24" stroke-linecap="round"/><path d="M512 200C512 200 560 160 600 180" stroke="white" stroke-width="24" stroke-linecap="round"/><defs><linearGradient id="foodGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#10b981"/><stop offset="1" stop-color="#059669"/></linearGradient></defs></svg>`;
// Contacts icon (address book/person with gradient)
const contactsSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#contactsGrad)"/><circle cx="512" cy="380" r="100" fill="white"/><path d="M320 620C320 540 408 480 512 480C616 480 704 540 704 620V680C704 702.091 685.091 720 663 720H361C338.909 720 320 702.091 320 680V620Z" fill="white"/><rect x="240" y="300" width="24" height="80" rx="12" fill="white" fill-opacity="0.6"/><rect x="240" y="420" width="24" height="80" rx="12" fill="white" fill-opacity="0.6"/><rect x="240" y="540" width="24" height="80" rx="12" fill="white" fill-opacity="0.6"/><defs><linearGradient id="contactsGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#3b82f6"/><stop offset="1" stop-color="#2563eb"/></linearGradient></defs></svg>`;
@ -89,7 +89,7 @@ export const APP_ICONS = {
zitare: svgToDataUrl(zitareSvg),
wisekeep: svgToDataUrl(wisekeepSvg),
moodlit: svgToDataUrl(moodlitSvg),
nutriphi: svgToDataUrl(nutriphiSvg),
food: svgToDataUrl(foodSvg),
contacts: svgToDataUrl(contactsSvg),
calendar: svgToDataUrl(calendarSvg),
storage: svgToDataUrl(storageSvg),
@ -153,7 +153,7 @@ export const APP_ICONS = {
body: svgToDataUrl(
// Dumbbell + heart-pulse hybrid: training (barbell) + body (pulse line).
// Red→orange gradient to set it apart from the green health-adjacent
// modules (plants, nutriphi) and the pink cycles icon.
// modules (plants, food) and the pink cycles icon.
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><linearGradient id="bd" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#ef4444"/><stop offset="100%" style="stop-color:#f97316"/></linearGradient></defs><rect width="100" height="100" rx="22" fill="url(#bd)"/><rect x="18" y="42" width="6" height="16" rx="2" fill="white"/><rect x="76" y="42" width="6" height="16" rx="2" fill="white"/><rect x="24" y="46" width="4" height="8" rx="1" fill="white" fill-opacity="0.85"/><rect x="72" y="46" width="4" height="8" rx="1" fill="white" fill-opacity="0.85"/><rect x="28" y="48" width="44" height="4" rx="2" fill="white"/><path d="M30 70h12l4-8 6 16 4-10 6 6h12" stroke="white" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>`
),
firsts: svgToDataUrl(

View file

@ -79,9 +79,9 @@ export const APP_BRANDING: Record<AppId, AppBranding> = {
logoStroke: true,
logoStrokeWidth: 1.5,
},
nutriphi: {
id: 'nutriphi',
name: 'Nutriphi',
food: {
id: 'food',
name: 'Food',
tagline: 'AI Nutrition Tracker',
primaryColor: '#10b981',
secondaryColor: '#34d399',

View file

@ -21,7 +21,7 @@ export {
UloadLogo,
ChatLogo,
PresiLogo,
NutriPhiLogo,
FoodLogo,
ZitareLogo,
ContactsLogo,
CalendarLogo,

View file

@ -10,4 +10,4 @@
let { size = 55, color, class: className = '' }: Props = $props();
</script>
<AppLogo app="nutriphi" {size} {color} class={className} />
<AppLogo app="food" {size} {color} class={className} />

View file

@ -8,7 +8,7 @@ export { default as CardsLogo } from './CardsLogo.svelte';
export { default as UloadLogo } from './UloadLogo.svelte';
export { default as ChatLogo } from './ChatLogo.svelte';
export { default as PresiLogo } from './PresiLogo.svelte';
export { default as NutriPhiLogo } from './NutriPhiLogo.svelte';
export { default as FoodLogo } from './FoodLogo.svelte';
export { default as ZitareLogo } from './ZitareLogo.svelte';
export { default as ContactsLogo } from './ContactsLogo.svelte';
export { default as CalendarLogo } from './CalendarLogo.svelte';

View file

@ -223,8 +223,8 @@ export const MANA_APPS: ManaApp[] = [
archived: true,
},
{
id: 'nutriphi',
name: 'Nutriphi',
id: 'food',
name: 'Food',
description: {
de: 'KI Ernährungstracker',
en: 'AI Nutrition Tracker',
@ -233,7 +233,7 @@ export const MANA_APPS: ManaApp[] = [
de: 'Tracke deine Ernährung mit KI-gestützter Foto-Analyse und erhalte detaillierte Nährwertinformationen.',
en: 'Track your nutrition with AI-powered photo analysis and get detailed nutritional information.',
},
icon: APP_ICONS.nutriphi,
icon: APP_ICONS.food,
color: '#10b981',
comingSoon: false,
status: 'development',

View file

@ -8,7 +8,7 @@ export type AppId =
| 'uload'
| 'chat'
| 'presi'
| 'nutriphi'
| 'food'
| 'zitare'
| 'picture'
| 'contacts'

View file

@ -36,7 +36,7 @@ import { z } from 'zod';
// - Changing a type → BUMP (zod parse fails on old client)
//
// History:
// 1 — initial schemas (foods/totalNutrition for nutriphi,
// 1 — initial schemas (foods/totalNutrition for food,
// scientificName/commonNames/etc for plants)
export const AI_SCHEMA_VERSION = '1' as const;
@ -59,19 +59,21 @@ export interface AiResponseEnvelope<T> {
* side is stale.
*/
export class AiSchemaVersionMismatchError extends Error {
constructor(
public readonly received: string,
public readonly expected: AiSchemaVersion = AI_SCHEMA_VERSION
) {
readonly received: string;
readonly expected: AiSchemaVersion;
constructor(received: string, expected: AiSchemaVersion = AI_SCHEMA_VERSION) {
super(
`AI wire-format version mismatch: received "${received}", expected "${expected}". ` +
`The client and server are out of sync — reload the page or redeploy.`
);
this.name = 'AiSchemaVersionMismatchError';
this.received = received;
this.expected = expected;
}
}
// ─── NutriPhi: meal photo / text analysis ────────────────────────
// ─── Food: meal photo / text analysis ────────────────────────
const AnalyzedFoodSchema = z.object({
name: z.string().describe('The food item name in German'),

View file

@ -5,25 +5,25 @@
*/
// Theme types
export * from './theme';
export * from './theme.ts';
// Auth types
export * from './auth';
export * from './auth.ts';
// UI types
export * from './ui';
export * from './ui.ts';
// Common utility types
export * from './common';
export * from './common.ts';
// Contact types for cross-app integration
export * from './contact';
export * from './contact.ts';
// Landing page configuration types
export * from './landing-config';
export * from './landing-config.ts';
// AI structured-output Zod schemas (shared between mana-api + web frontend)
export * from './ai-schemas';
export * from './ai-schemas.ts';
// API types
export interface User {

View file

@ -155,7 +155,7 @@ const track = {
mana: createModuleTracker('mana'),
context: createModuleTracker('context'),
skilltree: createModuleTracker('skilltree'),
nutriphi: createModuleTracker('nutriphi'),
food: createModuleTracker('food'),
plants: createModuleTracker('plants'),
questions: createModuleTracker('questions'),
photos: createModuleTracker('photos'),
@ -375,17 +375,17 @@ export const SkillTreeEvents = {
};
/**
* NutriPhi App Events
* Food App Events
*/
export const NutriPhiEvents = {
export const FoodEvents = {
mealAdded: (mealType: string, inputType: string) =>
track.nutriphi('meal_added', { meal_type: mealType, input_type: inputType }),
mealDeleted: () => track.nutriphi('meal_deleted'),
photoAnalyzed: () => track.nutriphi('photo_analyzed'),
textAnalyzed: () => track.nutriphi('text_analyzed'),
goalsUpdated: () => track.nutriphi('goals_updated'),
favoriteSaved: () => track.nutriphi('favorite_saved'),
favoriteUsed: () => track.nutriphi('favorite_used'),
track.food('meal_added', { meal_type: mealType, input_type: inputType }),
mealDeleted: () => track.food('meal_deleted'),
photoAnalyzed: () => track.food('photo_analyzed'),
textAnalyzed: () => track.food('text_analyzed'),
goalsUpdated: () => track.food('goals_updated'),
favoriteSaved: () => track.food('favorite_saved'),
favoriteUsed: () => track.food('favorite_used'),
};
/**

View file

@ -192,7 +192,7 @@ export const MANA_APP_INDEX: Record<string, number> = {
citycorners: 14,
inventory: 15,
times: 16,
nutriphi: 17,
food: 17,
plants: 18,
questions: 19,
moodlit: 20,