The single axios.ts file becomes a directory with:
- factory.ts — createApiClient + the registerDefaultInterceptors /
registerPortalTokenInterceptors seam (preserves the
TECH-AXIOS-STORE-COUPLING decoupling — no store imports inside)
- default.ts — cookie-authenticated client (organizer + cookie-auth
portal flows; existing 45 call sites resolve unchanged)
- portal-token.ts — Bearer-auth client for the artist-advance /
supplier-intake flows (forward-compatible groundwork; no active
consumers today)
- index.ts — re-exports apiClient + portalApiClient + the register* /
createApiClient surface; the existing `import { apiClient } from
'@/lib/axios'` continues to work directory-resolved.
The bindings plugin (plugins/3.axios-bindings.ts) now wires both
clients with a shared deps base + flavour-specific overrides. The
`getPortalToken` callback returns null until Phase E surfaces
`portalToken` on useAuthStore — no current consumers exercise the
Bearer path, so the null-return is intentional placeholder.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Chose Option A from the follow-up brief: useImpersonationStore
already holds an `ImpersonationState` ref hydrated from
sessionStorage at store-init and exposes the active impersonation
target user as a public `targetUserId` computed. The store is the
canonical source; sessionStorage is just its persistence sidecar.
Adds a fifth callback `getImpersonationTargetUserId: () => string
| null` to AxiosBindingsDeps and replaces the
sessionStorage.getItem('crewli_impersonation') + JSON.parse block
in the request interceptor with a single `deps.getImpersonationTargetUserId()`
call. The bindings plugin wires it to
`useImpersonationStore().targetUserId`.
After this commit lib/axios.ts has zero references to
sessionStorage and zero magic strings about impersonation
persistence — the only persistence-mechanism knowledge left is in
useImpersonationStore (where it belongs) and in
plugins/3.axios-bindings.ts (allowed to know about stores). The
HTTP module is now unambiguously pure infrastructure.
Behavior preserved 1:1: the store hydrates from sessionStorage
synchronously inside the defineStore factory, so the very first
HTTP request after page load sees the same target user id as
before.
Co-Authored-By: Claude <noreply@anthropic.com>
Supplies the runtime closures that the registerInterceptors seam
needs. The plugin imports the four stores
(`useOrganisationStore`, `useNotificationStore`, `useAuthStore`,
`useImpersonationStore`) — allowed by the boundaries matrix
(`plugins → stores`) — and passes them as lazy callbacks so the
store factories only resolve when an HTTP call actually fires.
Numeric prefix `3.` runs after `2.pinia.ts` (auto-loaded by
`@core/utils/plugins.ts` in alphabetical-path order), so Pinia is
guaranteed active before the bindings register. No change to
`main.ts` is required — the file is picked up by the existing
`import.meta.glob('./plugins/*.{ts,js}')` glob.
Two redirects previously inside axios.ts now live where they
belong:
- `window.location.href = '/platform'` on impersonation
revocation, in the `onImpersonationRevoked` closure.
- `handleUnauthorized()` (which itself redirects to `/login`)
on 401, gated by `isInitialized` inside the `onAuthFail`
closure — preserves the race-condition fix from sessie 1b-iii.
With this commit the two Vite mixed-import warnings
(useAuthStore + useImpersonationStore being both statically and
dynamically imported) disappear from `pnpm build`. Lint stays at
0 problems, typecheck clean, 49/49 tests pass.
Refs TECH-AXIOS-STORE-COUPLING.
Co-Authored-By: Claude <noreply@anthropic.com>