Persiste proposal, specs delta (auth, api-client, access-requests, platform-shell, testing), design con 8 ADRs, tasks (27 items en 10 fases), verify-report (28 tests, 0 críticos, 0 warnings tras el cierre) y archive-report final. Cierra el ciclo SDD del cleanup de deuda técnica del console.
132 lines
9.8 KiB
Markdown
132 lines
9.8 KiB
Markdown
# Proposal: console-tech-debt-cleanup
|
||
|
||
**Change name:** `console-tech-debt-cleanup`
|
||
**Target service:** `services/frontend-console`
|
||
**Stack:** React 19 + Vite 7 + TypeScript 5.9, Zustand (persist), TanStack Query 5
|
||
**Type:** Technical debt + safety net (no feature work, no backend changes)
|
||
|
||
---
|
||
|
||
## Why
|
||
|
||
The console has accumulated correctness debt concentrated in the **auth layer** and the **API client typing**, on top of a **completely untested mutation surface** that approves/rejects platform access. Each item alone is small; together they form a risk cluster:
|
||
|
||
- **Two sources of truth for the access token** (`token` and `accessToken` fields in the auth store) silently diverged across consumers — the next refactor touching auth has a real chance of breaking sign-in.
|
||
- **`LoginResponse` types do not match runtime** — the login page falls back to `response.access_token ?? response.token`, which means either the type or the runtime contract is wrong. The compiler currently lies about the shape.
|
||
- **`apiClient` casts `null as T`** on 204 responses, defeating TypeScript's ability to catch consumers that forgot to handle "no body".
|
||
- **First-load UX flashes "Restaurando sesion…"** for users who never had a session, because the heuristic uses an unpersisted field.
|
||
- **Failed queries are invisible** on `AccessRequestsPage` — an operator sees an empty table on API failure with no way to distinguish "no requests" from "backend down".
|
||
- **No `ErrorBoundary` at the shell** — any unhandled render error blanks the whole console.
|
||
- **Zero tests in this app**, despite Vitest being the project default. The approve/reject mutations on `AccessRequestsPage` directly gate platform access. Shipping refactors against this surface without a baseline suite is unacceptable.
|
||
|
||
This proposal pays down that debt and lays the testing foundation so future changes land safely.
|
||
|
||
---
|
||
|
||
## What changes
|
||
|
||
Eight discrete fixes, grouped into three bundles by priority. None alter the network contract; the backend is untouched.
|
||
|
||
### Bundle P0 — Correctness, types, and test scaffolding
|
||
|
||
1. **Unify access token field in auth store.** Keep `accessToken` as the single source of truth. Rename consumers in `ProtectedRoute.tsx` and `LoginPage.tsx` to read `accessToken`. Drop the duplicated `token` field from `auth-store.ts`.
|
||
2. **Align `LoginResponse` with the actual backend response.** Determine the real shape (likely `access_token` snake_case from the API). Either make the type match exactly and remove the `?? response.token` fallback, or, if both shapes are legitimately possible, mark both as optional and guard explicitly. No silent `??` chains across non-equivalent fields.
|
||
3. **Remove `null as T` casts in `apiClient`.** Split into two call paths: the default `apiClient<T>` returns `T` and throws on 204 (or refuses to be called on void endpoints), and a sibling `apiClientVoid` returns `void` for endpoints that legitimately respond 204. Callers update accordingly.
|
||
4. **Bootstrap Vitest + Testing Library** in `frontend-console` (it is the project default per `sdd-init` but not yet wired up here). Add `vitest`, `@testing-library/react`, `@testing-library/jest-dom`, `jsdom`, a `vitest.config.ts` aligned with `frontend-admin`, a `setupTests.ts`, and an `npm script` (`pnpm test`). Write the **baseline suite** that protects every P0/P1 change:
|
||
- `SessionCoordinator.refresh` — dedupes concurrent refresh calls.
|
||
- `apiClient` — 401 triggers refresh-and-retry; 204 path is type-safe.
|
||
- `auth-store` — `setSession` and `logout` behave correctly (single token field).
|
||
- `AccessRequestsPage` — approve, reject, and "mark as review" mutations on the happy path; error path surfaces a toast.
|
||
|
||
### Bundle P1 — UX and robustness
|
||
|
||
5. **Fix the "Restaurando sesion…" flash on first load.** In `ProtectedRoute.tsx`, base the "previous session existed, attempt refresh" heuristic on persisted `user` presence, not on the in-memory `token`. Users without a prior session go straight to login with no flash.
|
||
6. **Surface query failures on `AccessRequestsPage`.** Read `requestsQuery.isError` and render an explicit error state (retry CTA + message). No more empty-table-on-failure ambiguity.
|
||
7. **Add an `ErrorBoundary` at the platform shell** so render errors render a recoverable error screen instead of a blank console.
|
||
|
||
### Bundle P2 — DRY hygiene
|
||
|
||
8. **Use the existing `isActionable` helper** in `AccessRequestsPage.tsx` instead of inlining `['pending','in_review'].includes(request.status)` three times.
|
||
|
||
---
|
||
|
||
## Impact
|
||
|
||
### Files touched (expected)
|
||
|
||
- `src/features/auth/stores/auth-store.ts` — drop `token`, keep `accessToken` only.
|
||
- `src/features/auth/api/auth-api.ts` — `LoginResponse` aligned with runtime.
|
||
- `src/features/auth/pages/LoginPage.tsx` — read `accessToken`, drop `??` fallback (or make it explicit and typed).
|
||
- `src/app/router/ProtectedRoute.tsx` — read `accessToken`; refresh heuristic uses persisted `user`.
|
||
- `src/lib/api/client.ts` — split `apiClient` / `apiClientVoid`; remove `null as T`.
|
||
- `src/features/access-requests/pages/AccessRequestsPage.tsx` — error UI, use `isActionable`.
|
||
- `src/app/` (shell) — new `ErrorBoundary`.
|
||
- `vitest.config.ts`, `src/setupTests.ts`, `package.json` (devDeps + scripts) — test scaffolding.
|
||
- New `__tests__` files colocated with the units listed in P0.4.
|
||
|
||
### Behavioral changes
|
||
|
||
- **Internal-only:** the auth store exposes one token field instead of two. Any external code reading `state.token` would break, but no such consumers exist outside the four files listed.
|
||
- **User-visible:**
|
||
- First-load no longer flashes "Restaurando sesion…" for users without a prior session.
|
||
- Failed access-request queries now show an error state instead of an empty table.
|
||
- Render errors render an error boundary instead of a blank screen.
|
||
- **Network contract:** unchanged. No backend modification.
|
||
|
||
### Tooling
|
||
|
||
- New dev dependencies: `vitest`, `@testing-library/react`, `@testing-library/jest-dom`, `@testing-library/user-event`, `jsdom`. Aligned with what `frontend-admin` already uses.
|
||
- New script: `pnpm test` (and `pnpm test:watch`).
|
||
|
||
---
|
||
|
||
## Out of scope
|
||
|
||
- New features or new modules.
|
||
- Redesign of `AccessRequestsPage` UX (filtering, pagination, sorting, etc.).
|
||
- Backend API changes — even if `LoginResponse` shape is awkward, the proposal aligns the **type** to the existing **runtime**, not the other way around.
|
||
- Migrating other apps (`frontend-admin`, `landing`) — they are out of scope for this change.
|
||
- Auth flow architectural changes (refresh token rotation, session storage strategy, etc.) — only the duplicated-field bug is addressed.
|
||
- E2E tests. Only unit/component tests via Vitest + Testing Library.
|
||
|
||
---
|
||
|
||
## Priorities
|
||
|
||
| Priority | Items | Rationale |
|
||
|----------|-------|-----------|
|
||
| **P0** | 1, 2, 3, 4 | Correctness + type safety + the test scaffolding required to land everything else without regressions. P0.4 must come first within the bundle so P0.1–3 are protected by tests. |
|
||
| **P1** | 5, 6, 7 | UX correctness and robustness. Visible-but-non-correctness issues. Land after P0 so the test suite already protects against regressions. |
|
||
| **P2** | 8 | Pure DRY hygiene. Lowest risk, lowest reward. Can ship in the same PR as P1 or as a tail commit. |
|
||
|
||
Recommended sequencing inside P0: **(4) test scaffolding + baseline tests → (1) unify token → (2) align types → (3) split `apiClient`**. Tests come first so the auth and client refactors land green.
|
||
|
||
---
|
||
|
||
## Risks
|
||
|
||
**Overall risk: low.** This is a local refactor inside a single app, no backend changes, no network contract changes.
|
||
|
||
| Risk | Likelihood | Impact | Mitigation |
|
||
|------|-----------|--------|------------|
|
||
| Renaming `token` → `accessToken` misses a consumer and silently breaks login | Low | High (sign-in broken) | Test scaffolding (P0.4) ships first and includes auth-store + protected-route tests; TypeScript catches structural reads; manual smoke of login + refresh in dev. |
|
||
| Splitting `apiClient` into `apiClient` / `apiClientVoid` requires touching every void-returning caller | Medium | Low (compile-time) | TypeScript flags every miss at build time; mechanical fix, no runtime ambiguity. |
|
||
| `LoginResponse` runtime contract turns out to differ from assumption | Low | Medium | Verify against backend handler before changing the type; if both shapes truly exist, model both as optional and document why. |
|
||
| Test scaffolding diverges from `frontend-admin` conventions | Low | Low | Mirror `frontend-admin`'s `vitest.config.ts` and `setupTests.ts` 1:1 where possible. |
|
||
| `ErrorBoundary` swallows errors useful for debugging | Low | Low | Boundary logs to console + shows a "reload" CTA; only catches render errors, not async ones. |
|
||
|
||
---
|
||
|
||
## Success criteria
|
||
|
||
- The auth store exposes **a single** access-token field; no consumer reads `state.token` (it no longer exists).
|
||
- `LoginResponse` matches the backend response exactly; the `?? response.token` fallback is gone (or both fields are explicitly optional and guarded).
|
||
- `apiClient` no longer contains `null as T`. Void endpoints use a dedicated `apiClientVoid` (or equivalent pattern) and TypeScript correctly types the absence of a body.
|
||
- First-load with no prior session goes **directly to login** — no "Restaurando sesion…" flash.
|
||
- A failed `AccessRequestsPage` query renders a **visible error state** with a retry, distinguishable from an empty list.
|
||
- A render error anywhere in the console renders the `ErrorBoundary`, not a blank page.
|
||
- `AccessRequestsPage` uses the `isActionable` helper everywhere; no inline duplication of the status check.
|
||
- `pnpm test` runs Vitest in `frontend-console` and the **baseline suite passes green**, covering: `SessionCoordinator.refresh` dedupe, `apiClient` 401 retry + 204 typing, `auth-store` `setSession` / `logout`, `AccessRequestsPage` approve/reject/review mutations (happy + error toast).
|
||
- `pnpm run lint` is clean.
|
||
- No conventional-commit trailers reference AI authorship.
|