litellm/ui/litellm-dashboard/CLAUDE.md
ryan-crabbe-berri 7b574b9df6
Some checks failed
CI Coverage / assert-ci-coverage (push) Waiting to run
CodSpeed Benchmarks / benchmarks (push) Waiting to run
Publish basedpyright base counts / publish (push) Waiting to run
Code Quality Checks / code-quality (push) Waiting to run
UI Unit Tests / ui-unit-tests (push) Waiting to run
Unit Tests: Documentation Validation / documentation (push) Waiting to run
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Waiting to run
Unit Tests: Proxy DB Operations / auth-checks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / budgets (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / custom-logging (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / db-and-spend (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / key-generation (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / logging-misc (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-runtime (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-server-core (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-utils (push) Blocked by required conditions
GitHub Actions Security Analysis / zizmor (push) Waiting to run
Unit Tests: Core Utilities / core-utils (push) Has been cancelled
Unit Tests: Enterprise, Google GenAI & Routing / enterprise-routing (push) Has been cancelled
Unit Tests: Integrations (Callbacks & Logging) / integrations (push) Has been cancelled
Unit Tests: LLM Provider Transformations / Vertex AI (push) Has been cancelled
Unit Tests: LLM Provider Transformations / All Other Providers (push) Has been cancelled
Unit Tests: MCP, Secrets, Containers & Misc / misc (push) Has been cancelled
Unit Tests: Proxy Auth & Key Management / proxy-auth (push) Has been cancelled
Unit Tests: Proxy Infrastructure / proxy-infra (push) Has been cancelled
Unit Tests: Responses, Caching & Types / responses-caching-types (push) Has been cancelled
Unit Tests: Proxy API Endpoints / proxy-endpoints (push) Has been cancelled
Unit Tests: Proxy API Endpoints / proxy-server (push) Has been cancelled
chore(ui): drop the antd dependency and its leftovers (#37574)
Nothing in the dashboard renders antd any more, so the package and the
scaffolding around it can go. This removes `antd` and
`@ant-design/cssinjs` from package.json, deletes the global StyleProvider
the root layout wrapped every page in, drops the `antd` cascade layer and
the z-index override that lifted Base UI popups over an antd Modal, and
retires the lint rules that policed antd imports and antd class selectors
in tests.

Fifteen test files still carried `vi.mock("antd", ...)` factories for
components that stopped importing antd during the migration. They were
inert, and they resolve the real module, so they would have broken the
moment the package left node_modules.

The compatibility shims keep their behaviour and lose the antd name:
`antdRules`/`antdRequired` become `validatorRules`/`requiredRule`,
`isAntdUrl` becomes `isValidUrl`, and `ABOVE_ANTD_MODAL` becomes
`NESTED_DIALOG_LAYER`. Comments that explain why a contract looks the way
it does still name antd, because that history is the reason.
2026-08-19 20:44:27 -07:00

25 lines
5.9 KiB
Markdown

Never put LiteLLM tokens or API keys in `localStorage`. `localStorage` survives browser close. Prefer `httpOnly` cookies, or `sessionStorage` at most, understanding that any web storage is readable by injected scripts (XSS), and only httpOnly cookies are not
When you fix lint violations that are grandfathered in `eslint-suppressions.json`, run `eslint . --prune-suppressions` and commit the updated baseline so the gate ratchets down instead of leaving a stale suppression
`src/lib/http/schema.d.ts` is generated from the proxy's OpenAPI spec; never hand-edit it. After changing a backend route or response model that the dashboard consumes, run `npm run gen:api` and commit the result (CI `Check UI API Types Sync` enforces this)
Tests come in three tiers, named by the standard definitions. `Foo.test.tsx` is a unit test: one module, collaborators replaced by doubles, no multi-component tree, and it should run in milliseconds. `Foo.integration.test.tsx` renders a real component tree with real children and only stubs the network boundary; it costs seconds per case, so it earns its place by proving wiring that a unit test cannot reach. Browser-level tests live in `tests/e2e/ui/` as Playwright specs against a live proxy
When a component holds logic worth asserting, extract the logic and unit-test it there rather than driving it through a render. `CreateMCPServer` is the worked example: its payload building lives in `createServerPayload.ts` with 46 unit tests that run in single-digit milliseconds, while `CreateMCPServer.integration.test.tsx` keeps only the cases that prove a form field reaches the right payload key. A test that renders a whole modal to assert the shape of one object belongs in the first category, not the second
Most of the suite predates this split and is not yet classified, so an unsuffixed `*.test.tsx` is not evidence that a file is really a unit test. Classify what you touch
Assert something the user could perceive, and assert it precisely enough that the test fails when the behaviour breaks. `eslint-plugin-testing-library` and `eslint-plugin-jest-dom` enforce the mechanical part of that. Two of the enabled rules exist because the failure they catch is silent rather than cosmetic: `await-async-queries` catches an unawaited `findBy*`, whose returned Promise is always truthy and makes the whole assertion vacuous, and `no-wait-for-side-effects` catches work inside a `waitFor` callback, which is retried on every poll. Prefer `findBy*` over `waitFor` wrapped around `getBy*`, and keep a `waitFor` callback to a single assertion
Do not trust `eslint --fix` for these two plugins. Fixing the suite in bulk produced seven distinct kinds of broken output. Four fail loudly: `no-wait-for-side-effects` and `no-wait-for-multiple-assertions` hoist a statement out of the `waitFor` callback while leaving the `const` it reads inside, `prefer-enabled-disabled` drops a closing paren when the subject carries a type assertion, `prefer-presence-queries` swaps in a query it never destructures, and `prefer-in-document` collapses `getAllBy*` to `getBy*` on a value still indexed as an array. Two fail quietly, which is worse: `prefer-checked` swaps the `checked` attribute for the `.checked` property, and a radio can set one without the other, and `prefer-to-have-text-content` wraps arbitrary strings in `new RegExp()` without escaping, so `toContain("100K+ requests")` becomes a pattern meaning "100 followed by one-or-more K". That last one compiles, lints clean, and keeps passing while no longer asserting what it says. Pass a plain string to `toHaveTextContent`, which is already a substring match. Run the fixer on a handful of files at a time and read the diff
`jest-dom/prefer-to-have-value` stays off because its fixer is wrong here, not merely noisy. It matches any attribute whose name contains "value", so it rewrites `toHaveAttribute("aria-valuenow", n)` into `toHaveValue(n)`, and jest-dom's `toHaveValue` only supports form controls, so the assertion fails on the `role="meter"` elements the dashboard renders. Assert ARIA value attributes with `toHaveAttribute`
Reach for `fireEvent.change` rather than `user.type` when a test only needs a field to hold a value. `user.type` dispatches one event per character and re-renders the whole form each time, which is why a single form test could burn seven seconds. Keep `user.type` where the typing itself is the behaviour under test: an autocomplete that filters per keystroke, a debounce, a key handler, or any Base UI combobox, whose filter state is driven by real keyboard input and does not react to a raw change event
A test may reach for a component library's own CSS class only when that library exposes no role, label, title or ARIA state to query instead. Check first: the shadcn primitives forward roles and `aria-label`, and the shared form field associates its label with the control, so both are reachable accessibly. When nothing accessible identifies the element, prefer its `data-slot` attribute, which the primitives set deliberately and treat as stable. When a label does not resolve, suspect the control rather than the test, since a custom wrapper that destructures props without spreading them drops the `id` the field generates and leaves the rendered label pointing at nothing
Rules beyond the enabled set were measured against the whole suite and left off rather than recorded in a budget file, because a ceiling that permits a violation anywhere is worse than an honest gap. `no-node-access` and `no-container` are the ones worth revisiting first, since they catch the DOM archaeology the rules above only discourage. `prefer-implicit-assert` and `prefer-explicit-assert` contradict each other, so neither is enabled
Never run the full unit suite (`npx vitest run` with no path). It is 380 files and thousands of tests, it saturates the machine for many minutes, and CI runs it anyway. Run only the test files your change touches, plus any file whose failure your change could plausibly explain, by passing explicit paths