diff --git a/apps/mana/apps/web/src/lib/modules/news/api.ts b/apps/mana/apps/web/src/lib/modules/news/api.ts index 72508e2e2..a2f77b7e0 100644 --- a/apps/mana/apps/web/src/lib/modules/news/api.ts +++ b/apps/mana/apps/web/src/lib/modules/news/api.ts @@ -5,17 +5,20 @@ * - GET /feed — pulls the curated pool, with topic/lang filters * - POST /extract/* — Mozilla Readability for ad-hoc URL saves * - * The base URL is read from `PUBLIC_MANA_API_URL` if set (production - * docker setup), otherwise falls back to localhost dev. Auth is the - * unified Mana JWT, picked up by the same fetch wrapper the rest of - * the app uses (cookie + Authorization header set by SvelteKit `fetch` - * via the auth-provider middleware). + * The base URL comes from `getManaApiUrl()`, which on the client reads the + * browser-injected `__PUBLIC_MANA_API_URL__` (set from + * `PUBLIC_MANA_API_URL_CLIENT` in hooks.server.ts → e.g. + * `https://mana-api.mana.how`) and on the server reads `process.env` + * directly. Reading `$env/dynamic/public.PUBLIC_MANA_API_URL` here would + * leak the SSR-side internal Docker hostname (`http://mana-api:3060`) to + * the browser and trip CSP / DNS. + * + * Auth is the unified Mana JWT, picked up by the same fetch wrapper the + * rest of the app uses (cookie + Authorization header set by SvelteKit + * `fetch` via the auth-provider middleware). */ -import { env as publicEnv } from '$env/dynamic/public'; - -const API_BASE = - publicEnv.PUBLIC_MANA_API_URL || (typeof window !== 'undefined' ? '' : 'http://localhost:3060'); +import { getManaApiUrl } from '$lib/api/config'; export interface FeedArticleDto { id: string; @@ -57,7 +60,7 @@ export async function fetchFeed( if (query.limit != null) params.set('limit', String(query.limit)); if (query.offset != null) params.set('offset', String(query.offset)); - const url = `${API_BASE}/api/v1/news/feed${params.toString() ? `?${params}` : ''}`; + const url = `${getManaApiUrl()}/api/v1/news/feed${params.toString() ? `?${params}` : ''}`; const response = await fetchImpl(url, { credentials: 'include' }); if (!response.ok) { throw new Error(`fetchFeed failed: ${response.status}`); @@ -87,7 +90,7 @@ export async function extractFromUrl( url: string, fetchImpl: typeof fetch = fetch ): Promise { - const response = await fetchImpl(`${API_BASE}/api/v1/news/extract/save`, { + const response = await fetchImpl(`${getManaApiUrl()}/api/v1/news/extract/save`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, diff --git a/apps/mana/apps/web/src/lib/modules/news/stores/preferences.svelte.ts b/apps/mana/apps/web/src/lib/modules/news/stores/preferences.svelte.ts index 6bd952e2a..83d4f1f96 100644 --- a/apps/mana/apps/web/src/lib/modules/news/stores/preferences.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/news/stores/preferences.svelte.ts @@ -33,10 +33,13 @@ export const preferencesStore = { blockedSources?: string[]; }): Promise { await ensureRow(); + // Spread the input arrays — callers in onboarding pass Svelte 5 + // `$state` proxy arrays, which IndexedDB cannot structured-clone + // (DataCloneError on the Dexie hook's _pendingChanges write). const diff: Partial = { - selectedTopics: input.topics, - preferredLanguages: input.languages, - blockedSources: input.blockedSources ?? [], + selectedTopics: [...input.topics], + preferredLanguages: [...input.languages], + blockedSources: [...(input.blockedSources ?? [])], onboardingCompleted: true, updatedAt: new Date().toISOString(), }; @@ -47,7 +50,7 @@ export const preferencesStore = { async setTopics(topics: Topic[]): Promise { await ensureRow(); const diff: Partial = { - selectedTopics: topics, + selectedTopics: [...topics], updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', diff); @@ -57,7 +60,7 @@ export const preferencesStore = { async setLanguages(languages: Language[]): Promise { await ensureRow(); const diff: Partial = { - preferredLanguages: languages, + preferredLanguages: [...languages], updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', diff); diff --git a/apps/mana/apps/web/src/routes/(app)/news/+page.svelte b/apps/mana/apps/web/src/routes/(app)/news/+page.svelte index 82ca73885..eed0da150 100644 --- a/apps/mana/apps/web/src/routes/(app)/news/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/news/+page.svelte @@ -57,10 +57,13 @@ } async function finishOnboarding() { + // $state.snapshot strips the Svelte 5 reactive proxies — without it + // the arrays travel into Dexie hooks as proxies and trip + // DataCloneError on the structured-clone into _pendingChanges. await preferencesStore.completeOnboarding({ - topics: pickedTopics, - languages: pickedLanguages, - blockedSources: pickedBlocked, + topics: $state.snapshot(pickedTopics) as Topic[], + languages: $state.snapshot(pickedLanguages) as Language[], + blockedSources: $state.snapshot(pickedBlocked) as string[], }); // The +layout effect will pick up the new prefs and refresh. }