Make `filter[client]` on `GET /api/admin/v1/oauth2-sessions` repeatable
so admin tooling can fetch sessions belonging to a set of clients in
one request. The field on `FilterParams` changes from `Option<Ulid>`
to `Vec<Ulid>`; the struct was already extracted with
`axum_extra::extract::Query` so the repeated values are not silently
dropped.
Each client ULID is validated to exist (mirroring the previous
single-client `404` behaviour) before being passed to the new
`OAuth2SessionFilter::for_clients` storage filter. The `Display`
impl used to reconstruct cursor links now emits one
`filter[client]=…` segment per client so pagination preserves the
filter. The OpenAPI schema is regenerated via `misc/update.sh` and
now describes the parameter as an array.
Add a new `for_clients(&[&Client])` method (and matching `clients()`
getter) on `OAuth2SessionFilter`. The existing single-client
`for_client(&Client)` is kept intact as GraphQL still uses it; the two
fields are independent and ANDed together when both happen to be set.
In the Postgres implementation the predicate uses sea-query's `is_in`,
which translates to a plain `column IN (...)`. As a side effect, an
empty client list matches no rows (sea-query emits `WHERE 1 = 2`),
which is the desired behaviour for an explicit "filter on this empty
set of clients" request.
For example, `#[expect(unused_mut)]` is better because it will complain about the lint being unused if there is not `mut` usage anymore.
> Example
> ```rust
> #[allow(unused_mut)]
> fn foo() -> usize {
> let mut a = Vec::new();
> a.len()
> }
> ```
>
> Use instead:
> ```rust
> #[expect(unused_mut)]
> fn foo() -> usize {
> let mut a = Vec::new();
> a.len()
> }
> ```
Spawning from https://github.com/element-hq/matrix-authentication-service/pull/5670#discussion_r3244177988
Tailwind 4 ships its own Vite plugin and no longer needs PostCSS plumbing.
Drop postcss/autoprefixer/postcss-import/postcss-nesting and the .postcssrc.json,
wire @tailwindcss/vite into vite.config.ts, replace the @tailwind directives
with @import "tailwindcss", and convert the JS tailwind.config.cjs theme
into a CSS @theme block in shared.css. An @source directive points back at
the SSR templates so the Jinja-rendered HTML in templates/ still gets scanned
for utility classes.
The third-party CSS (compound design tokens, compound-web, fontsource) moves
into a new vendor.css entrypoint, loaded ahead of shared.css from main.tsx,
storybook, and base.html. Tailwind v4's @import bundler silently drops the
nested `@import url(...) layer(cpd-base) screen` statements inside the
compound-design-tokens.css barrel, so we have to keep those imports out of
any file that contains Tailwind directives and let Vite's normal CSS pipeline
resolve them instead.
They are very small and don't need an extra request to load. This is useful as
well for making the snapshots not dependant on where the project is installed