# Frontend TODO Findings from architectural review. Ordered roughly by severity. --- ## Bugs ### Backend URL hardcoded as `localhost` in the session hook **File:** `src/hooks.server.ts:20` ```ts const response = await fetch('http://localhost:8080/api/users/me', { ``` The `userGroup` handle (which runs on every request) calls the backend via a hardcoded `localhost:8080` URL. The `API_INTERNAL_URL` env var used in `handleFetch` is not applied here. Inside Docker, `localhost` from the frontend container does not resolve to the backend container — requests will fail silently (the catch swallows the error) and `event.locals.user` will be `undefined` for every request. **Fix:** Centralise the base URL: ```ts const API_BASE = env.API_INTERNAL_URL ?? 'http://localhost:8080'; // then use it in both userGroup and handleFetch ``` --- ### `import { env } from 'process'` bypasses SvelteKit's env system **File:** `src/hooks.server.ts:4` ```ts import { env } from 'process'; ``` This is a raw Node.js import that bypasses SvelteKit's `$env/dynamic/private` and `$env/static/private` modules. It won't work if the adapter is ever changed (e.g., to Deno or a serverless edge runtime), and it skips SvelteKit's build-time validation that env vars are actually set. **Fix:** ```ts import { env } from '$env/dynamic/private'; ``` --- ## Design Issues ### Every page load hits the backend twice minimum **File:** `src/hooks.server.ts:15–34` The `userGroup` hook calls `GET /api/users/me` on every single server-side request to check the session. Then each page's `+page.server.ts` load function makes its own API calls. For a search page that's three sequential backend round-trips before the user sees anything. **Fix options:** - Cache the user in the session (if Spring Session is adopted on the backend) — validate the session cookie once, not per request - If sticking with Basic Auth: trust the cookie value directly and only call `/api/users/me` when the user object is actually needed (e.g., in `+layout.server.ts`), not unconditionally in the hook --- ### Two conflicting package managers **Files:** `package-lock.json` (npm), `yarn.lock` (Yarn) Both lock files exist, meaning the project has been installed with both npm and Yarn at different times. This leads to divergent dependency trees and confusing contributor setup. **Fix:** Pick one (npm is already the default in the devcontainer), delete the other lock file, and add an `.npmrc` or `package.json` `engines` field to enforce it: ```json "engines": { "npm": ">=10" } ``` --- ### i18n is a stub — only German is implemented **Files:** `messages/en.json`, `messages/es.json` English and Spanish message files contain only the scaffolding example key `hello_world`. All actual UI strings are rendered directly in German inside Svelte components and are not extracted into the message catalogue. **Fix options:** - If multi-language support is a real requirement: extract all German strings from `.svelte` files into `messages/de.json` and provide translations in `en.json` / `es.json` - If it's not a requirement: remove Paraglide, the `messages/` directory, and the i18n hook to reduce complexity --- ### No `Dockerfile` — frontend cannot run in Docker **File:** `docker-compose.yml` (frontend service commented out) The frontend service in `docker-compose.yml` is commented out because no `Dockerfile` exists. The SvelteKit Node adapter is already configured, so producing a Dockerfile is straightforward. **Fix:** Add `frontend/Dockerfile`: ```dockerfile FROM node:24-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:24-alpine WORKDIR /app COPY --from=builder /app/build ./build COPY --from=builder /app/package.json . EXPOSE 3000 CMD ["node", "build"] ``` Then uncomment and complete the frontend service in `docker-compose.yml`, adding `API_INTERNAL_URL=http://backend:8080`. --- ### Demo routes are leftover scaffolding **Files:** `src/routes/demo/+page.svelte`, `src/routes/demo/paraglide/+page.svelte` These routes were generated by the SvelteKit + Paraglide scaffolding tool. They serve no function in the application and are publicly accessible. **Fix:** Delete `src/routes/demo/` entirely. --- ## Missing Capabilities ### No shared TypeScript types with the backend The frontend manually constructs objects and parses JSON responses without any type definitions that match the backend's DTOs (`DocumentUpdateDTO`, `GroupDTO`, etc.). A breaking change in the backend API — renaming a field, changing a type — won't be caught until runtime. **Fix options (pick one):** - **Simple:** Define matching TypeScript interfaces manually in `src/lib/types.ts` for all backend response shapes and use them in load functions - **Robust:** Add `springdoc-openapi` to the backend and use `openapi-typescript` to generate types from the OpenAPI spec as part of the build pipeline --- ### No global error page SvelteKit renders a default unstyled error page for unhandled errors and 404s. There is no `src/routes/+error.svelte` in the project. **Fix:** Create `src/routes/+error.svelte` with the application's layout and a user-friendly message. Use SvelteKit's `$page.status` and `$page.error` stores to display appropriate messages for 404 vs 500. --- ### No loading state during navigation Long-running searches or slow backend responses give the user no visual feedback. SvelteKit's `$navigating` store is available but appears unused. **Fix:** Add a global navigation progress indicator in `+layout.svelte`: ```svelte {#if $navigating}
{/if} ``` --- ### `handleFetch` redirect inside a hook can cause silent failures **File:** `src/hooks.server.ts:46` ```ts throw redirect(302, '/login'); ``` Throwing a redirect inside `handleFetch` during a server-side load that makes multiple API calls (e.g., the search page calling both `/api/documents/search` and `/api/persons`) means only the first missing-token fetch triggers the redirect — subsequent ones may behave unpredictably. **Fix:** Move the auth guard to `+layout.server.ts` as the single authoritative redirect point, and let `handleFetch` simply pass through without the token (letting the backend return 401). Handle 401 responses from the backend in load functions with an explicit `redirect(302, '/login')`.