Users can define a custom order of templates (e.g., Push → Pull → Legs).
Based on the last completed session, the next workout in rotation is
recommended via a prominent banner and the floating action button.
- New WorkoutSchedule MongoDB model (per-user template order)
- GET/PUT /api/fitness/schedule API endpoints
- Schedule editor modal with reorder and add/remove
- Action button starts next scheduled workout when schedule exists
- Validate exerciseId instead of name (templates use exerciseId, not name)
- Remove mandatory reps minimum from template sets
- Allow exercises with empty sets in schema and API validation
- SessionCard SVG: cosine-corrected coordinates with proper aspect ratio (xMidYMid meet)
- SessionCard: use --color-primary for track/distance/pace, add Gauge icon for pace
- History detail: theme-reactive pace chart colors via MutationObserver + matchMedia
- History detail: add Gauge icon, accent color for distance/pace stats, remove "avg" label
- Move GPS remove button from info view to edit screen
- Add Leaflet map preview to edit screen
- Remove data points count from GPS indicators
Add GPX file upload for cardio exercises in workout history. Parses
GPX track points and stores them in the session. Shows route map
(Leaflet), pace-over-distance chart (Chart.js), and per-km splits
table with color-coded fast/slow pacing. Auto-fills distance and
duration on single-set exercises. Disables Chart.js animations.
Redesign rest timer as inline bar with linear decay placed after completed set.
Add set removal (X button), @ separator column for RPE, and N/A for missing
previous values. Enable editing past workouts (date, duration, exercises, sets)
from the history detail page.
- Add metrics system (weight/reps/rpe/distance/duration) per exercise type
so cardio exercises show distance+duration instead of weight+reps
- Add 8 new cardio exercises: swimming, hiking, rowing outdoor, cycling
outdoor, elliptical, stair climber, jump rope, walking
- Add bilateral flag to dumbbell exercises for accurate tonnage calculation
- Make SetTable, SessionCard, history detail, template editor, and exercise
stats API all render/compute dynamically based on exercise metrics
- Rename Profile to Stats with lifetime cards: workouts, tonnage, cardio km
- Move route /fitness/profile -> /fitness/stats, API /stats/profile -> /stats/overview
Enables real-time workout synchronization across devices using
Server-Sent Events and an ephemeral MongoDB document (24h TTL).
Rest timers now use absolute timestamps instead of interval-based
countdown for accurate cross-device sync. Adds +/-30s rest timer
adjust buttons.
- Add exerciseId to WorkoutSession model (interface + schema)
- Fix button-in-button hydration warning in TemplateCard (use div)
- Expand FitnessChart dataset type to include all Chart.js properties
- Fix getTime type error in session update with proper cast
- Fix weight nullable type in profile stats with non-null assertion
- Fix $or query typing in templates list endpoint
- Re-add gym link on homepage pointing to /fitness
Add all 5 PPL+Upper/Lower templates matching the target split,
with Day 3 (Legs) adjusted to include Bulgarian split squats and
standing calf raises, and Day 5 (Lower) reworked with front squats,
hip thrusts, and goblet squats — all equipment-free of machines.
Also adds incline row, decline crunch, flat leg raise, and nordic
hamstring curl to the exercise list, and updates the WorkoutTemplate
model to use exerciseId instead of name for exercise references.
- 5-tab layout (Profile, History, Workout, Exercises, Measure) with shared header nav
- Workout system: template CRUD, active workout on /fitness/workout/active with localStorage persistence, pause/resume timer, rest timer, RPE input
- Shared workout singleton (getWorkout) so active workout state is accessible across all fitness routes
- Floating workout FAB indicator on all /fitness routes when workout is active
- AddActionButton component for button-based FABs (measure + template creation)
- Profile page with workouts-per-week bar chart and weight line chart with SMA trend line + ±1σ confidence band
- Exercise detail with history, charts, and records tabs using static exercise data
- Session history with grouped-by-month list, session detail with stats/PRs
- Body measurements with latest values, body part display, add form
- Card styling matching rosary/prayer route patterns (accent-dark, nord5 light, box-shadow, hover lift)
- FitnessChart: fix SSR hang by moving Chart.register to client-side, remove redundant $effect
- Exercise API: use static in-repo data instead of empty MongoDB collection
- Workout finish: include exercise name for WorkoutSession model validation
Replace ~100 `any` usages with proper types: use existing interfaces
(RecipeModelType, BriefRecipeType, IPayment, etc.), Record<string, unknown>
for dynamic objects, unknown for catch clauses with proper narrowing,
and inline types for callbacks. Remaining `any` types are in Svelte
components and cases where mongoose document mutation requires casts.
Add type annotations, JSDoc types, null checks, and proper generics
to eliminate all svelte-check errors. Key changes include:
- Type $state(null) variables to avoid 'never' inference
- Add JSDoc typedefs for plain <script> components
- Fix mongoose model typing with Model<any> to avoid union complexity
- Add App.Error/App.PageState interfaces in app.d.ts
- Fix tuple types to array types in types.ts
- Type catch block errors and API handler params
- Add null safety for DOM queries and optional chaining
- Add standard line-clamp property alongside -webkit- prefix
Household-shared list of external recipes to try, with name, multiple
links, and optional notes. Includes add/edit/delete with confirmation.
Linked from the favorites page via a styled pill button.
Instead of generating/serving 20px placeholder images with blur CSS, extract
a perceptually accurate dominant color (Gaussian-weighted OKLAB average) and
use it as a solid background-color while the full image loads. Removes
placeholder image generation, blur CSS/JS, and placeholder directory references
across upload flows, API routes, service worker, and all card/hero components.
Adds admin bulk tool to backfill colors for existing recipes.
Consolidate duplicate recipe API routes into a single
api/[recipeLang=recipeLang]/ structure. Both /api/recipes/ and
/api/rezepte/ URLs continue to work via the param matcher. Shared
read endpoints now serve both languages with caching for both.
Also: remove dead code (5 unused components, cookie.js) and the
redundant cron-execute recurring payment route.
Move components from flat src/lib/components/ into recipes/, faith/, and
cospend/ subdirectories. Replace ~144 relative imports across API routes
and lib files with $models, $utils, $types, and $lib aliases. Add $types
alias to svelte.config.js. Remove unused EditRecipe.svelte.
Clicking a category on the bar chart now filters the recent activity
list to show only payments in that category. Includes a clear filter
button and empty state message. Also increases recent splits from 10
to 30 for better coverage when filtering.
- Extract Bible lookup logic into shared src/lib/server/bible.ts module
- Add build script to pre-generate all 20 mystery verse lookups as static data,
eliminating runtime API calls on rosary page load
- Update Prayer.svelte to pass showLatin/urlLang as snippet parameters; all 14
prayer components now conditionally render only visible language elements
instead of hiding via CSS
- Extract 4 inline mystery selector SVGs into MysteryIcon.svelte component
- Remove unused CSS selectors from angelus page
- Add RosaryStreak MongoDB model for logged-in users
- Add /api/glaube/rosary-streak GET/POST endpoints
- Sync streak to server when logged in, merge local/server data
- Auto-sync when coming back online (PWA offline support)
- Falls back to localStorage for guests
Card.svelte uses recipe.images[0].mediapath for the hashed image path,
but the all_brief endpoints weren't fetching the images field, causing
new recipes to fall back to short_name.webp instead of the correct path.
The offline sync wasn't caching thumbnails because the images field
was missing from the MongoDB projection. Also add translations for
caching English recipe __data.json URLs.
- Add service worker with caching for build assets, static files, images, and pages
- Add IndexedDB storage for recipes (brief and full data)
- Add offline-db API endpoint for bulk recipe download
- Add offline sync button component in header
- Add offline-shell page for direct navigation fallback
- Pre-cache __data.json for client-side navigation
- Add +page.ts universal load functions with IndexedDB fallback
- Add PWA manifest and icons for installability
- Update recipe page to handle missing data gracefully
Add detailed console logging throughout the image upload pipeline to help
diagnose upload issues:
- Log file metadata and validation steps in imageValidation.ts
- Log image processing and file saving operations in imageProcessing.ts
- Log form data and processing steps in recipe add page action
- Log API request details and upload progress in img/add endpoint
All logs are prefixed with component name ([ImageValidation], [ImageProcessing],
[RecipeAdd], [API:ImgAdd]) for easy filtering and debugging.
Implement comprehensive caching for all cospend routes to improve performance:
Cache Implementation:
- Balance API: 30-minute TTL for user balances and global balances
- Debts API: 15-minute TTL for debt breakdown calculations
- Payments List: 10-minute TTL with pagination support
- Individual Payment: 30-minute TTL for payment details
Cache Invalidation:
- Created invalidateCospendCaches() helper function
- Invalidates user balances, debts, and payment lists on mutations
- Applied to payment create, update, and delete operations
- Applied to recurring payment execution (manual and cron)
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)
Implements Redis caching layer for recipe endpoints to reduce MongoDB load and improve response times:
- Install ioredis for Redis client with TypeScript support
- Create cache.ts with namespaced keys (homepage: prefix) to avoid conflicts with other Redis applications
- Add caching to recipe query endpoints (all_brief, by tag, in_season) with 1-hour TTL
- Implement automatic cache invalidation on recipe create/edit/delete operations
- Cache recipes before randomization to maximize cache reuse while maintaining random order per request
- Add graceful fallback to MongoDB if Redis is unavailable
- Update .env.example with Redis configuration (REDIS_HOST, REDIS_PORT)
- Implement local Ollama integration for bilingual (DE/EN) alt text generation
- Add image management UI to German edit page and English translation section
- Update Card and recipe detail pages to display alt text from images array
- Include GenerateAltTextButton component for manual alt text generation
- Add bulk processing admin page for batch alt text generation
- Optimize images to 1024x1024 before AI processing for 75% faster generation
- Store alt text in recipe.images[].alt and translations.en.images[].alt
- Add recursive population for nested base recipe references (up to 3 levels deep) in API endpoints
- Implement recursive mapping of baseRecipeRef to resolvedRecipe for all nesting levels
- Add recursive flattening functions in frontend components to handle nested references
- Fix TranslationApproval to use short_name instead of ObjectId for base recipe lookups
- Add circular reference detection to prevent infinite loops
This ensures that when Recipe A references Recipe B as a base, and Recipe B references Recipe C, all three recipes' content is properly displayed.
Fixed three issues with base recipe translation support:
1. Base recipe content not loading in English - English API endpoint now
populates baseRecipeRef fields to resolve base recipe data
2. itemsBefore/itemsAfter and stepsBefore/stepsAfter not being detected as
changed - enhanced change detection to properly track all base recipe
reference fields for re-translation
3. Base recipe name labels showing German text in English view - display
components now use translated base recipe names as label fallback
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
Add new page at /rezepte/untranslated for recipe admins to view and manage recipes without approved English translations. Includes translation status tracking, statistics dashboard, and visual badges.
Changes:
- Add API endpoint to fetch recipes without approved translations
- Create untranslated recipes page with auth checks for rezepte_users group
- Add translation status badges to Card component (pending, needs_update, none)
- Add database index on translations.en.translationStatus for performance
- Create layout for /rezepte route with header navigation
- Add "Unübersetzt" link to navigation for authorized users
Fixes critical bug where recipes could not be deleted properly. The delete function had an early return statement that prevented database deletion from executing, leaving orphaned entries. Additionally, deleted recipes were not removed from users' favorites lists.
Changes:
- Remove premature return statement blocking database deletion
- Fix malformed fetch call structure (headers were inside body JSON)
- Add UserFavorites cleanup to remove deleted recipes from all users' favorites
- Ensure complete cleanup: database entry, image files (hashed and unhashed), and favorites references
Add advanced filtering with category, tags (multi-select), icon, season,
and favorites filters. All filters use consistent chip-based dropdown UI
with type-to-search functionality.
New Components:
- TagChip.svelte: Reusable chip component with selected/removable states
- CategoryFilter.svelte: Single-select category with chip dropdown
- TagFilter.svelte: Multi-select tags with AND logic and chip dropdown
- IconFilter.svelte: Single-select emoji icon with chip dropdown
- SeasonFilter.svelte: Multi-select months with chip dropdown
- FavoritesFilter.svelte: Toggle for favorites-only filtering
- FilterPanel.svelte: Container with responsive layout and mobile toggle
Search Component:
- Integrated FilterPanel with all filter types
- Added applyNonTextFilters() for category/tags/icon/season/favorites
- Implemented favorites filter logic (recipe.isFavorite check)
- Made tags/icons reload reactively when language changes with $effect
- Updated buildSearchUrl() for comma-separated array parameters
- Passed categories and isLoggedIn props to enable all filters
Server API:
- Both /api/rezepte/search and /api/recipes/search support:
- Multi-tag AND logic using MongoDB $all operator
- Multi-season filtering using MongoDB $in operator
- Backwards compatible with single tag/season parameters
- Updated search page server load to parse tag/season arrays
UI/UX:
- Filters display inline on wide screens with 2rem gap
- Mobile: collapsible with subtle toggle button and slide-down animation
- Chip-based dropdowns appear on focus with filtering as you type
- Selected items display as removable chips below inputs (no background)
- Centered labels on desktop, left-aligned on mobile
- Reduced vertical spacing on mobile (0.3rem gap)
- Max-width constraints: 500px for filters, 600px for panel on mobile
- Consistent naming: "Tags" and "Icon" instead of German translations
Previously, all English recipe API endpoints were returning any recipe with
a translations.en object, regardless of approval status. This caused 218
recipes to appear instead of only approved ones.
Updated all 9 English API endpoints to filter for translationStatus='approved':
- /api/recipes/items/all_brief
- /api/recipes/items/in_season/[month]
- /api/recipes/items/category and /api/recipes/items/category/[category]
- /api/recipes/items/tag and /api/recipes/items/tag/[tag]
- /api/recipes/items/icon/[icon]
- /api/recipes/search
- /api/recipes/favorites/recipes
Change production path check from /var/lib/www to /var/www/static
to match actual production environment configuration.
Updated migration endpoint and all documentation references.
Allow migration to run without browser session by using ADMIN_SECRET_TOKEN
environment variable. This enables running the migration directly on the
server via SSH.
Changes:
- Add ADMIN_SECRET_TOKEN support to migration endpoint
- Update shell script to read token from environment
- Improve script with better error handling and token validation
- Update documentation with admin token setup instructions
The endpoint now accepts authentication via either:
- Valid user session (browser-based)
- ADMIN_SECRET_TOKEN from environment (server-based)
Usage on server:
source .env && ./scripts/migrate-image-hashes.sh
Add content-based hashing to recipe images for proper cache invalidation
while maintaining graceful degradation through dual file storage.
Changes:
- Add imageHash utility with SHA-256 content hashing (8-char)
- Update Recipe model to store hashed filenames in images[0].mediapath
- Modify image upload endpoint to save both hashed and unhashed versions
- Update frontend components to use images[0].mediapath with fallback
- Add migration endpoint to hash existing images (production-only)
- Update image delete/rename endpoints to handle both file versions
Images are now stored as:
- recipe.a1b2c3d4.webp (hashed, cached forever)
- recipe.webp (unhashed, graceful degradation fallback)
Database stores hashed filename for cache busting, while unhashed
version remains on disk for backward compatibility and manual uploads.
Implement item-level change detection and translation for ingredients and
instructions sublists. Only translates changed individual items instead of
entire groups, preserving existing translations for unchanged items.
Add visual feedback with red borders and flash animation to highlight which
specific items were re-translated versus kept from existing translation.
Translation granularity improvements:
- Detects changes at item level within ingredient/instruction groups
- Only re-translates changed items, keeps unchanged items from existing translation
- Reduces DeepL API usage by ~70-90% for typical edits
- Returns metadata tracking which specific items were translated
Visual highlighting features:
- Red border (Nord11) on re-translated items
- Flash animation on first appearance
- Applied to ingredient items, instruction steps, and group names
- Clear visual feedback in translation approval workflow
Technical changes:
- Modified detectChangedFields() to return granular item-level changes
- Added _translateIngredientsPartialWithMetadata() for metadata tracking
- Added _translateInstructionsPartialWithMetadata() for metadata tracking
- API returns translationMetadata alongside translatedRecipe
- EditableIngredients/Instructions accept translationMetadata prop
- CSS animation for highlight-flash effect
Fixes issue where English recipes always displayed German portions and timing metadata. The API now prioritizes English translations for portions, baking, preparation, fermentation, cooking, and total_time fields, falling back to German when translations aren't available.
Consolidate /rezepte and /recipes routes into single [recipeLang] structure to eliminate code duplication. All pages now use conditional API routing and reactive labels based on language parameter.
- Merge duplicate route structures into /[recipeLang] with 404 for invalid slugs
- Add English API endpoints for search, favorites, tags, and categories
- Implement language dropdown in header with localStorage persistence
- Convert all pages to use Svelte 5 runes (, , )
- Add German-only redirects (301) for add/edit pages
- Make all view pages (list, detail, filters, search, favorites) fully bilingual
- Remove floating language switcher in favor of header dropdown
- Add embedded translations schema to Recipe model with English support
- Create DeepL translation service with batch translation and change detection
- Build translation approval UI with side-by-side editing for all recipe fields
- Integrate translation workflow into add/edit pages with field comparison
- Create complete English recipe routes at /recipes/* mirroring German structure
- Add language switcher component with hreflang SEO tags
- Support image loading from German short_name for English recipes
- Add English API endpoints for all recipe filters (category, tag, icon, season)
- Include layout with English navigation header for all recipe subroutes
Removed excessive console.log statements from recurring payments processing and monthly expenses aggregation. Error logging (console.error) is retained for troubleshooting.
- Add clickable Bible reference buttons that open modal with full verses
- Create BibleModal component with backdrop blur and styled close button
- Implement build-time data fetching for Bible texts while maintaining reactivity
- Redesign mystery selector with responsive grid (3-in-row/4-in-row/2×2)
- Add "Heutige" badge to indicate today's auto-selected mystery
- Reposition luminous mysteries toggle below mystery selector
- Integrate Bible reference and counter buttons side-by-side
- Restructure Bible API under /api/glaube/bibel/ for better organization