# Admin UI Vite + React 19 single-page app served at `/admin`. Talks to the backend over socket.io for the existing settings / plugins / pads pages, and (when endpoints are added to the OpenAPI spec) over a typed REST client. ## Scripts | Script | What it does | | -------------------- | -------------------------------------------------------- | | `pnpm dev` | `gen:api` + Vite dev server (expects backend on :9001). | | `pnpm gen:api` | Regenerates `src/api/{schema.d.ts,version.ts}` from the OpenAPI spec. | | `pnpm build` | `gen:api` + `tsc` + `vite build`. | | `pnpm build-copy` | Same, but writes into `../src/templates/admin`. | | `pnpm test` | `gen:api` + smoke tests for the API client wiring. | | `pnpm lint` | ESLint. | ## Typed API client The admin uses [`openapi-typescript`] to generate types from `src/node/hooks/express/openapi.ts`, [`openapi-fetch`] for typed requests, and [`openapi-react-query`] for TanStack Query bindings. [`openapi-typescript`]: https://github.com/openapi-ts/openapi-typescript [`openapi-fetch`]: https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch [`openapi-react-query`]: https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-react-query ### Generated files `admin/src/api/schema.d.ts` and `admin/src/api/version.ts` are generated by `gen:api` and gitignored — never commit them. They are produced by: ```sh pnpm --filter admin gen:api ``` `admin/scripts/gen-api.mjs` loads `src/node/hooks/express/openapi.ts`, calls `generateDefinitionForVersion` for the latest API version, pipes the JSON through `openapi-typescript` to produce `schema.d.ts`, and emits a runtime constant `LATEST_API_VERSION` (read from `info.version` in the spec) to `version.ts` so `client.ts` can build the right `/api//` baseUrl. `gen:api` runs as the first step of `dev`, `build`, `build-copy`, and `test`, so a fresh checkout produces the generated files automatically when any of those scripts is invoked. After modifying any of the following, the next `pnpm ` will refresh the generated files; you can also run `gen:api` directly: - `src/node/hooks/express/openapi.ts` - `src/node/handler/APIHandler.ts` (changes to `latestApiVersion`) - the resource definitions referenced by `openapi.ts` ### Using the client ```tsx import { $api } from './api/client'; const SettingsPanel = () => { const { data } = $api.useQuery('get', '/admin/settings'); // example return
{JSON.stringify(data, null, 2)}
; }; ``` The admin endpoints are not yet present in the OpenAPI spec — this client is in place to support upcoming work (see issue #7638 follow-up). For now, it is exercised only by the smoke test. ## Socket.io: `padLoad` query shape The admin `/settings` namespace's `padLoad` event accepts a `PadSearchQuery` defined in `src/node/types/PadSearchQuery.ts`: | field | type | required | notes | | ------------ | ----------------------------------------------------------------- | -------- | ----- | | `pattern` | `string` | yes | Substring match on pad name. | | `offset` | `number` | yes | Pagination start, in items. Clamped server-side. | | `limit` | `number` | yes | Page size. Capped at 12. | | `ascending` | `boolean` | yes | Sort direction. | | `sortBy` | `"padName" \| "lastEdited" \| "userCount" \| "revisionNumber"` | yes | Column to sort by. | | `filter` | `"all" \| "active" \| "recent" \| "empty" \| "stale"` *(opt.)* | no | Filter chip; defaults to `"all"`. Applied **before** pagination so `total` and the page slice both reflect the filtered universe. Older clients that omit the field get the unchanged `"all"` behaviour. | Filter semantics — applied after pattern matching, before sort + slice: - `active`: `userCount > 0` - `recent`: edited within the last 7 days - `empty`: `revisionNumber === 0` - `stale`: not edited in the last 365 days - `all` / missing: no further filtering