refactor: $app/stores → $app/state, legacy stores → runes

Codemod-driven migration of 55 .svelte files from the deprecated
$app/stores module to the rune-based $app/state ($page.x → page.x,
no auto-subscription wrapper). Two custom writable() stores converted
to .svelte.ts factory functions matching the existing theme store
pattern, with consumers updated to use .value getters and the explicit
.set() method.

UserHeader.svelte's login link now guards page.url.search behind
the browser flag — search-param access throws during prerender, and
this defensive change unblocks future prerender adoption on any page
that includes the header.
This commit is contained in:
2026-04-29 22:31:16 +02:00
parent e5d218820b
commit 3cd2a678a6
62 changed files with 255 additions and 178 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "homepage",
"version": "1.51.0",
"version": "1.52.0",
"private": true,
"type": "module",
"scripts": {
+84
View File
@@ -0,0 +1,84 @@
/**
* Migrate `$app/stores` (deprecated) to `$app/state` (rune-based).
*
* For each .svelte file:
* - Rewrite `from '$app/stores'` → `from '$app/state'`
* - For each named import, drop the `$` prefix from auto-subscriptions:
* `$page.url.pathname` → `page.url.pathname`
* `$navigating` → `navigating`
* `$updated` → `updated`
* Aliased imports (`page as appPage`) are tracked, so `$appPage` becomes `appPage`.
*
* Skips:
* - Non-.svelte files (server-only code uses getRequestEvent instead).
* - Files importing other things from $app/stores that don't have a state equivalent
* (none observed in this repo).
*
* Run: pnpm exec vite-node scripts/codemod-app-stores-to-state.ts [--dry]
*/
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
import { join, extname } from 'node:path';
const SRC = 'src';
const DRY = process.argv.includes('--dry');
const STORES_IMPORT_RE =
/import\s*\{([^}]+)\}\s*from\s*['"]\$app\/stores['"]\s*;?/;
function walk(dir: string, out: string[] = []): string[] {
for (const name of readdirSync(dir)) {
const p = join(dir, name);
const s = statSync(p);
if (s.isDirectory()) walk(p, out);
else if (extname(p) === '.svelte') out.push(p);
}
return out;
}
function parseImports(inner: string): Array<{ orig: string; local: string }> {
return inner
.split(',')
.map((s) => s.trim())
.filter(Boolean)
.map((spec) => {
const m = spec.match(/^(\w+)(?:\s+as\s+(\w+))?$/);
if (!m) return null;
return { orig: m[1], local: m[2] ?? m[1] };
})
.filter((x): x is { orig: string; local: string } => x !== null);
}
function rewriteFile(src: string): { code: string; changed: boolean } {
const m = STORES_IMPORT_RE.exec(src);
if (!m) return { code: src, changed: false };
const imports = parseImports(m[1]);
if (imports.length === 0) return { code: src, changed: false };
// Replace the import path; preserve the same import shape.
let out = src.replace(STORES_IMPORT_RE, (full) =>
full.replace(/['"]\$app\/stores['"]/, "'$app/state'")
);
// Drop `$` prefix from each local name where it appears as a store
// auto-subscription (i.e. $name followed by a non-word boundary).
for (const { local } of imports) {
const re = new RegExp(`\\$${local}\\b`, 'g');
out = out.replace(re, local);
}
return { code: out, changed: out !== src };
}
const files = walk(SRC);
let changed = 0;
for (const f of files) {
const orig = readFileSync(f, 'utf8');
const { code, changed: didChange } = rewriteFile(orig);
if (!didChange) continue;
if (!DRY) writeFileSync(f, code);
changed++;
console.log(` ${f}`);
}
console.log(`\n${DRY ? '[dry] ' : ''}${changed} files migrated`);
+2 -2
View File
@@ -1,12 +1,12 @@
<script lang="ts">
import { browser } from '$app/environment';
import { enhance } from '$app/forms';
import { page } from '$app/stores';
import { page } from '$app/state';
import Heart from '@lucide/svelte/icons/heart';
let { recipeId, isFavorite = $bindable(false), isLoggedIn = false } = $props<{ recipeId: string, isFavorite?: boolean, isLoggedIn?: boolean }>();
const recipeLang = $derived($page.url.pathname.split('/')[1] || 'rezepte');
const recipeLang = $derived(page.url.pathname.split('/')[1] || 'rezepte');
let isLoading = $state(false);
+9 -9
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
import { languageStore } from '$lib/stores/language';
import { page } from '$app/state';
import { recipeTranslationStore } from '$lib/stores/recipeTranslation.svelte';
import { languageStore } from '$lib/stores/language.svelte';
import { convertFitnessPath } from '$lib/js/fitnessI18n';
import { convertCospendPath } from '$lib/js/cospendI18n';
import { onMount } from 'svelte';
@@ -10,7 +10,7 @@
let { lang = undefined }: { lang?: 'de' | 'en' | 'la' } = $props();
// Use prop for display if provided (SSR-safe), otherwise fall back to store
const displayLang = $derived(lang ?? $languageStore);
const displayLang = $derived(lang ?? languageStore.value);
let currentPath = $state('');
let langButton: HTMLButtonElement;
@@ -33,14 +33,14 @@
};
// Whether the current page is a faith route (show LA option)
const faithPath = $derived(currentPath || $page.url.pathname);
const faithPath = $derived(currentPath || page.url.pathname);
const isFaithRoute = $derived(
faithPath.startsWith('/glaube') || faithPath.startsWith('/faith') || faithPath.startsWith('/fides')
);
$effect(() => {
// Update current language and path when page changes (reactive to browser navigation)
const path = $page.url.pathname;
const path = page.url.pathname;
currentPath = path;
if (path.startsWith('/recipes') || path.startsWith('/faith')) {
@@ -87,7 +87,7 @@
// Compute target paths for each language (used as href for no-JS)
function computeTargetPath(targetLang: 'de' | 'en' | 'la'): string {
const path = currentPath || $page.url.pathname;
const path = currentPath || page.url.pathname;
if (path.startsWith('/glaube') || path.startsWith('/faith') || path.startsWith('/fides')) {
return convertFaithPath(path, targetLang);
@@ -102,7 +102,7 @@
}
// Use translated recipe slugs from page data when available (works during SSR)
const pageData = $page.data;
const pageData = page.data;
if (targetLang === 'en' && path.startsWith('/rezepte')) {
if (pageData?.englishShortName) {
return `/recipes/${pageData.englishShortName}`;
@@ -171,7 +171,7 @@
}
// If we have recipe translation data from store, use the correct short names
const recipeData = $recipeTranslationStore;
const recipeData = recipeTranslationStore.value;
if (recipeData) {
if (lang === 'en' && recipeData.englishShortName) {
await goto(`/recipes/${recipeData.englishShortName}`);
+3 -3
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { page } from '$app/stores';
import { page } from '$app/state';
import { goto } from '$app/navigation';
import ErrorView from './ErrorView.svelte';
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
@@ -18,8 +18,8 @@
let { sectionHref, sectionLabel, isEnglish: isEnglishProp, extraActions }: Props = $props();
let status = $derived($page.status);
let error = $derived($page.error as any);
let status = $derived(page.status);
let error = $derived(page.error as any);
let bibleQuote = $derived(error?.bibleQuote);
let detectedEnglish = $derived(error?.lang === 'en');
let isEnglish = $derived(isEnglishProp ?? detectedEnglish);
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { onMount } from "svelte";
import { page } from '$app/stores';
import { page } from '$app/state';
import { browser } from '$app/environment';
import LogIn from '@lucide/svelte/icons/log-in';
let { user, recipeLang = 'rezepte', lang = 'de' } = $props();
@@ -157,7 +158,7 @@
<li><a href={resolve('/[recipeLang=recipeLang]/administration', { recipeLang })}>Administration</a></li>
{/if}
<li><a href="https://sso.bocken.org/if/user/#/settings" >Einstellungen</a></li>
<li><a href={`${resolve('/logout')}?callbackUrl=${encodeURIComponent(getLogoutCallbackUrl($page.url.pathname))}`}>Log Out</a></li>
<li><a href={`${resolve('/logout')}?callbackUrl=${encodeURIComponent(getLogoutCallbackUrl(page.url.pathname))}`}>Log Out</a></li>
</ul>
</div>
</div>
@@ -165,7 +166,7 @@
{:else}
<a
class="entry login-link"
href={`${resolve('/login')}?callbackUrl=${encodeURIComponent($page.url.pathname + $page.url.search)}`}
href={`${resolve('/login')}?callbackUrl=${encodeURIComponent(page.url.pathname + (browser ? page.url.search : ''))}`}
aria-label={lang === 'de' ? 'Anmelden' : 'Login'}
title={lang === 'de' ? 'Anmelden' : 'Login'}
>
@@ -1,11 +1,11 @@
<script>
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
import ProfilePicture from './ProfilePicture.svelte';
import { formatCurrency } from '$lib/utils/formatters';
import { detectCospendLang, locale, t } from '$lib/js/cospendI18n';
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const loc = $derived(locale(lang));
/**
@@ -1,11 +1,11 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
import ProfilePicture from './ProfilePicture.svelte';
import { formatCurrency as formatCurrencyUtil } from '$lib/utils/formatters';
import { detectCospendLang, locale, t } from '$lib/js/cospendI18n';
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const loc = $derived(locale(lang));
let { initialBalance = null, initialDebtData = null } = $props<{ initialBalance?: any, initialDebtData?: any }>();
@@ -2,7 +2,7 @@
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import ProfilePicture from './ProfilePicture.svelte';
import EditButton from '$lib/components/EditButton.svelte';
import { getCategoryEmoji } from '$lib/utils/categories';
@@ -13,9 +13,9 @@
let { paymentId, onclose, onpaymentDeleted } = $props();
// Get session from page store
let session = $derived($page.data?.session);
let session = $derived(page.data?.session);
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const loc = $derived(locale(lang));
@@ -1,9 +1,9 @@
<script>
import ProfilePicture from './ProfilePicture.svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
import { detectCospendLang, t } from '$lib/js/cospendI18n';
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
let {
splitMethod = $bindable('equal'),
@@ -1,12 +1,12 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import { getEnrichedExerciseById } from '$lib/data/exercisedb';
import { detectFitnessLang, fitnessSlugs } from '$lib/js/fitnessI18n';
let { exerciseId, plain = false } = $props();
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const exercise = $derived(getEnrichedExerciseById(exerciseId, lang));
const sl = $derived(fitnessSlugs(lang));
</script>
@@ -9,10 +9,10 @@
import PersonStanding from '@lucide/svelte/icons/person-standing';
import Shapes from '@lucide/svelte/icons/shapes';
import Weight from '@lucide/svelte/icons/weight';
import { page } from '$app/stores';
import { page } from '$app/state';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const isEn = $derived(lang === 'en');
/**
+2 -2
View File
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import { browser } from '$app/environment';
import { untrack } from 'svelte';
import Heart from '@lucide/svelte/icons/heart';
@@ -50,7 +50,7 @@
initialResults = undefined,
} = $props();
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const s = $derived(fitnessSlugs(lang));
const isEn = $derived(lang === 'en');
const btnLabel = $derived(confirmLabel ?? t('log_food', lang));
@@ -1,6 +1,6 @@
<script>
import { detectFitnessLang } from '$lib/js/fitnessI18n';
import { page } from '$app/stores';
import { page } from '$app/state';
import Beef from '@lucide/svelte/icons/beef';
import Droplet from '@lucide/svelte/icons/droplet';
import Wheat from '@lucide/svelte/icons/wheat';
@@ -31,7 +31,7 @@
showDetailRows = true,
} = $props();
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const isEn = $derived(lang === 'en');
const macroPercent = $derived.by(() => {
@@ -1,5 +1,5 @@
<script>
import { page } from '$app/stores';
import { page } from '$app/state';
import { onMount } from 'svelte';
import { detectFitnessLang } from '$lib/js/fitnessI18n';
import frontSvgRaw from '$lib/assets/muscle-front.svg?raw';
@@ -13,7 +13,7 @@
/** @type {{ data?: { totals?: Record<string, MuscleTotals> } | null }} */
let { data } = $props();
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const isEn = $derived(lang === 'en');
/** @type {Record<string, MuscleTotals>} */
const totals = $derived(data?.totals ?? {});
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import { getExerciseById, getExerciseMetrics } from '$lib/data/exercises';
import Clock from '@lucide/svelte/icons/clock';
import Weight from '@lucide/svelte/icons/weight';
@@ -10,7 +10,7 @@
import Flame from '@lucide/svelte/icons/flame';
import { detectFitnessLang, fitnessSlugs } from '$lib/js/fitnessI18n';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const sl = $derived(fitnessSlugs(lang));
/**
+2 -2
View File
@@ -5,10 +5,10 @@
import Square from '@lucide/svelte/icons/square';
import { METRIC_LABELS } from '$lib/data/exercises';
import RestTimer from './RestTimer.svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
/**
* @type {{
@@ -2,10 +2,10 @@
import { getExerciseById } from '$lib/data/exercises';
import EllipsisVertical from '@lucide/svelte/icons/ellipsis-vertical';
import MapPin from '@lucide/svelte/icons/map-pin';
import { page } from '$app/stores';
import { page } from '$app/state';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
/**
* @type {{
+2 -2
View File
@@ -4,10 +4,10 @@ import Play from '@lucide/svelte/icons/play';
import Pause from '@lucide/svelte/icons/pause';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import SyncIndicator from '$lib/components/fitness/SyncIndicator.svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
let { href, elapsed = '0:00', paused = false, syncStatus = 'idle', onPauseToggle,
restSeconds = 0, restTotal = 0, onRestAdjust = null, onRestSkip = null } = $props();
@@ -1,7 +1,7 @@
<script>
import { browser } from '$app/environment';
import { enhance } from '$app/forms';
import { page } from '$app/stores';
import { page } from '$app/state';
let { item, multiplier = 1, yeastId = 0, lang = 'de' } = $props();
@@ -11,7 +11,7 @@
: 'Zwischen Frischhefe und Trockenhefe wechseln');
// Get all current URL parameters to preserve state
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : $page.url.searchParams);
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : page.url.searchParams);
/** @param {Event} event */
function toggleHefe(event) {
@@ -2,7 +2,7 @@
import { onMount } from 'svelte';
import { onNavigate } from "$app/navigation";
import { browser } from '$app/environment';
import { page } from '$app/stores';
import { page } from '$app/state';
import HefeSwapper from './HefeSwapper.svelte';
import NutritionSummary from './NutritionSummary.svelte';
import AddToFoodLogButton from './AddToFoodLogButton.svelte';
@@ -272,7 +272,7 @@ const yeastIds = $derived.by(() => {
});
// Get all current URL parameters to preserve state in multiplier forms
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : $page.url.searchParams);
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : page.url.searchParams);
// Progressive enhancement - use JS if available
onMount(() => {
+12
View File
@@ -0,0 +1,12 @@
export type Language = 'de' | 'en';
function createLanguage() {
let value = $state<Language>('de');
return {
get value() { return value; },
set: (v: Language) => { value = v; }
};
}
export const languageStore = createLanguage();
-27
View File
@@ -1,27 +0,0 @@
import { writable } from 'svelte/store';
type Language = 'de' | 'en';
function createLanguageStore() {
const { subscribe, set } = writable<Language>('de');
return {
subscribe,
set,
init: () => {
if (typeof window !== 'undefined') {
const path = window.location.pathname;
if (path.startsWith('/recipes') || path.startsWith('/faith')) {
set('en');
} else if (path.startsWith('/rezepte') || path.startsWith('/glaube')) {
set('de');
} else {
const preferredLanguage = localStorage.getItem('preferredLanguage');
set(preferredLanguage === 'en' ? 'en' : 'de');
}
}
}
};
}
export const languageStore = createLanguageStore();
@@ -0,0 +1,16 @@
export interface RecipeTranslationData {
germanShortName: string;
englishShortName?: string;
hasEnglishTranslation: boolean;
}
function createRecipeTranslation() {
let value = $state<RecipeTranslationData | null>(null);
return {
get value() { return value; },
set: (v: RecipeTranslationData | null) => { value = v; }
};
}
export const recipeTranslationStore = createRecipeTranslation();
-9
View File
@@ -1,9 +0,0 @@
import { writable } from 'svelte/store';
interface RecipeTranslationData {
germanShortName: string;
englishShortName?: string;
hasEnglishTranslation: boolean;
}
export const recipeTranslationStore = writable<RecipeTranslationData | null>(null);
+3 -3
View File
@@ -1,12 +1,12 @@
<script lang="ts">
import { page } from '$app/stores';
import { page } from '$app/state';
import { goto } from '$app/navigation';
import Header from '$lib/components/Header.svelte';
import ErrorView from '$lib/components/ErrorView.svelte';
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
let status = $derived($page.status);
let error = $derived($page.error as any);
let status = $derived(page.status);
let error = $derived(page.error as any);
let bibleQuote = $derived(error?.bibleQuote);
let isEnglish = $derived(error?.lang === 'en');
@@ -1,10 +1,10 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import SectionError from '$lib/components/SectionError.svelte';
import { detectCospendLang, cospendRoot } from '$lib/js/cospendI18n';
let lang = $derived(detectCospendLang($page.url.pathname));
let lang = $derived(detectCospendLang(page.url.pathname));
let isEnglish = $derived(lang === 'en');
</script>
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { fly } from 'svelte/transition';
@@ -17,7 +17,7 @@
let { data, children } = $props();
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const labels = $derived(cospendLabels(lang));
@@ -29,9 +29,9 @@
$effect(() => {
// Check if URL contains payment view route OR if we have paymentId in state
const match = $page.url.pathname.match(/\/(cospend|expenses)\/payments\/view\/([^\/]+)/);
const statePaymentId = $page.state?.paymentId;
const isOnDashboard = $page.route.id === '/[cospendRoot=cospendRoot]/dash';
const match = page.url.pathname.match(/\/(cospend|expenses)\/payments\/view\/([^\/]+)/);
const statePaymentId = page.state?.paymentId;
const isOnDashboard = page.route.id === '/[cospendRoot=cospendRoot]/dash';
// Only show modal if we're on the dashboard AND have a payment to show
if (isOnDashboard && (match || statePaymentId)) {
@@ -49,14 +49,14 @@
paymentId = null;
// Dispatch a custom event to trigger dashboard refresh
if ($page.route.id === '/[cospendRoot=cospendRoot]/dash') {
if (page.route.id === '/[cospendRoot=cospendRoot]/dash') {
window.dispatchEvent(new CustomEvent('dashboardRefresh'));
}
}
/** @param {string} path */
function isActive(path) {
const currentPath = $page.url.pathname;
const currentPath = page.url.pathname;
// Exact match for dash
if (path.endsWith('/dash')) {
return currentPath === path || currentPath === path + '/';
@@ -1,7 +1,7 @@
<script>
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
import { invalidateAll } from '$app/navigation';
import { pushState } from '$app/navigation';
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
@@ -17,7 +17,7 @@
import { detectCospendLang, cospendRoot, t, locale, paymentCategoryName } from '$lib/js/cospendI18n';
let { data } = $props(); // Contains session data and balance from server
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const loc = $derived(locale(lang));
@@ -34,7 +34,7 @@
import Copy from '@lucide/svelte/icons/copy';
import Check from '@lucide/svelte/icons/check';
import { page } from '$app/stores';
import { page } from '$app/state';
import { detectCospendLang, t, locale, categoryName, formatTTL as formatTTLi18n, ttlOptions } from '$lib/js/cospendI18n';
let { data } = $props();
@@ -50,7 +50,7 @@
}
});
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const loc = $derived(locale(lang));
/** @type {Record<string, { icon: typeof Plus, color: string }>} */
@@ -355,7 +355,7 @@
/** @param {{ id: string, token: string }} tok */
async function copyTokenLink(tok) {
const root = $page.url.pathname.split('/')[1];
const root = page.url.pathname.split('/')[1];
const url = new URL(`/${root}/list`, window.location.origin);
url.searchParams.set('token', tok.token);
await navigator.clipboard.writeText(url.toString());
@@ -2,7 +2,7 @@
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
import { getCategoryEmoji } from '$lib/utils/categories';
import { toast } from '$lib/js/toast.svelte';
@@ -14,7 +14,7 @@
import { formatCurrency } from '$lib/utils/formatters';
let { data } = $props();
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const loc = $derived(locale(lang));
@@ -1,7 +1,7 @@
<script>
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import { enhance } from '$app/forms';
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n, frequencyDescription } from '$lib/js/cospendI18n';
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
@@ -16,7 +16,7 @@
let { data, form } = $props();
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const loc = $derived(locale(lang));
@@ -1,7 +1,7 @@
<script>
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n } from '$lib/js/cospendI18n';
import { confirm } from '$lib/js/confirmDialog.svelte';
import FormSection from '$lib/components/FormSection.svelte';
@@ -15,7 +15,7 @@
let { data } = $props();
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const loc = $derived(locale(lang));
@@ -2,7 +2,7 @@
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
import { goto, invalidateAll } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
import { getCategoryEmoji } from '$lib/utils/categories';
import EditButton from '$lib/components/EditButton.svelte';
@@ -13,7 +13,7 @@
let { data } = $props();
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const loc = $derived(locale(lang));
@@ -8,12 +8,12 @@
import AddButton from '$lib/components/AddButton.svelte';
import { formatCurrency } from '$lib/utils/formatters';
import Toggle from '$lib/components/Toggle.svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
import { detectCospendLang, cospendRoot, t, locale, paymentCategoryName, frequencyDescription, formatNextExecutionI18n } from '$lib/js/cospendI18n';
let { data } = $props();
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const loc = $derived(locale(lang));
@@ -1,7 +1,7 @@
<script>
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n, frequencyDescription } from '$lib/js/cospendI18n';
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
import { validateCronExpression, calculateNextExecutionDate } from '$lib/utils/recurring';
@@ -13,7 +13,7 @@
let { data } = $props();
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const loc = $derived(locale(lang));
@@ -2,7 +2,7 @@
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
import { enhance } from '$app/forms';
import { page } from '$app/stores';
import { page } from '$app/state';
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
import { detectCospendLang, cospendRoot, t, locale } from '$lib/js/cospendI18n';
@@ -11,7 +11,7 @@
let { data, form } = $props();
const lang = $derived(detectCospendLang($page.url.pathname));
const lang = $derived(detectCospendLang(page.url.pathname));
const root = $derived(cospendRoot(lang));
const loc = $derived(locale(lang));
@@ -1,9 +1,9 @@
<script lang="ts">
import { resolve } from '$app/paths';
import SectionError from '$lib/components/SectionError.svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
let faithLang = $derived($page.params.faithLang!);
let faithLang = $derived(page.params.faithLang!);
let isEnglish = $derived(faithLang === 'faith');
let sectionLabel = $derived(
faithLang === 'fides'
@@ -1,7 +1,7 @@
<script>
import { asset, resolve } from '$app/paths';
import '$lib/css/christ.css';
import { page } from '$app/stores';
import { page } from '$app/state';
import Header from '$lib/components/Header.svelte'
import UserHeader from '$lib/components/UserHeader.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
@@ -39,7 +39,7 @@ const typedLang = $derived(/** @type {'de' | 'en'} */ (data.lang));
/** @param {string} path */
function isActive(path) {
const currentPath = $page.url.pathname;
const currentPath = page.url.pathname;
return currentPath.startsWith(path);
}
@@ -3,12 +3,12 @@
import { onMount } from 'svelte';
import ArrowDown from '@lucide/svelte/icons/arrow-down';
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
import { page } from '$app/stores';
import { page } from '$app/state';
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
/** @type {number | string | null} */
let expanded = $state(null);
const isGerman = $derived($page.url.pathname.startsWith('/glaube'));
const isLatin = $derived($page.url.pathname.startsWith('/fides'));
const isGerman = $derived(page.url.pathname.startsWith('/glaube'));
const isLatin = $derived(page.url.pathname.startsWith('/fides'));
/** @param {number | string} id */
function toggle(id) {
@@ -1,9 +1,9 @@
<script lang="ts">
import { resolve } from '$app/paths';
import SectionError from '$lib/components/SectionError.svelte';
import { page } from '$app/stores';
import { page } from '$app/state';
let recipeLang = $derived($page.params.recipeLang!);
let recipeLang = $derived(page.params.recipeLang!);
let isEnglish = $derived(recipeLang === 'recipes');
</script>
@@ -1,7 +1,7 @@
<script>
import { resolve } from '$app/paths';
import '$lib/css/recipe-links.css';
import { page } from '$app/stores';
import { page } from '$app/state';
import { onNavigate } from '$app/navigation';
import Header from '$lib/components/Header.svelte'
@@ -68,7 +68,7 @@ const labels = $derived({
/** @param {string} path */
function isActive(path) {
const currentPath = $page.url.pathname;
const currentPath = page.url.pathname;
// Exact match for recipe lang root
if (path === `/${data.recipeLang}`) {
return currentPath === `/${data.recipeLang}` || currentPath === `/${data.recipeLang}/`;
@@ -1,16 +1,16 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import ErrorView from '$lib/components/ErrorView.svelte';
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
let status = $derived($page.status);
let error = $derived($page.error as any);
let recipeLang = $derived($page.params.recipeLang);
let recipeName = $derived($page.params.name);
let user = $derived($page.data?.session?.user);
let status = $derived(page.status);
let error = $derived(page.error as any);
let recipeLang = $derived(page.params.recipeLang);
let recipeName = $derived(page.params.name);
let user = $derived(page.data?.session?.user);
let isEnglishRoute = $derived(recipeLang === 'recipes');
let isEnglish = $derived(error?.lang === 'en' || isEnglishRoute);
@@ -13,7 +13,7 @@
import RecipeNote from '$lib/components/recipes/RecipeNote.svelte';
import FavoriteButton from '$lib/components/FavoriteButton.svelte';
import { onDestroy } from 'svelte';
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
import { recipeTranslationStore } from '$lib/stores/recipeTranslation.svelte';
let { data } = $props<{ data: PageData }>();
@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount, tick } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
let { data } = $props();
@@ -13,7 +13,7 @@ let { data } = $props();
onMount(() => {
// Only proceed if we're actually offline or have a redirect target
// This prevents issues if someone navigates here directly while online
const targetUrl = $page.url.searchParams.get('redirect');
const targetUrl = page.url.searchParams.get('redirect');
if (!targetUrl) {
// No redirect target - just go to main recipe list
+2 -2
View File
@@ -1,10 +1,10 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import SectionError from '$lib/components/SectionError.svelte';
import { detectFitnessLang } from '$lib/js/fitnessI18n';
let lang = $derived(detectFitnessLang($page.url.pathname));
let lang = $derived(detectFitnessLang(page.url.pathname));
let isEnglish = $derived(lang === 'en');
</script>
+9 -9
View File
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import { onMount, onDestroy } from 'svelte';
import Header from '$lib/components/Header.svelte';
import UserHeader from '$lib/components/UserHeader.svelte';
@@ -23,7 +23,7 @@
const workout = getWorkout();
const sync = getWorkoutSync();
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const s = $derived(fitnessSlugs(lang));
const labels = $derived(fitnessLabels(lang));
@@ -63,22 +63,22 @@
/** @param {string} path */
function isActive(path) {
const currentPath = $page.url.pathname;
const currentPath = page.url.pathname;
return currentPath.startsWith(path);
}
const activePath = $derived(`/fitness/${s.workout}/${s.active}`);
const isOnActivePage = $derived($page.url.pathname === activePath);
const isOnActivePage = $derived(page.url.pathname === activePath);
const isNutritionPage = $derived(
$page.url.pathname.startsWith(`/fitness/${s.nutrition}`) &&
!$page.url.pathname.startsWith(`/fitness/${s.nutrition}/food`) &&
!$page.url.pathname.startsWith(`/fitness/${s.nutrition}/meals`)
page.url.pathname.startsWith(`/fitness/${s.nutrition}`) &&
!page.url.pathname.startsWith(`/fitness/${s.nutrition}/food`) &&
!page.url.pathname.startsWith(`/fitness/${s.nutrition}/meals`)
);
const isMeasureIndex = $derived(
/^\/fitness\/(check-in|erfassung)\/?$/.test($page.url.pathname)
/^\/fitness\/(check-in|erfassung)\/?$/.test(page.url.pathname)
);
const isExercisesIndex = $derived(
/^\/fitness\/(exercises|uebungen)\/?$/.test($page.url.pathname)
/^\/fitness\/(exercises|uebungen)\/?$/.test(page.url.pathname)
);
/** @param {number} secs */
function formatElapsed(secs) {
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import Pencil from '@lucide/svelte/icons/pencil';
import Trash2 from '@lucide/svelte/icons/trash-2';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
@@ -28,7 +28,7 @@
`viewBox="0 ${BP_VIEW_TOP} 660.46 ${BP_VIEW_H}"`
);
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
import { getWorkout } from '$lib/js/workout.svelte';
import PeriodTracker from '$lib/components/fitness/PeriodTracker.svelte';
@@ -1,5 +1,5 @@
<script>
import { page } from '$app/stores';
import { page } from '$app/state';
import { goto } from '$app/navigation';
import Minus from '@lucide/svelte/icons/minus';
import Plus from '@lucide/svelte/icons/plus';
@@ -23,7 +23,7 @@
let { data } = $props();
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
/** @typedef {{ key: string, labelKey: string, img: string | null, paired: boolean, tipKey: string, dbSingle?: string, dbLeft?: string, dbRight?: string }} Step */
@@ -1,5 +1,5 @@
<script>
import { page } from '$app/stores';
import { page } from '$app/state';
import { goto } from '$app/navigation';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
@@ -8,7 +8,7 @@
import SaveFab from '$lib/components/SaveFab.svelte';
import DatePicker from '$lib/components/DatePicker.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
let { data } = $props();
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import Search from '@lucide/svelte/icons/search';
import Cable from '@lucide/svelte/icons/cable';
import Cog from '@lucide/svelte/icons/cog';
@@ -16,7 +16,7 @@
import { MUSCLE_GROUPS, MUSCLE_GROUP_DE } from '$lib/data/muscleMap';
import MuscleFilter from '$lib/components/fitness/MuscleFilter.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const isEn = $derived(lang === 'en');
const sl = $derived(fitnessSlugs(lang));
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
/** @param {string | undefined | null} type @param {'en'|'de'} lang */
function exerciseTypeInfo(type, lang) {
@@ -22,7 +22,7 @@
}
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const s = $derived(fitnessSlugs(lang));
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
import MuscleMap from '$lib/components/fitness/MuscleMap.svelte';
@@ -1,12 +1,12 @@
<script>
import { resolve } from '$app/paths';
import { page as appPage } from '$app/stores';
import { page as appPage } from '$app/state';
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import SessionCard from '$lib/components/fitness/SessionCard.svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
const lang = $derived(detectFitnessLang($appPage.url.pathname));
const lang = $derived(detectFitnessLang(appPage.url.pathname));
const s = $derived(fitnessSlugs(lang));
let { data } = $props();
@@ -1,6 +1,6 @@
<script>
import { goto, invalidateAll } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import Clock from '@lucide/svelte/icons/clock';
import Weight from '@lucide/svelte/icons/weight';
import Trophy from '@lucide/svelte/icons/trophy';
@@ -20,7 +20,7 @@
import { confirm } from '$lib/js/confirmDialog.svelte';
import { toast } from '$lib/js/toast.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const sl = $derived(fitnessSlugs(lang));
import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises';
import { formatPaceRangeLabel, formatPaceValue } from '$lib/data/cardioPrRanges';
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import { goto, invalidateAll } from '$app/navigation';
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
@@ -70,7 +70,7 @@
* }} FoodSelection
*/
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const s = $derived(fitnessSlugs(lang));
const isEn = $derived(lang === 'en');
@@ -641,7 +641,7 @@
let inlineTab = $state('search'); // 'search' | 'favorites' | 'meals'
// --- FAB modal (route-based via ?add param) ---
const showFabModal = $derived($page.url.searchParams.has('add'));
const showFabModal = $derived(page.url.searchParams.has('add'));
let fabMealType = $state('lunch');
const fabHref = $derived(`${resolve('/fitness/[nutrition=fitnessNutrition]', { nutrition: s.nutrition })}?add`);
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import ExternalLink from '@lucide/svelte/icons/external-link';
import Heart from '@lucide/svelte/icons/heart';
@@ -14,7 +14,7 @@
let { data } = $props();
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const s = $derived(fitnessSlugs(lang));
const isEn = $derived(lang === 'en');
@@ -1,5 +1,5 @@
<script>
import { page } from '$app/stores';
import { page } from '$app/state';
import { untrack } from 'svelte';
import Plus from '@lucide/svelte/icons/plus';
import Trash2 from '@lucide/svelte/icons/trash-2';
@@ -14,7 +14,7 @@
/** @typedef {import('$models/CustomMeal').ICustomMeal & { _id?: string }} Meal */
/** @typedef {import('$models/CustomMeal').ICustomMealIngredient} MealIngredient */
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const s = $derived(fitnessSlugs(lang));
const isEn = $derived(lang === 'en');
@@ -1,7 +1,7 @@
<script>
import { resolve } from '$app/paths';
import { invalidateAll } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
import MuscleHeatmap from '$lib/components/fitness/MuscleHeatmap.svelte';
import Dumbbell from '@lucide/svelte/icons/dumbbell';
@@ -23,7 +23,7 @@
import StatsRingGraph from '$lib/components/fitness/StatsRingGraph.svelte';
import { BODY_PART_CARDS, bodyPartSlug, bodyPartAccent } from '$lib/js/fitnessBodyParts';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const statsSlug = $derived(lang === 'en' ? 'stats' : 'statistik');
const historySlug = $derived(lang === 'en' ? 'history' : 'verlauf');
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
import Ruler from '@lucide/svelte/icons/ruler';
import TrendingUp from '@lucide/svelte/icons/trending-up';
@@ -12,7 +12,7 @@
let { data } = $props();
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const statsSlug = $derived(lang === 'en' ? 'stats' : 'statistik');
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
const card = $derived(data.card);
@@ -1,6 +1,6 @@
<script>
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import { onMount } from 'svelte';
import Plus from '@lucide/svelte/icons/plus';
import Trash2 from '@lucide/svelte/icons/trash-2';
@@ -23,7 +23,7 @@
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const sl = $derived(fitnessSlugs(lang));
import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises';
import TemplateCard from '$lib/components/fitness/TemplateCard.svelte';
@@ -1,7 +1,7 @@
<script>
import { resolve } from '$app/paths';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import Trash2 from '@lucide/svelte/icons/trash-2';
import Play from '@lucide/svelte/icons/play';
import Pause from '@lucide/svelte/icons/pause';
@@ -25,7 +25,7 @@
import { confirm } from '$lib/js/confirmDialog.svelte';
import { toast } from '$lib/js/toast.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
const lang = $derived(detectFitnessLang(page.url.pathname));
const isEn = $derived(lang === 'en');
const sl = $derived(fitnessSlugs(lang));
import { getWorkout } from '$lib/js/workout.svelte';
+2 -2
View File
@@ -1,6 +1,6 @@
<script>
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { page } from '$app/state';
import Header from '$lib/components/Header.svelte';
import UserHeader from '$lib/components/UserHeader.svelte';
import ClipboardList from '@lucide/svelte/icons/clipboard-list';
@@ -10,7 +10,7 @@
/** @param {string} path */
function isActive(path) {
const currentPath = $page.url.pathname;
const currentPath = page.url.pathname;
if (path === '/tasks') {
return currentPath === '/tasks' || currentPath === '/tasks/';
}