### Problem Two pages do too much themselves. A page should compose: get its data, put components on the screen, and leave the rules to hooks and components. | Page | Lines | What is in it besides the layout | |---|---|---| | `ElementsPage.tsx` | 422 | About 160 lines of logic before the first line of JSX: the search text and its debounce, the address (`?search=`, filters, sort) read and written through a ref, the structure query, the search query, and "Load more" with its own loading and error state | | `YourGymnastsPage.tsx` | 261 | Five `useState`, two refs, an effect, the form open/close with focus handling, the create mutation, and the screen-reader announcement | (Paths: `src/pages/…` once #154 is merged, `src/components/elements/` and `src/components/gymnasts/` before.) The project has no custom hooks besides `useAuth` and no `hooks/` folder. The ElementsPage logic is also hard to read as one block: the comments are good, but the order of the code (address, then filters, then sort, then query, then "Load more") is only visible by scrolling through 160 lines. Why this matters: readable, well-structured code is assessed. In the peer review of another team's project, pages of 700+ lines with many `useState`/`useEffect` calls and no custom hooks were a main finding on code quality. ### What to do Pull the logic out into hooks in `src/hooks/` (or `src/lib/` if the team prefers; decide once and say so in the README "Project structure"). Suggested split, each hook with one job and a name that says it: - [ ] **`useDebouncedSearchParam`** (or `useSearchText`): the field text, the debounce with `SEARCH_DELAY_MS`, and keeping the field in step with the address when the user goes back or forward. - [ ] **`useElementsAddress`**: reading and writing `?search=`, the filters and the sort in the address (`changeAddress`, `writeAddress`, `changeSort`, `changeFilters`), including the "latest address" ref that stops two quick changes from overwriting each other. - [ ] **`useLoadMore`**: `loadMore`, `loadingMore` and `moreError`. - [ ] **`useAddGymnastForm`** (YourGymnastsPage): form open/close, focus handling, the mutation, the error and the announcement. - [ ] `ElementsPage` and `YourGymnastsPage` end up as: call the hooks, then JSX. Aim for roughly 150 lines for ElementsPage; do not chase an exact number. ### Rules - **Behaviour must not change.** The existing page tests are the safety net: the `ElementsPage` and `YourGymnastsPage` tests and the e2e tests must pass unchanged. If a test has to change, the refactor went too far. - Keep the explaining comments (for example the one about `setSearchParams` and two quick choices); move them with the code. - Do not add new behaviour or new dependencies. - Add a small test for each hook that has logic of its own (the debounce, the "latest address" rule, `useLoadMore`'s error handling), using `renderHook`. - One hook per commit, with `type(scope): subject` messages. ### Done when - [ ] `ElementsPage` and `YourGymnastsPage` contain no search, address or mutation logic of their own, only calls to hooks and layout. - [ ] Each new hook has a comment saying what it does and why it exists. - [ ] Lint, type check, all tests, e2e and build pass. - [ ] The README "Project structure" table lists the hooks folder. - [ ] `docs/AI_USAGE.md` has an entry. ### Timing Do this after #154 (the move to `src/pages/`) and #133 (Prettier) are merged, or it conflicts with both. Tell the team before starting, because every open branch that touches the two pages will conflict. ### Not in this issue - Moving `DifficultyBadge`, `ElementValueBadge` and `ElementVariants` out of `components/ui/`. - Splitting `ResultsTable.tsx` or the filter menus. _Comment made with Claude Code in discussion with Ronja_