The image upload broke because formData.append() was being called in the
async callback of use:enhance, which runs AFTER the form submission.
Moved the append call to the outer function which runs BEFORE submission.
Also cleaned up debug console.log statements from CardAdd.svelte.
Add console logging in the browser to track image selection and form
submission:
- Log when user selects an image file in CardAdd component
- Log file validation steps (MIME type, size)
- Log form submission and FormData preparation
- Log whether image is being appended to form
This helps diagnose if the issue is client-side (image not selected/sent)
or server-side (image not received/processed).
Changed recipe image upload behavior to only process images when the
form is submitted, rather than immediately on file selection. This
prevents orphaned image files when users abandon the form.
Changes:
- CardAdd.svelte: Preview only, store File object instead of uploading
- Created imageProcessing.ts: Shared utility for image processing
- Add/edit page clients: Use selected_image_file instead of filename
- Add/edit page servers: Process and save images during form submission
- Images are validated, hashed, and saved in multiple formats on submit
Benefits:
- No orphaned files from abandoned forms
- Faster initial file selection experience
- Server-side image processing ensures security validation
- Cleaner architecture with shared processing logic
- Add hidden input to properly serialize isBaseRecipe boolean as "true"/"false" string
- Replace plain HTML checkbox with Toggle component for consistent styling
- Checkbox values don't submit when unchecked; hidden input ensures value is always sent
Previously, when users approved or skipped translations in the recipe forms, the translation data wasn't being saved to the database. This was caused by a timing issue where the form was submitted before Svelte had updated the DOM with the hidden inputs containing the translation data.
Fixed by using tick() to wait for pending state changes to be applied before submitting the form.
Convert recipe data functions to $derived reactive variables to prevent
infinite $effect loops. Previously, calling functions inline in component
props created new objects on every reactive check, causing the
TranslationApproval component's syncBaseRecipeReferences $effect to run
continuously, resulting in the translation workflow hanging.
Refactor recipe add/edit routes from client-side fetch to proper SvelteKit
form actions with progressive enhancement and comprehensive security improvements.
**Security Enhancements:**
- Implement 5-layer image validation (file size, MIME type, extension, magic bytes, Sharp structure)
- Replace insecure base64 JSON encoding with FormData for file uploads
- Add file-type@19 dependency for magic bytes validation
- Validate actual file type via magic bytes to prevent file type spoofing
**Progressive Enhancement:**
- Forms now work without JavaScript using native browser submission
- Add use:enhance for improved client-side UX when JS is available
- Serialize complex nested data (ingredients/instructions) via JSON in hidden fields
- Translation workflow integrated via programmatic form submission
**Bug Fixes:**
- Add type="button" to all interactive buttons in CreateIngredientList and CreateStepList
to prevent premature form submission when clicking on ingredients/steps
- Fix SSR errors by using season_local state instead of get_season() DOM query
- Fix redirect handling in form actions (redirects were being caught as errors)
- Fix TranslationApproval to handle recipes without images using null-safe checks
- Add reactive effect to sync editableEnglish.images with germanData.images length
- Detect and hide 150x150 placeholder images in CardAdd component
**Features:**
- Make image uploads optional for recipe creation (use placeholder based on short_name)
- Handle three image scenarios in edit: keep existing, upload new, rename on short_name change
- Automatic image file renaming across full/thumb/placeholder directories when short_name changes
- Change detection for partial translation updates in edit mode
**Technical Changes:**
- Create imageValidation.ts utility with comprehensive file validation
- Create recipeFormHelpers.ts for data extraction, validation, and serialization
- Refactor /api/rezepte/img/add endpoint to use FormData instead of base64
- Update CardAdd component to upload via FormData immediately with proper error handling
- Use Image API for placeholder detection (avoids CORS issues with fetch)
Fixes issues where translation buttons and rosary bead counter were not working
due to incomplete Svelte 5 migration. Updated parent components to use new
callback prop syntax (onapproved/onskipped/oncancelled) and lowercase onclick
handlers to match child component expectations.
- Fix TranslationApproval event handlers in recipe add page
- Fix CounterButton onclick prop in rosary page
Add comprehensive base recipe system allowing recipes to reference other recipes dynamically. References can include custom items before/after the base recipe content and render as unified lists.
Features:
- Mark recipes as base recipes with isBaseRecipe flag
- Insert base recipe references at any position in ingredients/instructions
- Add custom items before/after referenced content (itemsBefore/itemsAfter, stepsBefore/stepsAfter)
- Combined rendering displays all items in single unified lists
- Full edit/remove functionality for additional items with modal reuse
- Empty item validation prevents accidental blank entries
- HTML rendering in section titles for proper <wbr> and ­ support
- Reference links in section headings with multiplier preservation
- Subtle hover effects (2% scale) on add buttons
- Translation support for all reference fields
- Deletion handling expands references before removing base recipes
The images field was incorrectly set as a single object instead of an array,
causing translation to fail with 'images.forEach is not a function' error.
Also added defensive Array.isArray check in translation service.
Use SvelteKit param matcher to constrain [recipeLang] to only match
'recipes' or 'rezepte', preventing it from catching /login, /logout,
and other non-recipe routes.