The page had 2 stacked headers (its own + MainLayout), was capped at
max-w-md which wasted desktop space, and had cheap-looking dashed amber
borders. Full rewrite with 4 clean states.
State machine: idle -> recording -> review -> processing
IDLE state:
- Brand pill 'Nouvelle recette' with Wand2 icon
- Big H1 with 'ton frigo' in warm-gradient
- Subtitle explaining the chef Antoine
- User preferences rendered as chips (vegan, allergies, max time,
cuisine) loaded from /users/profile. Each chip is a Badge with
backdrop-blur, + a 'Modifier' chip linking to /profile. Chips only
appear if the user actually set preferences.
- Giant centered mic button (112-128px) with:
- 3 concentric pulsing rings (border-2, staggered 0.6s delays)
- Gradient from-orange-500 to-amber-500
- shadow-2xl + orange glow
- ring-4 ring-white for separation from background
- Scale 1.04/0.94 on hover/tap
- Keyboard shortcut hint (Space) shown on md+
- Rotating tip card below with Lightbulb icon (4 tips cycling every 4.5s)
RECORDING state:
- Pulsing red dot + 'ENREGISTREMENT' label
- 7xl monospaced tabular timer
- Synthetic 20-bar waveform animating (each bar randomized
height/duration for organic feel)
- Large red stop button (80px) with Square icon
- Keyboard shortcut hint
REVIEW state:
- Spring-animated green checkmark badge
- H2 'Tout est bon ?' + subtitle
- Audio player card with:
- Circular play/pause button (brand gradient)
- 32-bar static waveform (deterministic sin/cos pattern)
- Trash button
- Hidden <audio> controlled via id
- File size in KB
- Two buttons: 'Recommencer' (outline) + 'Créer ma recette' (brand
gradient, ChefHat icon that rotates -8deg on hover)
PROCESSING state: (kept from previous version, slightly enlarged to
60-72 and using the new gradient halo)
Bonus:
- Space bar shortcut: starts/stops recording when not focused on input
- Remove the duplicate sticky header
- 'Mes recettes' back button + 'Préférences' link in top bar
- max-w-3xl centered layout, works from mobile to desktop
- All imports cleaned up (no more KitchenIllustration, Sheet, Card,
useMobile, cn, Info, showTips, isRecorderLoading)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ui/shimmer.tsx: reusable shimmer placeholder
- recipe-card-skeleton.tsx: skeleton grid for loading states
- CookingLoader: rebuilt as floating chef hat with orbiting sparkles
- RecipeDetailLoader: now a proper skeleton of the detail page
- PageTransition: smooth fade+lift on route change
- index.css: custom keyframes (shimmer, float, glow-pulse), thin
scrollbars, :focus-visible, safe-area utilities
- RecipeList: skeleton grid, header with count, polished tabs,
hover lift on cards, spring FAB on mobile
- Header: scroll-aware blur/shadow, animated active underline,
auto-close mobile menu on navigation
- MainLayout: ambient blurred blobs in background, warm gradient
- Home hero: gradient pill badge with wobbling Sparkles,
CTA with sliding sheen
- Login/Register buttons: brand gradient, inline spinners
- Profile: skeleton loading state instead of plain spinner
- RecipeForm streaming: glow halo behind image, blur-to-sharp reveal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- Prisma: add user preferences (dietaryPreference, allergies, maxCookingTime,
equipment, cuisinePreference, servingsDefault) + migration SQL.
Also make stripeId nullable so signup works without Stripe.
- prompts.ts: buildUserPrompt now takes a BuildPromptOptions with preferences.
Injects strong, explicit constraints in the user message (vegan rules,
allergy warnings, time limits, equipment availability, cuisine hints).
- recipe-generator.ts: new streamRecipe() async generator. Streams OpenAI
chat completion with json_schema strict mode, parses the growing buffer
to detect 'titre' and 'description' early, yields typed events:
{ type: 'delta' | 'title' | 'description' | 'complete' }
Final event includes the parsed StructuredRecipe + cost log.
- recipes.ts route: new POST /recipes/create-stream returning SSE:
event: progress { step }
event: transcription{ text }
event: title { title } <- triggers parallel image gen
event: description { description }
event: recipe { recipe }
event: image { url }
event: saved { id }
event: done
Heartbeat every 15s to prevent proxy timeouts. Image generation is
kicked off the moment the title is extracted, running in parallel with
the rest of the recipe stream. Legacy POST /recipes/create still works
and now also passes user preferences.
- users.ts route: GET /profile now returns preferences (equipment
deserialized from JSON). New PUT /users/preferences with validation
(diet enum, time 5-600, servings 1-20, equipment array -> JSON).
- ai.ts plugin: generateRecipe signature extended to accept preferences.
Frontend:
- api/recipe.ts: createRecipeStream() async generator that consumes SSE
via fetch + ReadableStream + TextDecoder (EventSource can't do POST).
Parses 'event:' and 'data:' lines, yields typed StreamEvent union.
- api/auth.ts: User interface extended with preferences; new
UserPreferences type exported.
- api/user.ts: updatePreferences() method.
- RecipeForm.tsx: handleSubmit now consumes the stream. Live UI displays:
1. Initial cooking loader with step label
2. Transcription appears as soon as it's ready
3. Title fades in the moment it's extracted (before the rest of the
recipe finishes generating)
4. Description appears right after
5. Image replaces the loader when ready
6. Small delay before navigating to the saved recipe detail page
- Profile.tsx: new 'Cuisine' tab with form for diet, allergies, max time,
servings, cuisine preference, and equipment checkboxes (8 options).
UX improvement: perceived latency is dramatically reduced. Instead of
waiting 40s staring at a spinner, the user sees the title ~3-4s in and
can start reading, while the image finishes generating in parallel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Helmet's default 'Cross-Origin-Resource-Policy: same-origin' header was
blocking the frontend (http://localhost:5173) from loading images and
audio served by the backend at /uploads/*. Set policy to 'cross-origin'
so images can be embedded in the frontend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>