pwa: fix offline caching for prayer/faith routes
All checks were successful
CI / update (push) Successful in 1m53s

The glob in sync.ts targeted a nonexistent /src/routes/glaube/ directory
instead of the actual [faithLang=faithLang] parameterized route. This meant
zero prayer pages were ever precached for offline use.

- Fix glob to match [faithLang=faithLang] and expand param segments to
  both language variants (glaube/faith, gebete/prayers, rosenkranz/rosary)
- Extract validPrayerSlugs to shared module for build-time route enumeration
- Add faith to service worker cacheable route regex
This commit is contained in:
2026-03-09 17:49:05 +01:00
parent 75401784ba
commit eafa2caa27
4 changed files with 79 additions and 39 deletions

View File

@@ -0,0 +1,24 @@
// Valid prayer slugs (both languages) — single source of truth
// Used by the [prayer] route for validation and by sync.ts for offline precaching
export const validPrayerSlugs = new Set([
'das-heilige-kreuzzeichen', 'the-sign-of-the-cross',
'gloria-patri',
'paternoster', 'our-father',
'credo', 'nicene-creed',
'ave-maria', 'hail-mary',
'salve-regina',
'das-fatimagebet', 'fatima-prayer',
'gloria',
'gebet-zum-hl-erzengel-michael', 'prayer-to-st-michael-the-archangel',
'bruder-klaus-gebet', 'prayer-of-st-nicholas-of-flue',
'josephgebet-des-hl-papst-pius-x', 'prayer-to-st-joseph-by-pope-st-pius-x',
'das-confiteor', 'the-confiteor',
'postcommunio',
'anima-christi',
'prayer-before-a-crucifix', 'gebet-vor-einem-kruzifix',
'schutzengel-gebet', 'guardian-angel-prayer',
'apostolisches-glaubensbekenntnis', 'apostles-creed',
'tantum-ergo',
'angelus',
'regina-caeli',
]);

View File

@@ -1,16 +1,55 @@
import { saveAllRecipes } from './db';
import type { BriefRecipeType, RecipeModelType } from '$types/types';
import { validPrayerSlugs } from '$lib/data/prayerSlugs';
// Discover glaube routes at build time using Vite's glob import
const glaubePageModules = import.meta.glob('/src/routes/glaube/**/+page.svelte');
const glaubeRoutes = Object.keys(glaubePageModules).map(path => {
// Convert file path to route path
// /src/routes/glaube/+page.svelte -> /glaube
// /src/routes/glaube/angelus/+page.svelte -> /glaube/angelus
return path
.replace('/src/routes', '')
.replace('/+page.svelte', '') || '/glaube';
});
// Discover faith routes at build time using Vite's glob import
// The actual directory is [faithLang=faithLang] with parameterized sub-dirs
const faithPageModules = import.meta.glob('/src/routes/\\[faithLang=faithLang\\]/**/+page.svelte');
// Convert file paths to actual route URLs for both language variants
// e.g. /src/routes/[faithLang=faithLang]/[prayers=prayersLang]/+page.svelte
// -> /glaube/gebete, /faith/prayers
const paramMap: Record<string, [string, string]> = {
'[faithLang=faithLang]': ['glaube', 'faith'],
'[prayers=prayersLang]': ['gebete', 'prayers'],
'[rosary=rosaryLang]': ['rosenkranz', 'rosary'],
};
function expandFaithRoutes(): string[] {
const routes: string[] = [];
for (const filePath of Object.keys(faithPageModules)) {
// Strip prefix and suffix: /src/routes/[faithLang=faithLang]/angelus/+page.svelte -> [faithLang=faithLang]/angelus
let route = filePath.replace('/src/routes/', '').replace('/+page.svelte', '');
// Skip routes with dynamic [prayer] segment — those need explicit slug enumeration
if (route.includes('[prayer]')) continue;
// Generate both language variants by replacing all param segments
const segments = route.split('/');
const deSegments: string[] = [];
const enSegments: string[] = [];
for (const seg of segments) {
if (paramMap[seg]) {
deSegments.push(paramMap[seg][0]);
enSegments.push(paramMap[seg][1]);
} else {
deSegments.push(seg);
enSegments.push(seg);
}
}
routes.push('/' + deSegments.join('/'));
routes.push('/' + enSegments.join('/'));
}
return routes;
}
const faithRoutes = expandFaithRoutes();
// Add individual prayer pages (dynamic [prayer] slug, resolved at build time)
for (const slug of validPrayerSlugs) {
faithRoutes.push(`/glaube/gebete/${slug}`);
faithRoutes.push(`/faith/prayers/${slug}`);
}
export type SyncProgress = {
phase: 'recipes' | 'pages' | 'data' | 'images';
@@ -133,8 +172,8 @@ async function precacheMainPages(_fetchFn: typeof fetch): Promise<void> {
'/recipes/favorites/__data.json'
];
// Add dynamically discovered glaube routes (HTML and __data.json)
for (const route of glaubeRoutes) {
// Add dynamically discovered faith routes (HTML and __data.json)
for (const route of faithRoutes) {
pagesToCache.push(route);
pagesToCache.push(`${route}/__data.json`);
}

View File

@@ -1,32 +1,9 @@
import type { PageServerLoad } from "./$types";
import { error } from "@sveltejs/kit";
// Valid prayer slugs (both languages)
const validSlugs = new Set([
'das-heilige-kreuzzeichen', 'the-sign-of-the-cross',
'gloria-patri',
'paternoster', 'our-father',
'credo', 'nicene-creed',
'ave-maria', 'hail-mary',
'salve-regina',
'das-fatimagebet', 'fatima-prayer',
'gloria',
'gebet-zum-hl-erzengel-michael', 'prayer-to-st-michael-the-archangel',
'bruder-klaus-gebet', 'prayer-of-st-nicholas-of-flue',
'josephgebet-des-hl-papst-pius-x', 'prayer-to-st-joseph-by-pope-st-pius-x',
'das-confiteor', 'the-confiteor',
'postcommunio',
'anima-christi',
'prayer-before-a-crucifix', 'gebet-vor-einem-kruzifix',
'schutzengel-gebet', 'guardian-angel-prayer',
'apostolisches-glaubensbekenntnis', 'apostles-creed',
'tantum-ergo',
'angelus',
'regina-caeli',
]);
import { validPrayerSlugs } from '$lib/data/prayerSlugs';
export const load: PageServerLoad = async ({ params, url }) => {
if (!validSlugs.has(params.prayer)) {
if (!validPrayerSlugs.has(params.prayer)) {
throw error(404, 'Prayer not found');
}

View File

@@ -73,7 +73,7 @@ sw.addEventListener('fetch', (event) => {
// Handle SvelteKit __data.json requests for cacheable routes (recipes, glaube, root)
// Cache successful responses, serve from cache when offline
const isCacheableDataRoute = url.pathname.includes('__data.json') &&
(url.pathname.match(/^\/(rezepte|recipes|glaube)(\/|$)/) || url.pathname === '/__data.json');
(url.pathname.match(/^\/(rezepte|recipes|glaube|faith)(\/|$)/) || url.pathname === '/__data.json');
if (isCacheableDataRoute) {
event.respondWith(
@@ -193,7 +193,7 @@ sw.addEventListener('fetch', (event) => {
// Cache successful HTML responses for cacheable pages (using pathname as key)
const isCacheablePage = response.ok && (
url.pathname.match(/^\/(rezepte|recipes|glaube)(\/|$)/) ||
url.pathname.match(/^\/(rezepte|recipes|glaube|faith)(\/|$)/) ||
url.pathname === '/'
);
if (isCacheablePage) {