The frontend still reads the catalog from `data/*.json` (the element page in PR #64) and has no connection to the GraphQL server. This issue sets up [Apollo Client](https://www.apollographql.com/docs/react) so the pages can use the API, without changing the pages themselves. The pages are moved over in the frontend issues (#40–#48, #63, #67). ## Scope - **Packages.** `@apollo/client` (and `graphql`, pinned to 16 like the backend). - **Client and provider.** One `ApolloClient` in `src/lib/apollo.ts` with `InMemoryCache`, wrapped around the app in `src/main.tsx`. The address is `VITE_API_URL` (default `/graphql`), and `.env.example` lists it. In development Vite proxies `/graphql` and `/images` to `http://localhost:3001`, so the browser sees one origin and there is no CORS to configure. On the virtual machine Apache proxies `/graphql` to port 3001 (see #26). - **Cache for the search.** `searchCatalog` is a Relay connection. A type policy merges the pages (`keyArgs` = search, filter and sort, not `after`), so "load more" and the cursor work, and `totalCount` is asked for only with the first page (`@include`). - **Authorization link.** A link that adds `Authorization: Bearer <accessToken>` from the token store (in memory), and on `UNAUTHENTICATED` calls `refreshSession` once with the refresh token and repeats the request. It refreshes from one place at a time (`navigator.locks`), as `docs/API.md` requires. When the refresh fails, the tokens are cleared. The Google login button itself is not part of this issue. - **Error handling.** One place that turns the error codes of `docs/API.md` into messages the pages can show (`BAD_USER_INPUT`, `UNAUTHENTICATED`, `INTERNAL_SERVER_ERROR`, network errors). - **Tests.** The cache policy (two pages merge, a new search starts over), the authorization link (header added, one refresh on `UNAUTHENTICATED`, two requests at once refresh once, a failed refresh clears the tokens), with a mocked link. - **Docs.** README (the `VITE_API_URL` setting and the proxy) and `docs/AI_USAGE.md`. ## Done when - [ ] `npm run dev` with the backend running can send a `searchCatalog` query from a component and show the result - [ ] "Load more" with the cursor appends the next page, and a new search replaces the list - [ ] An expired access token is refreshed once and the request is repeated - [ ] The tests above pass, and lint, typecheck and build pass - [ ] The README explains the setting and the proxy Depends on the GraphQL server (#28) and the login (#70). The `base: '/project2/'` path from #26 must be taken into account for the API address in production.