# Junior Developer — MVP Happy-Path TODO **Owner:** Junior developer **Status:** Ready to implement **Goal:** Make the core happy path work end-to-end: **register → land logged-in on the overview → log in → land on the overview → register a child → see the overview → fetch the child's data from Försäkringskassan.** **Date:** 2026-06-12 > **Context:** The backend is in good shape and the FK scraper is now implemented (Playwright, real captured selectors). The breakages are almost all in **frontend wiring**. FK scraping is now legally cleared, so the overview's day numbers should come from the scraper. > > **Do NOT undo or rewrite** the scraper (`backend/app/scrapers/fk_scraper.py`), its captured selectors, or the `FetchDataService` contract. Reuse what exists. > > Work top-to-bottom; tasks are ordered so each unblocks the next. Run `npx tsc --noEmit` and `npm run lint` after each task — keep them green. --- ## J1 — Redirect after login & register *(the #1 visible bug)* **Problem:** `pages/Auth.tsx` calls `login()` / `register()`, which *do* authenticate you (AuthContext sets `isAuthenticated: true`), but the page never navigates, and nothing bounces an already-authenticated user off `/auth`. So you're silently logged in but stuck on the form — this is why "after registering I'm not logged in" and "login doesn't take me anywhere." **Do:** - In `pages/Auth.tsx`, after a successful `login`/`register`, navigate to `/` (use `useNavigate()` from `react-router-dom`, `navigate('/', { replace: true })`). - Also guard `/auth` itself: if `isAuthenticated` is already true, redirect to `/`. Either inside `Auth.tsx` (early ``) or in `App.tsx`'s `/auth` route. This handles the "I manually went to /auth while logged in" case cleanly. **Acceptance:** - Registering a brand-new account lands you on the dashboard, logged in, with no manual navigation. - Logging in lands you on the dashboard. - Visiting `/auth` while logged in redirects to `/`. --- ## J2 — Bootstrap the workspace after auth *(why the dashboard is empty)* **Problem:** `context/WorkspaceContext.tsx` only reads `workspaceId` from `localStorage`, which **nothing ever sets**. So after login there is no workspace → the dashboard shows defaults. The backend already exposes everything you need at **`GET /api/auth/context`** (`api.getUserContext()`), which returns `{ personal_data_uuid, workspace_id, children[] }`. **Do:** - When the user becomes authenticated, call `api.getUserContext()` and use its `workspace_id` to drive `WorkspaceContext` (set it via the existing `setWorkspaceId`, and keep persisting to `localStorage` is fine as a cache — just don't *depend* on it being pre-populated). - Expose the **children** and a **`selectedChildId`** from context (or a small `ChildContext`). For MVP, **auto-select the first child** if any exist. Add a `selectChild(id)` setter for later. - Also expose `personal_data_uuid` (the overview + FK fetch need it). - Re-fetch context after creating a child (see J4) so the new child appears and gets selected. **Acceptance:** - After logging in (fresh browser, empty localStorage), the dashboard loads the user's workspace automatically. - If the workspace has a child, it's auto-selected and the overview renders for it. *(Senior will review this context seam — keep the change small and readable.)* --- ## J3 — Fix the broken API client paths *(several calls 404 today)* **Problem:** `services/api.ts` invents `/workspaces/{id}/…` REST paths that **do not exist** on the backend. Confirmed mismatches: | `api.ts` call (wrong) | Real backend route | |---|---| | `GET /workspaces/{id}/break-periods` | prefix is `/api/break-periods` (see `routers/break_periods.py` + `routers/settings.py`) | | `POST /workspaces/{id}/break-periods` | `/api/break-periods` | | `GET /workspaces/{id}/tips` | `/api/tips/{workspace_id}` (prefix `/api/tips`, see `routers/settings.py`) | | `GET /workspaces/{id}/planned-days` | prefix `/api/planned-days` (see `routers/planned_days.py`) | | `POST /workspaces/{id}/planned-days` | `/api/planned-days` | | `GET /workspaces/{id}/parental-leave` | `/api/parental-leave/{personal_data_uuid}/{child_id}` — **different shape; see note** | **Do:** - Open `backend/app/routers/*.py`, read the **actual** `@router` decorators under each prefix, and correct each path + its params/body in `api.ts`. The analytics overview path (`/analytics/{ws}/overview?child_id=…`) is already correct — use it as your reference for "right". - For **parental-leave**, the backend route needs `personal_data_uuid` + `child_id`, not a workspace id. **Don't guess** — the senior is adding/clarifying a workspace-friendly read route (Senior task S2). Until that lands, wire `getParentalLeave` to the contract the senior specifies (likely `/api/parental-leave/{personal_data_uuid}/{child_id}` using the IDs from context). Coordinate; don't invent a path. **Acceptance:** - No 404s in the network tab when the dashboard loads. - `tsc`/lint green; existing hook tests updated to the corrected paths and passing. --- ## J4 — Child registration on the overview *(empty-state happy path)* **Problem:** There's no way to add a child from the overview. The pieces exist: `api.createChild()` → `POST /api/children`, and the `ChildBirthdayInput` component. **Do:** - On the Dashboard, when the selected workspace has **no children**, render an empty state with an **"Lägg till barn"** form (reuse `ChildBirthdayInput`; collect name + birth date). - On submit: call `api.createChild({ workspace_id, name, birth_date })`, then re-fetch context (J2) so the child appears and is auto-selected, and the overview re-renders for it. - Show a clear success/error message; disable the submit button while in flight. - Keep it simple — one child is enough for MVP. A child switcher can come later. **Acceptance:** - A freshly registered user sees "no child yet → add one", can add a child, and immediately sees the overview populate for that child. --- ## J5 — "Hämta från Försäkringskassan" on the overview **Problem:** The FK fetch flow currently only lives in the dev harness (`pages/DevFetch.tsx`). The real overview should let the user pull their data for the selected child. **Do:** - Extract the harness's session logic (start → poll every ~1s → render QR `data:image/png;base64,…` → show result/error → cancel) out of `DevFetch.tsx` into a **reusable hook** (e.g. `useScraperFetch`) and a small **`QRCodeModal`** component. `DevFetch` should then use these too (don't duplicate). - On the Dashboard, add a **"Hämta från Försäkringskassan"** button (enabled only when a child is selected). Clicking it opens the `QRCodeModal`, starts a session (the backend auto-resolves IDs from the logged-in user — send an empty body, or pass the selected `child_id`), shows the live QR, and on `done` closes the modal and **refreshes the overview** (re-fetch parental-leave for the selected child). - Handle the readable error states the backend can return (e.g. "No child in workspace — add one before fetching"). Show the message; don't crash. **Acceptance:** - From the overview, clicking the button shows the BankID QR, and after a successful scan the overview updates with the fetched numbers. > Depends on Senior **S1** (persist the scraped snapshot) for the refreshed read to actually return data. You can build and demo the modal/QR against the live `done` result before S1 lands; full overview refresh works once S1 is in. --- ## J6 — Small dev-harness auth fix *(stop the bare "Not authenticated")* **Problem:** `npm run login` opens `/dev/fetch` directly. The auth token lives only in memory after an app login, so opening the harness cold makes `getUserContext()` / `start` return **"Not authenticated"** with no guidance. **Do (minimal):** - In `pages/DevFetch.tsx`, when `getUserContext()` (or any scraper call) returns a 401/unauthorized, render a friendly banner with a **"Logga in först"** link to `/auth` (use `isUnauthorized` from `services/errors.ts`). Optionally auto-redirect to `/auth` and back. - Keep the existing dev-only mount and the "real BankID" warning. This is a small UX fix, not a redesign. **Acceptance:** - Opening `/dev/fetch` while logged out shows a clear "log in first" link instead of a raw error. After logging in (J1 now redirects properly), the harness works. --- ## Definition of done (the demo to run) 1. Register a new account → you land on the dashboard, logged in. 2. Dashboard shows the empty state → add a child → overview renders for that child. 3. Click "Hämta från Försäkringskassan" → scan QR → overview updates with FK numbers. 4. Log out, log back in → you land on the dashboard with the child still there. 5. `npx tsc --noEmit`, `npm run lint`, `npm test` all green. **Escalate to senior** (don't guess): the parental-leave read contract (J3/S2), and anything about how/where scraped data is persisted (S1).