Adds `filter[has-active-compat-session]=true|false` to
`GET /api/admin/v1/users`. When `true` (resp. `false`), only users with
(resp. without) at least one active (non-finished) compatibility session
are returned.
Adds `filter[active-oauth2-client]` to `GET /api/admin/v1/users`. The
filter is a repeatable ULID query parameter; the semantics are OR across
the supplied clients (a user matches if they have an active OAuth2
session belonging to any of them).
Each supplied client ID is validated up front: a missing client returns
404 (mirroring the pattern in `oauth2-sessions/list`).
Adds `UserFilter::with_active_compat_session(has: bool)` and its
PostgreSQL implementation (an `EXISTS` / `NOT EXISTS` subquery against
`compat_sessions` on `user_id` with `finished_at IS NULL`).
Also adds a partial index `compat_sessions (user_id) WHERE finished_at IS
NULL` so that on installations with many finished compat sessions the
existence/non-existence check stays cheap.
Adds `UserFilter::with_active_oauth2_session_for_any_of_clients` and its
PostgreSQL implementation (an `EXISTS` sub-query joining `oauth2_sessions`
on `user_id`, restricting to `finished_at IS NULL` and
`oauth2_client_id = ANY(...)`). The semantics are OR across the supplied
clients.
Also adds a partial composite index `oauth2_sessions (user_id,
oauth2_client_id) WHERE finished_at IS NULL` so that on installations with a
lot of churn we don't need to walk through finished sessions when answering
"does this user have an active session for any of these clients?". The
existing FK indexes can answer the query, but visit finished rows too.
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