* test(web): standardize frontend tests on Vitest
- configure Vitest, jsdom, and React Testing Library with shared test scripts.
- migrate existing node:test suites to the Vitest runner.
- rewrite JsonCodeEditor component tests with RTL and remove the direct happy-dom dependency.
* fix(ci): run frontend tests with Vitest
- invoke the configured Vitest script so browser test setup loads in CI.
- migrate remaining node:test suites to Vitest lifecycle APIs.
* test(web): use shared jsdom environment for component tests
- migrate usage cost and tool price tests to React Testing Library.
- remove duplicate happy-dom globals and rely on the configured Vitest setup.
* test(web): verify behavior with shared vitest setup
- replace Node test assertions with Vitest expect across frontend suites.
- migrate Keys component tests to React Testing Library interactions.
- centralize jsdom browser mocks for consistent component execution.
* fix(web): unblock frozen installs and Vitest CI
- sync dompurify 3.4.13 metadata into the Bun lockfile.
- replace the bun:test and happy-dom redemption harness with Vitest and RTL.
- preserve quota conversion, error feedback, and stale-response coverage in jsdom.
* fix(oauth): stop treating a foreign window.opener as a bind flow
The /oauth/:provider callback decided between an account bind and a plain
login with `window.opener ? 'bind' : 'login'`. Any tab opened from an
external link (target="_blank", Slack, mail clients, another site) carries
a live opener, and that opener survives the cross-origin round trip to the
identity provider. Such a login callback was therefore misread as a bind:
it posted a handshake to a window that speaks no such protocol, showed the
"binding your account" screen, and hung until the 30s deadline fired with
"OAuth binding timed out" — while the backend was never called at all.
Reproduced against a real Keycloak round trip: a tab opened via window.open
still reports window.opener !== null on the callback, so mode resolved to
'bind' for an ordinary OIDC login.
A bind now requires positive proof: the popup we open for it is same-origin
(about:blank) before being sent to the provider, so we stamp its own
sessionStorage. The stamp rides through the provider round trip and is
scoped to that popup alone, so a login tab can never carry it. Ambiguity
resolves to 'login', which is the recoverable direction.
Affects every provider sharing this callback (OIDC, GitHub, Discord,
LinuxDO, custom).
* fix(oauth): harden bind popup detection
* docs: add design spec for OIDC custom display name
Mirrors the existing Custom OAuth Provider name pattern so admins can
show a meaningful label instead of the hardcoded "OIDC" on the login
page and in related copy.
* feat(oidc): add configurable display name with OIDC fallback
* feat(oidc): use configured display name in provider name and status API
* feat(oidc): add display name field to default-theme OIDC settings
Claude-Session: https://claude.ai/code/session_01FDkWJqigJi9yE3HG5pjZP5
* feat(oidc): show configured display name on default-theme login button
* feat(oidc): add display name field to classic-theme OIDC settings
* feat(oidc): show configured display name on classic-theme login button
* fix(oidc): trim whitespace before applying display name fallback
* chore: remove internal design doc from PR
Design/planning docs are working artifacts for this session and
shouldn't be submitted to the upstream project.
* fix(oidc): lead with example in classic-theme display name placeholder
Reorders the combined placeholder to show the example first, then
the fallback note, matching the Custom OAuth Provider Name field's
placeholder convention (example-only) that this feature mirrors.
* fix(i18n): improve Russian grammar in OIDC display-name placeholder translation
Leads each clause with its condition/subject and adds the missing
verb, per PR review feedback.
* test(web): remove redundant OIDC harness tests