Commit graph

4874 commits

Author SHA1 Message Date
yuneng-jiang
e126975468
refactor(ui): migrate the antd Button call sites onto the shadcn Button (#37505)
Moves all 58 antd Button JSX sites across 24 files onto the shadcn
Button, leaving zero antd Button importers.

Prop mapping follows what already merged rather than a new convention:
type="primary" to the default variant, a bare button to outline (the
house default), type="text" to ghost, type="link" to link,
type="dashed" to outline plus border-dashed, danger to destructive,
size="small" to sm, htmlType to type, block to w-full, the icon prop to
a child, and loading to disabled plus aria-busy. Icon-only buttons take
the matching icon-* size. Inline style props that had a direct utility
equivalent moved to className, and the opacity toggle on the create key
submit is dropped since the base cva already carries disabled:opacity-50.

Base UI's Button defaults type to "button" for native buttons, the same
default antd used, so bare buttons inside a form do not start
submitting.

guardrail_info.tsx and tag_info.tsx lose their last antd import, so
their no-restricted-imports suppressions are pruned. The other 22 files
keep other antd symbols and keep their entries.

One behavior change: the MCP transports docs link now opens in a new
tab, matching every other external docs link in the dashboard, instead
of navigating the dashboard away.

The ModelSettingsModal loading assertion moves off antd's spinner
element onto aria-busy, which is what the rest of the suite already
asserts; that markup cannot survive removing antd.
2026-08-19 13:32:00 -07:00
mateo-berri
0aca0353d2 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_stale_member_search_results
# Conflicts:
#	ui/litellm-dashboard/src/components/organisms/create_key_button.tsx
2026-08-19 13:31:05 -07:00
Mateo Wang
0f19b5b9ab
Merge pull request #37361 from sytianhe/litellm_spend_log_timestamps
feat(spend-logs): add lifecycle timestamps
2026-08-19 13:29:49 -07:00
ryan-crabbe-berri
73e7105e60
fix(ui): restore tab strip styling and panel persistence lost in the shadcn migration (#37403)
Tremor's TabList defaulted to the underlined `line` variant and its TabPanel
rendered every panel, hiding the inactive ones with a class. The shadcn
TabsList defaults to a segmented pill and Base UI's TabsPanel unmounts a
hidden panel unless it carries `keepMounted`, so the conversion waves quietly
changed both on the pages that took tremor's defaults.

Restores the underline on the nine strips whose tremor markup carried no
`variant`, leaving the ones that were explicitly `variant="solid"` as pills,
and puts `keepMounted` back on the thirteen files whose panels used to stay
mounted, so filters, scroll position and in-progress input survive a tab
switch again.

Seeding the old usage page's activity state properly comes with it: it was
cast from `{}`, so the panel crashed on `data.length` the moment it mounted
before its fetch resolved, which only stayed hidden while the panel was
unmounted until first opened.
2026-08-19 13:28:20 -07:00
mateo-berri
6acf970009 chore(ui): regenerate dashboard API types for spend log timestamps 2026-08-19 12:41:52 -07:00
yuneng-jiang
4bb3152cc5
test(ui): drive fields with change events where the typing is not the behaviour (#37495)
user.type dispatches one event per character and re-renders the whole form on
each one, so a test that only needs a field to hold a value pays for every
keystroke. Where the value is all the test wants, fireEvent.change sets it in
one go.

Measured against a control on the same tree: the 100 converted files went from
654s to 619s of CPU, 5.4% cheaper, while the files nobody touched drifted 1.2%
the other way. Modest, and honest about it.

Rolled out one file at a time, running each before and after and keeping the
conversion only where the file stayed green. That rejected 35 files, all cases
where the keystrokes are the behaviour: Base UI comboboxes drive their filter
from real keyboard input and ignore a raw change event, and the same goes for
autocompletes, debounces and key handlers. Those keep user.type.
2026-08-19 12:11:37 -07:00
Devin AI
f80cb0d9f8 refactor(ui): drop redundant comment above guardrail mode formatter
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 18:33:06 +00:00
Devin AI
881aa20808 fix(ui): format tag-based guardrail mode in delete modal, playground, and policy picker
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 18:31:42 +00:00
Devin AI
b8680e6bae fix(ui): render tag-based guardrail mode instead of crashing guardrails page
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 18:22:31 +00:00
yuneng-jiang
481c08de4e
refactor(ui): port the MCP server forms off antd Form onto react-hook-form (#37483)
* refactor(ui): port the MCP server forms off antd Form onto react-hook-form

The MCP create and edit forms were the last antd `Form` graph in the
dashboard. antd's `onFinish` hands back only the fields mounted at submit
time, while react-hook-form with `shouldUnregister: false` hands back the
whole store, so a direct port would quietly widen every create and update
request.

All 14 files now bind through `MountedFormField`, whose mount registry
reproduces antd's mounted-only submit: both roots build their payload from
`projectMountedValues` instead of `getValues`. `mcpFormStore` carries the
rest of the FormInstance surface the two roots relied on, each piece
matched to what rc-field-form actually does rather than to what the API
name suggests: `setFieldsValue` deep-merges plain objects and writes an
explicit `undefined`, `resetFields` restores the seeded values rather than
clearing the key, and `onValuesChange` is rebuilt from a `watch`
subscription filtered to user input, carrying a single changed branch.

Two watches needed the mount gate moved rather than translated.
`MCPPermissionManagement` renders outside the transport gate that mounts
`auth_type`, so antd's watch read `undefined` there and mounted the
pass-through toggle; the effective auth type now arrives as a prop that
each root computes with exactly that gate. The edit root reads every watch
off the mounted projection for the same reason, which also stops the token
material in a saved server's credentials from reaching the tool preview.

`Form.List` becomes `useFieldArray` plus a `useMountedName` registration
for the list key itself, because antd registers a list as one field: a
per-user variable row keeps the `value` its scope hides, and an empty list
still submits `env_vars: []` instead of dropping the key.

* test(ui): cover the mount registry's unregister path on the real primitive

The existing MountedFormField suite drove a hand-written registry whose
register returned a no-op, so nothing exercised useMountRegistry's
ref-counting or the cleanup that React wires from useMountedName's effect
return value. A reviewer read that gap as a missing unregister.

These three cases drive the real hook through a gated tree: a key leaves
the submitted payload when its gate unmounts the field, a required field
that unmounts stops blocking submission, and a name held by two fields
survives one of them releasing it.

Verified by mutation: rewriting the effect body to discard the cleanup
turns the first two red, the second reporting the reviewer's exact
symptom, "expected [ 'server_name', 'token_url' ] to not include
'token_url'".

* test(ui): prove the permission panel's booleans reach the create payload

CreateMCPServer.integration.test.tsx mocks MCPPermissionManagement, so the
four booleans createServerPayload writes were invisible to every existing
create-side test. vi.mock is file-scoped, so rendering the real panel needs
its own file.

Four cases, each killed by a different mutation:

  unbind allow_all_keys                -> "sends allow_all_keys true"
  unbind available_on_public_internet  -> "sends the panel's defaults"
  invertedSwitchControl -> switchControl -> "sends ... false when the
                                            operator restricts"
  isOAuth2 gate forced open            -> "omits delegate_auth_to_upstream"

A payload assertion expecting false cannot detect an unbound field, since
Boolean(undefined) is false too, so the two cases carrying unbinding
detection are the ones asserting true. The other two are pinned by the
switch-inversion and mount-gate mutations instead.
2026-08-19 10:26:20 -07:00
yuneng-jiang
c696fdfb05
fix(ui): gate the pass-through guardrail field inputs when the section is disabled (#37435)
PassThroughGuardrailsSection threads its `disabled` prop to the guardrail
selector and to all three quick-add buttons, but not to the two `TagsInput`
controls for request and response field targeting. Every other prop crossed,
so the section greys out while a user can still type field names straight
into both tag inputs.

Latent rather than live: neither call site passes `disabled` today, so the
prop is currently dead across the tree. This arms the gate for whoever passes
it first.

`TagsInput` already honours `disabled` end to end, measured rather than read:
with it set, the input carries a real disabled attribute and all three commit
paths, token separator, Enter and blur, are inert. So the fix belongs at the
call sites and the primitive needs no change.

The new test drives the control rather than asserting an attribute, since a
Base UI control can paint a disabled state while still accepting input. The
enabled cases are the liveness gate: they type the same text through the same
gesture and assert it commits, so the disabled cases cannot pass vacuously.
2026-08-19 07:00:33 +00:00
yuneng-jiang
0700b1e54e
fix(ui): rebuild nested and list paths in the mounted-field projection (#37450)
* refactor(ui): extract the MCP server edit save payload into a pure builder

`handleSave` built the update payload inline across 276 lines, spreading
`...restValues` straight off a mounted-only `onFinish`. That makes the payload a
function of which fields happen to be mounted, and it leaves no seam to test the
shape without rendering the whole edit form.

Move the payload construction into `editServerPayload.ts` as
`buildEditServerPayload(values, ui)`, a pure function over the submitted values
plus the nine pieces of component state the handler reads. Failures become values
rather than early returns with a toast: the six error branches are a tagged union
that `editPayloadErrorMessage` maps back to the exact strings shown today, via an
exhaustive switch. `handleSave` keeps the network call, the OAuth token
persistence and its own try/catch.

This is a move, not a rewrite. To prove that, `editServerPayload.differential.test.ts`
holds a baseline machine-extracted from the pre-refactor function body by line
range, with the failure branches converted by exact string replacement. The
generator refuses to emit unless the slice is still present verbatim in the
source, every conversion matches exactly once, no toast call survives, and a
deliberately corrupted probe still trips that check. 59 scenarios run both
implementations and compare the payload object, its key order, and its serialised
bytes, so a re-ordering that leaves values untouched is caught too.

The duplicate local `AUTH_TYPES_REQUIRING_CREDENTIALS` is dropped in favour of the
identical exported list, and `reduceStaticHeaders` is shared with the create side.
Both were verified equal before reuse.

Behaviour is unchanged. The 466 pre-existing tests in the directory pass unedited.

* refactor(ui): type the MCP edit payload builder instead of Record<string, any>

The extraction created a new public signature, so it should carry a real
contract. buildEditServerPayload now takes EditServerFormValues and returns
EditServerPayload, both declaring every field the builder actually reads and
writes, with an unknown-valued index signature for the keys the form passes
straight through. handleSave is annotated too, so antd's untyped onFinish
value is narrowed once at the boundary rather than travelling as any.

Fields that arrive from the store with their own runtime validation
(static_headers, env_vars, credentials) stay unknown rather than being given a
narrower declared type the form does not actually guarantee. Values are not
run through a parser: the payload's serialised key order is part of the
contract this module exists to hold, and rebuilding the object would reorder
it.

The credentials assignment moves from two post-hoc mutations to a single
resolved entry, which keeps the payload readonly end to end and lands the key
in the same position in all four branches.

Behaviour is unchanged. The 59 differential scenarios still match the frozen
pre-extraction body on object, Object.keys order and JSON.stringify bytes, and
the mcp-servers suite is 525/525 across all 30 files. Three tsc probes confirm
the new types have teeth: a wrong payload assignment, a misspelled field read
and an invalid value each fail the type check.

* feat(ui): add mounted-field projections for the MCP server form graph

antd's onFinish reports exactly the fields mounted at submit time, and both MCP
server payload builders spread that object straight through. react-hook-form
with shouldUnregister false hands back the whole store instead, so a port needs
the mount set written out explicitly before any JSX moves.

This adds mountedEditFieldNames / mountedCreateFieldNames as pure functions over
the form values, plus the projections that apply them, covering all 22 gates
across the graph's 89 named bindings. No JSX changes, nothing imports them yet.

Three behaviours are probed against antd 5.29.3 rather than assumed, and the
tests pin all three:

- a mounted-but-unset field is EMITTED as a key holding undefined, at the root
  and inside credentials, so the projection emits rather than omits
- a Form.List row is NOT projected down to its mounted sub-fields, so env_vars
  rows pass through whole; filtering them would drop per-user values
- the two roots disagree on more than StdioConfiguration: edit gates url on a
  deny-list while create uses an allow-list, and create additionally gates the
  whole auth section on a non-empty transport

* refactor(ui): port the create key form off antd Form onto react-hook-form

antd hands onFinish exactly the fields mounted at submit time, so a collapsed
section contributes nothing to the request while the values typed into it
survive for re-expansion. react-hook-form reaches only one of those two
behaviours per shouldUnregister setting, so the store is kept intact and
projected down to the mounted set through an explicit mount registry.

MountedFormField carries the rest of the Form.Item contract the payload
depends on: defaults taken from each field's own declaration rather than a
blanket empty value, and help text that replaces the rule message instead of
sitting beside it.

The 60-case submit differential runs unedited against the port, joined by
cases for the writers outside the submit path, mounted-set validation, Enter
to submit, and switch coercion.

* test(ui): pin the mounted-field sets by membership, not array order

Two credential assertions compared the returned array with toStrictEqual
against a literal in source order, so they failed when two names were
swapped inside the source array even though the projected payload was
unchanged. Key order is not observable in the payload, so those two
assertions rejected a refactor that changes nothing a caller can see.

Route both through the same sorted() helper the other fifteen set
assertions already use. Membership keeps its teeth: deleting any one of
the eighty emitted field names still fails the suite, while reordering
two of them now passes.

* docs(ui): state the mounted projection's static-name limit at its export

The registry counts by name and the projection emits flat keys, so a
Form.List row and its per-row sub-fields, whose names are generated at
runtime, are never in the mounted set and go missing from the payload.
That is silent and it is correct for every static field around it, so the
contract belongs where the next consumer reads it.

* refactor(ui): cut the mounted field's explanatory comments to the contract limit

The mechanism the projection uses and the reason a helped field hides its
rule message are both derivable from the code, so they belong in the pull
request rather than in two places. What survives is the one thing no reader
can derive: that a runtime-generated name is silently absent from the payload.

* refactor(ui): drop the doc comment from MountedFormField

The static-name constraint it described moves to the PR description, where
it is not a second place to keep in sync with the code.

* fix(ui): rebuild nested and list paths in the mounted-field projection

projectMountedValues emitted one flat key per registered name, so a field
registered under a dotted path produced a literal "credentials.client_id"
key instead of a nested credentials object, and a field-array row produced
"env_vars.0.name" instead of a row.

Both are silent. buildEditServerPayload destructures credentials and passes
it to buildCredentials, which returns undefined for a non-object, so every
credential drops out of the payload while the code compiles and the existing
suites pass. reduceStaticHeaders loses its rows the same way.

Split each registered name on "." and rebuild the value, treating a numeric
segment as an array index so field-array rows come back as arrays rather
than objects keyed by digits. Nested credentials and both field-array sites
are the same defect and take the same fix.

* fix(ui): make mounted-field nesting opt-in via array names

The first cut split every registered name on ".", which diverges from antd.
antd's getNamePath is toArray, so a string name is a one-element path and
"a.b" is stored as a literal flat key; only an array name nests.

check_openapi_schema registers names taken from a live /openapi.json at
runtime, so the field set is an input rather than source. A spec property
containing a dot would have silently nested under the unconditional split
and changed that payload with nothing failing.

Accept string | readonly string[]. An array nests, with a numeric segment
as an array index, which is what the credential paths and the field-array
rows already use. A string stays one literal key. The registry keys by the
joined path but projects from the original shape, so the two cannot drift.
2026-08-18 23:44:12 -07:00
yuneng-jiang
14c05628b4
refactor(ui): host KeyLifecycleSettings in react-hook-form instead of antd Form (#37449)
The suppression said exercising this component requires a real antd Form.
That stopped being true once the create key form began driving it through
a bare mounted field, so the harness and the suppression go together.

Two of the auto-rotation cases were racing the Base UI select popup all
along: findByText resolves as soon as the option lands, while the popup
still carries pointer-events none until its open transition settles. The
antd harness happened to hide it, at 0 failures in 6 runs against 1 in 6
for plain state and 5 in 6 under a Controller, since each extra render is
another chance to catch the popup mid-transition. Both now take the same
PointerEventsCheckLevel.Never their sibling case already used.
2026-08-18 23:44:03 -07:00
yuneng-jiang
5a899f596b
refactor(ui): port the add model form off antd Form onto react-hook-form (#37446)
* fix(ui): restore the cache control Role and Index field hints

The add_model cache control editor lost both field hints when it moved off
antd Form.Item in #37392. "LiteLLM will mark all messages of this role as
cacheable" and "(Optional) If set litellm will mark the message at this index
as cacheable" went with the Form.Item tooltip props and neither string exists
in dashboard source any more. The Index hint was the only thing telling a user
that field is optional, so this is lost information rather than styling.

Both come back as shadcn tooltips beside their labels, matching how the
surviving switch-level hint is already rendered.

Also adds the payload characterization net this graph did not have. Before
this commit the seven suites over add_model and model_add held 37 cases, no
antd module mock, and zero toStrictEqual, so nothing pinned the submit
payload. AddModelPanel.integration.test.tsx drives the real panel, the real
antd store and the real prepareModelAddRequest, and asserts the object handed
to modelCreateCall.

It pins the distinctions only a strict assertion can see: litellm_credential_name
arrives as null from its initialValue while api_key, api_base, mode and
access_groups arrive as undefined, and team_id is absent entirely until the
Team-BYOK switch mounts it. It also pins the mount gate in both directions,
since a collapsed Advanced Settings drops both its keys and anything typed
into it while re-expanding restores them, and the empty-string skip, since a
cleared api_base must vanish rather than arrive as "".

Every fixture was captured from the running component rather than written by
hand. A 12-mutation battery over the bindings, the empty-string skip, the two
required rules and an added keepMounted all go red, each run gated on having
executed the expected case count.

* refactor(ui): port the add model form off antd Form onto react-hook-form

The Add Model form graph is shared by three antd hosts, so it only moves as
one piece: AddModelPanel, LlmCredentialsPanel and CredentialModal all mount
the same children. Form and Form.Item are replaced everywhere, and every
widget inside them is left alone, so the change is the binding layer only.

antd submits the mounted fields, react-hook-form submits its whole store. A
shared mount registry keeps that difference from reaching the request: each
field registers on mount, and the panel projects the store down to the
registered names before it builds the payload. shouldUnregister would have
been the other option, but it drops a collapsed section's typed values, so
re-expanding Advanced Settings would come back empty.

The antd rules modules are reused as-is through a thin validator adapter, so
the messages stay in one place rather than being reworded per field.

Advanced Settings held a Form.useForm() instance in a component that renders
no Form, which made ten imperative calls dead. They are removed rather than
translated, and the three behaviours they looked like they drove were checked
against the antd original first: invalid LiteLLM Params still blocks submit,
the pass-through toggle still leaves LiteLLM Params empty, and turning custom
pricing off then on still keeps the typed cost.

The existing 14 case payload net runs unedited against the port.

* test(ui): pin the three add model behaviours the dead form instance looked like it drove

Advanced Settings used to hold a form instance it never rendered, and the ten
imperative calls against it were dead. The inherited payload net covered none
of the three behaviours those calls appeared to own, so removing them looked
riskier than it was. These cases characterise what the antd original actually
did, checked against it before the port.

Invalid LiteLLM Params blocks the submit, which also closes the one mutation
the inherited net could not kill: dropping the JSON rule left all 14 green.
2026-08-18 23:43:54 -07:00
yuneng-jiang
963c7fb0d4
refactor(ui): port the create key form off antd Form onto react-hook-form (#37442)
* refactor(ui): port the create key form off antd Form onto react-hook-form

antd hands onFinish exactly the fields mounted at submit time, so a collapsed
section contributes nothing to the request while the values typed into it
survive for re-expansion. react-hook-form reaches only one of those two
behaviours per shouldUnregister setting, so the store is kept intact and
projected down to the mounted set through an explicit mount registry.

MountedFormField carries the rest of the Form.Item contract the payload
depends on: defaults taken from each field's own declaration rather than a
blanket empty value, and help text that replaces the rule message instead of
sitting beside it.

The 60-case submit differential runs unedited against the port, joined by
cases for the writers outside the submit path, mounted-set validation, Enter
to submit, and switch coercion.

* docs(ui): state the mounted projection's static-name limit at its export

The registry counts by name and the projection emits flat keys, so a
Form.List row and its per-row sub-fields, whose names are generated at
runtime, are never in the mounted set and go missing from the payload.
That is silent and it is correct for every static field around it, so the
contract belongs where the next consumer reads it.

* refactor(ui): cut the mounted field's explanatory comments to the contract limit

The mechanism the projection uses and the reason a helped field hides its
rule message are both derivable from the code, so they belong in the pull
request rather than in two places. What survives is the one thing no reader
can derive: that a runtime-generated name is silently absent from the payload.

* refactor(ui): drop the doc comment from MountedFormField

The static-name constraint it described moves to the PR description, where
it is not a second place to keep in sync with the code.
2026-08-18 23:16:35 -07:00
mateo-berri
f614f039c5 fix(ui): drop stale user search answers so Enter commits the current match
The Add Member modal and the Create Key owner picker both search users
server-side on a 300ms debounce with nothing sequencing the requests, so a
slow answer to an earlier, shorter search can land after the current one and
replace the list. With the first row highlighted at all times, Enter then
commits whoever sits on top of that abandoned batch, ordered newest account
first rather than best match.

Each search now takes a sequence number and only the newest one is allowed to
reach the option list or clear the spinner.
2026-08-18 23:00:47 -07:00
yuneng-jiang
8bb8525047
refactor(ui): extract the MCP server edit save payload into a pure builder (#37436)
* refactor(ui): extract the MCP server edit save payload into a pure builder

`handleSave` built the update payload inline across 276 lines, spreading
`...restValues` straight off a mounted-only `onFinish`. That makes the payload a
function of which fields happen to be mounted, and it leaves no seam to test the
shape without rendering the whole edit form.

Move the payload construction into `editServerPayload.ts` as
`buildEditServerPayload(values, ui)`, a pure function over the submitted values
plus the nine pieces of component state the handler reads. Failures become values
rather than early returns with a toast: the six error branches are a tagged union
that `editPayloadErrorMessage` maps back to the exact strings shown today, via an
exhaustive switch. `handleSave` keeps the network call, the OAuth token
persistence and its own try/catch.

This is a move, not a rewrite. To prove that, `editServerPayload.differential.test.ts`
holds a baseline machine-extracted from the pre-refactor function body by line
range, with the failure branches converted by exact string replacement. The
generator refuses to emit unless the slice is still present verbatim in the
source, every conversion matches exactly once, no toast call survives, and a
deliberately corrupted probe still trips that check. 59 scenarios run both
implementations and compare the payload object, its key order, and its serialised
bytes, so a re-ordering that leaves values untouched is caught too.

The duplicate local `AUTH_TYPES_REQUIRING_CREDENTIALS` is dropped in favour of the
identical exported list, and `reduceStaticHeaders` is shared with the create side.
Both were verified equal before reuse.

Behaviour is unchanged. The 466 pre-existing tests in the directory pass unedited.

* refactor(ui): type the MCP edit payload builder instead of Record<string, any>

The extraction created a new public signature, so it should carry a real
contract. buildEditServerPayload now takes EditServerFormValues and returns
EditServerPayload, both declaring every field the builder actually reads and
writes, with an unknown-valued index signature for the keys the form passes
straight through. handleSave is annotated too, so antd's untyped onFinish
value is narrowed once at the boundary rather than travelling as any.

Fields that arrive from the store with their own runtime validation
(static_headers, env_vars, credentials) stay unknown rather than being given a
narrower declared type the form does not actually guarantee. Values are not
run through a parser: the payload's serialised key order is part of the
contract this module exists to hold, and rebuilding the object would reorder
it.

The credentials assignment moves from two post-hoc mutations to a single
resolved entry, which keeps the payload readonly end to end and lands the key
in the same position in all four branches.

Behaviour is unchanged. The 59 differential scenarios still match the frozen
pre-extraction body on object, Object.keys order and JSON.stringify bytes, and
the mcp-servers suite is 525/525 across all 30 files. Three tsc probes confirm
the new types have teeth: a wrong payload assignment, a misspelled field read
and an invalid value each fail the type check.
2026-08-18 22:56:09 -07:00
yuneng-jiang
da2532a739
feat(ui): add mounted-field projections for the MCP server form graph (#37440)
* feat(ui): add mounted-field projections for the MCP server form graph

antd's onFinish reports exactly the fields mounted at submit time, and both MCP
server payload builders spread that object straight through. react-hook-form
with shouldUnregister false hands back the whole store instead, so a port needs
the mount set written out explicitly before any JSX moves.

This adds mountedEditFieldNames / mountedCreateFieldNames as pure functions over
the form values, plus the projections that apply them, covering all 22 gates
across the graph's 89 named bindings. No JSX changes, nothing imports them yet.

Three behaviours are probed against antd 5.29.3 rather than assumed, and the
tests pin all three:

- a mounted-but-unset field is EMITTED as a key holding undefined, at the root
  and inside credentials, so the projection emits rather than omits
- a Form.List row is NOT projected down to its mounted sub-fields, so env_vars
  rows pass through whole; filtering them would drop per-user values
- the two roots disagree on more than StdioConfiguration: edit gates url on a
  deny-list while create uses an allow-list, and create additionally gates the
  whole auth section on a non-empty transport

* test(ui): pin the mounted-field sets by membership, not array order

Two credential assertions compared the returned array with toStrictEqual
against a literal in source order, so they failed when two names were
swapped inside the source array even though the projected payload was
unchanged. Key order is not observable in the payload, so those two
assertions rejected a refactor that changes nothing a caller can see.

Route both through the same sorted() helper the other fifteen set
assertions already use. Membership keeps its teeth: deleting any one of
the eighty emitted field names still fails the suite, while reordering
two of them now passes.
2026-08-18 22:41:43 -07:00
yuneng-jiang
413fc7e517
fix(ui): restore the cache control Role and Index field hints (#37437)
* fix(ui): restore the cache control Role and Index field hints

The add_model cache control editor lost both field hints when it moved off
antd Form.Item in #37392. "LiteLLM will mark all messages of this role as
cacheable" and "(Optional) If set litellm will mark the message at this index
as cacheable" went with the Form.Item tooltip props and neither string exists
in dashboard source any more. The Index hint was the only thing telling a user
that field is optional, so this is lost information rather than styling.

Both come back as shadcn tooltips beside their labels, matching how the
surviving switch-level hint is already rendered.

Also adds the payload characterization net this graph did not have. Before
this commit the seven suites over add_model and model_add held 37 cases, no
antd module mock, and zero toStrictEqual, so nothing pinned the submit
payload. AddModelPanel.integration.test.tsx drives the real panel, the real
antd store and the real prepareModelAddRequest, and asserts the object handed
to modelCreateCall.

It pins the distinctions only a strict assertion can see: litellm_credential_name
arrives as null from its initialValue while api_key, api_base, mode and
access_groups arrive as undefined, and team_id is absent entirely until the
Team-BYOK switch mounts it. It also pins the mount gate in both directions,
since a collapsed Advanced Settings drops both its keys and anything typed
into it while re-expanding restores them, and the empty-string skip, since a
cleared api_base must vanish rather than arrive as "".

Every fixture was captured from the running component rather than written by
hand. A 12-mutation battery over the bindings, the empty-string skip, the two
required rules and an added keepMounted all go red, each run gated on having
executed the expected case count.

* refactor(ui): drop the explanatory comments from the add-model payload net

The repo bans explanatory source comments. The liveness-gate pairing the
second one described now lives in the test name instead, where a reader
deleting the paired case will actually see it.

* fix(ui): make the cache control field hints reachable without a pointer

SimpleTooltip renders its trigger as a bare span with no tabindex, so the
guidance behind it is mouse-only and the focus-visible ring classes it
already carries can never fire. The antd tooltips these hints replaced set
tabIndex null too, so this is an improvement on the original rather than a
restoration of it.

The primitive is shared by 20 files and is CLI-managed, so the trigger is
composed at the call site instead: a real button, an accessible name that
says which field it explains, and the icon marked aria-hidden.

Two tests cover it by tabbing to each trigger rather than counting keys,
so they keep working when a field is added between them. Swapping the
button for a span with role=button turns exactly those two red and leaves
the other five green.
2026-08-18 22:38:07 -07:00
yuneng-jiang
b9a267c693
refactor(ui): migrate the teams form graph off antd Form onto react-hook-form (#37417)
* test(ui): pin the teams create and update payloads before the form migration

The teams graph (Teams.tsx, TeamInfo.tsx and the MetadataKeyValueFields child they
share) is next for the antd Form to react-hook-form migration, and its submit payload
is a function of which collapsible sections the user happened to open. Nine sections
across the two files use the shadcn Collapsible, none of them passes keepMounted, and
Base UI unmounts the closed branch, so a closed section registers nothing and its keys
never reach the request body.

That matters beyond parity. /team/update reads the body with exclude_unset, so an
omitted key is never written, while an explicitly null team member budget key reaches
clear_team_member_budget_fields and nulls max_budget, budget_duration, rpm_limit and
tpm_limit on the shared budget row. antd cannot reach that today because the field is
unregistered rather than null. A port that seeds those fields or coalesces on the way
into the payload would turn a save with the section never opened into a silent clear.

The coverage that shipped with the team modal reached one of the four gating sections
on the create side and asserted key sets rather than the request body, so a null where
antd sent undefined would have passed. These cases assert both the raw payload and its
JSON round trip with toStrictEqual, which is what separates absent from null from
undefined, and they cover every gating section on both screens.

Also pinned, because each is a live behaviour a port can quietly change:

- the create path sends max_budget, tpm_limit and rpm_limit as strings, while
  team_member_budget arrives as a number through its normalize prop
- an invalid secret manager config blocks the create with its rule message suppressed
  by the item's help prop, so nothing is shown to the user
- the disable global guardrails switch is inert for a non premium user
- a value typed into a section survives collapsing and re-expanding it

Verified by adding keepMounted to all nine panels, which is the change a porter reaches
for on noticing that fields go missing: 35 of 118 went red, including every one of these
cases. The files were restored byte identical afterwards.

No production file changes here. 145 tests pass across the three files.

* refactor(ui): migrate the teams form graph off antd Form onto react-hook-form

Teams.tsx and TeamInfo.tsx were the last large antd `Form` graph in the
dashboard. Both now use `useZodForm` + `FormField`, with the shared
`MetadataKeyValueFields` child converted to a `useFieldArray`.

antd only returns the mounted registered fields from `onFinish`, so a
closed collapsible contributed no keys at all. react-hook-form keeps
unmounted values in the store (and `shouldUnregister: true` would lose
them on re-expand), so both forms project the submitted values through
the currently mounted section list before handing them to the existing
payload builders. Closed sections therefore still produce absent keys
rather than nulls, which matters at /team/update where an explicit null
clears the shared budget row.

Widgets that had no shadcn equivalent are replaced with the existing
shared ones: SearchSelect for the organization pickers, MultiSelect for
default member models, TagsInput for guardrails/policies, and a new
GuardrailsSelect for the grouped global/other guardrail dropdown.

* refactor(ui): forward the field ref to NumericalInput in the teams forms

staging turned NumericalInput into a forwardRef, so the teams graph can stop
dropping the react-hook-form ref on the floor.

* test(ui): pin the capability gate, required rules and guardrail kill switch

A mutation run over the ported teams forms found five survivors the payload
cases did not reach: the viewPolicies gate on both forms, the team name rule
on both forms, the guardrail kill switch resync, and the number coercion on a
typed model rate limit. Six cases close them.
2026-08-18 22:37:04 -07:00
yuneng-jiang
c94d692864
fix(ui): highlight the first member search match so Enter picks it (#37429)
* fix(ui): highlight the first member search match so Enter picks it

Moving the modal off antd Form swapped antd Select for Base UI Combobox, which
highlights nothing until an arrow key moves the cursor. Typing an email and
pressing Enter therefore selected no one, and the form submitted anyway, so
/team/member_add went out with member.user_email undefined.

autoHighlight="always" restores the behaviour antd had. The Enter key still
does not submit the form, which the existing test continues to cover.

* fix(ui): keep the always-highlight prop past the type check

Combobox.Root re-declares autoHighlight as boolean while the AriaCombobox it
wraps types it as boolean | "always". Only "always" highlights a list this
component filters server-side, since the plain flag highlights on Base UI's own
filtering pass, which filter={null} turns off.

Verified with next build, the same type check that failed in CI.
2026-08-19 03:31:23 +00:00
yuneng-jiang
a799351a5f
refactor(ui): extract the create-key payload builder out of create_key_button (#37397)
* refactor(ui): extract the create-key payload builder out of create_key_button

handleCreate built the POST /key/generate body inline by mutating the object antd
handed it, across roughly 170 lines mixing form values, twelve pieces of React
state and five sources that all fold into object_permission. Nothing could assert
that shape without rendering the whole modal.

The construction now lives in createKeyPayload.ts as a pure function returning a
tagged union, with 46 unit tests that run in single digit milliseconds and pin
whole payloads with toStrictEqual, following the createServerPayload pattern the
dashboard CLAUDE.md documents.

Behaviour is unchanged. The extraction was checked against the pre-extraction
handler with a differential harness over 50 input combinations, comparing the
built object, its key order and its serialised bytes, and eight mutations of the
new module were each confirmed to fail the committed tests.

* refactor(ui): align the key payload builder to the programme's entry-condition spec

Folds the duplicate-alias guard and the endpoint choice into the builder, so
KeyPayloadResult now carries three variants and handleCreate holds no payload
decision of its own. The two failure branches keep their original position
around toast.info and setIsModalVisible, and the builder checks the alias before
the agent selection, so the observable order is unchanged.

Pins the serialised wire shape as well as the object. The closed form registers
team_id at null through initialValue while organization_id has no initialValue
and stays undefined, so an untouched create sends seven of its eight keys.
Opening Optional Settings takes the object to 23 keys and the wire to nine. Both
directions of the definedness contract are now covered: undefined must not
become null, and null must not be dropped.

The earlier fixture fed a team_id the closed form cannot produce and pinned a
six-key wire as a result.

* refactor(ui): fold the service-account metadata write into its only caller

Removes assignServiceAccountId as a separate helper so there is no mutating
function available for reuse, which was the substance of the review finding. The
write now sits two lines below the JSON.parse that produced the value, so it is
visibly local and cannot reach a caller-owned object.

The write itself stays. Metadata has no validation rules, so a user can submit a
JSON body that parses to a primitive or an array. On a primitive the property
write raises a TypeError and the existing catch surfaces an error toast, and on
an array it leaves the array intact through JSON.stringify. A spread coerces
both to plain objects instead, silently creating a key from input the form
rejects today. Two tests pin those cases and go red against the spread.
2026-08-18 19:55:03 -07:00
yuneng-jiang
f27d88bd13
test(ui): repoint the e2e locators at the post-antd form controls (#37421)
* test(ui): repoint the e2e locators at the post-antd form controls

Nine Playwright specs went red after the tremor and antd removals, none of
them because the product broke. The specs selected on markup those libraries
owned: tremor's TextInput stamped data-testid="base-input", the antd toast
facade rendered .ant-notification, and the team member modal's email field was
an .ant-select. Removing the libraries deleted those hooks silently.

Repoint each onto a user-facing locator that survives the next migration:
getByLabel for the key name, the MCP tool argument and the two cache pricing
fields, getByRole("combobox") for the team member email search, and the toast
container for the add-model success message.

The pricing fields needed a source change to be reachable at all. antd's
Form.Item used to assign the field name as the input id and tie the label to
it; the react-hook-form rewrite renders FieldLabel with no htmlFor and lets
FormField generate an opaque control id, so both cache inputs lost their
accessible name and could only be told apart by placeholder, which they share.
Pin the id back to the field name and point the label at it.

* fix(ui): let FormField own the pricing field label instead of hand-rendering one

The previous commit gave the cache cost inputs an accessible name by rendering
a FieldLabel with htmlFor next to the FormField. FieldLabel forwards Label's
props, and Label only accepts children, so next build failed type checking.

FormField already renders a label wired to the control id it generates, and
FormField.test.tsx covers that association, so passing label through is both
type-safe and less markup. The read-only branch keeps its plain FieldLabel,
which has no control to point at.
2026-08-19 02:41:05 +00:00
yuneng-jiang
1bc1a5ed81
refactor(ui): migrate the key edit form off Ant Design onto react-hook-form (#37398)
* refactor(ui): migrate the key edit form off Ant Design onto react-hook-form

The key editor was the last antd Form in the key flow. antd's store decided
the saved payload implicitly: onFinish reported whatever mounted Form.Items
happened to be registered, so a control could stop feeding the request
without anything failing.

The form now runs on react-hook-form with the dashboard's own field
primitives, and the payload is projected explicitly in keyEditFormValues so
every saved key is written out by name. That reproduces the old payload
exactly, including the keys antd sent holding undefined and the two
role-gated keys it dropped entirely when the field was not mounted.

The existing suite is kept as the contract and passes unedited apart from
the selects, whose queries moved from Ant Design class selectors to roles
and labels. MultiSelect now forwards a per-option disabled flag, which the
Models select needs to keep greying out individual models once the
all-proxy-models sentinel is picked.

* fix(ui): keep the key edit prompts control gated behind premium

The antd control carried disabled={!premiumUser} and a tooltip saying
prompts by key are premium. The port kept the premium placeholder but
dropped the gate, so a non-premium admin could type a prompt and have the
whole save rejected by the endpoint.

TagsInput had no disabled prop at all, which is why the gate could not
survive the port; it now takes one and passes it to the combobox. An audit
of every disabled expression against the antd original shows this was the
only gate lost.

Also pins the two payload keys that are assembled in the submit handler
from React state rather than bound fields, budget_fallbacks and
tag_rpm_limit. A field-driven suite cannot see them, and tag_rpm_limit's
only previous appearance was an empty map that reads the same whether the
assignment works or is deleted. The cases were written against the antd
implementation by another lane and are added unchanged.
2026-08-18 19:40:37 -07:00
yuneng-jiang
cb5f158023
test(ui): characterize the create key form payload contract (#37405)
The create key suite mocked antd wholesale and swapped in a fake form
store whose onFinish spread every value it had ever seen, which is the
inverse of what rc-field-form does: real antd reports only the mounted
registered fields, so anything inside a closed collapsible is absent
from the body rather than present and null. Fourteen children were
stubbed to () => null on top of that, so the suite passed no matter
what the form would actually submit and could not protect a port off
antd.

The integration test now renders the real tree and stubs only the
network boundary, and a submit payload contract block pins the exact
body with toStrictEqual so null, undefined and absent stay distinct.
It covers all sections closed, Optional Settings alone, all open, and
each nested section opened by itself, plus the collapse and re-expand
path that antd's store survives and a shouldUnregister port would not.

The genuinely unit-level logic, fetchTeamModels and fetchUserModels,
moves to a plain unit test that mocks modelAvailableCall alone.

getOpenAPISchema stays reachable through a stubbed global fetch rather
than the networking module mock, because networking imports jsonFields
from check_openapi_schema and that module imports getOpenAPISchema back,
so importOriginal binds the real export through the cycle.
2026-08-18 19:40:23 -07:00
yuneng-jiang
63f740f511
test(ui): pin the MCP server edit save payload before the form migration (#37404)
`mcp_server_edit` builds its update payload from `validateFields()`, so the
request body is whatever antd had mounted at the moment Save was pressed.
Nothing asserted that body, and the existing suite has no `toStrictEqual`
anywhere, so a key appearing, disappearing, or arriving as null instead of
undefined was invisible.

This adds a characterization net that drives the real component tree and
captures the exact object handed to `updateMCPServer`. Every case is pinned
with `toStrictEqual`, which is the only matcher that separates the three
states a key can be in: absent, present-as-undefined, and present-with-value.
That distinction is the whole point here, since a gated-off field and a
mounted-but-empty field currently differ, and `entra_obo` versus plain
`token_exchange` is exactly that difference in the wild.

19 cases cover the transports, all ten auth types, the credential subsets each
one contributes, and the tool allowlist gate. Values were captured from the
component as it behaves today rather than written from reading the source, so
the file records current behaviour instead of intended behaviour

A six-mutant battery run against the payload builder kills 6/6, each run
executing all 19 tests: leaking an audit field, deleting a binding, removing a
gate, turning a flat key into a lodash path, swapping undefined for null, and
swapping an empty-array default for null

Two explicit assignments in the payload turn out to be dead. `alias` and
`mcp_access_groups` are both already supplied by the `...restValues` spread
above them, so removing either line changes nothing. Left in place here and
noted for the payload extraction that follows
2026-08-18 19:40:02 -07:00
ryan-crabbe-berri
ec6f1c1a56
refactor(ui): codemod the antd Tooltips outside form files onto the shadcn atom (#37402)
* refactor(ui): codemod the antd Tooltips outside form files onto the shadcn atom

The atoms/Tooltip wrapper gains an optional side and renders its children without a popup when there is no content, which covers antd's placement prop and its title={undefined} escape hatch. A TypeScript-AST codemod then rewrites every antd Tooltip that does not sit in a file still using antd Form, since those files get their primitives swapped as part of the react-hook-form migration.

* refactor(ui): fold the atoms Tooltip into the ui tooltip primitive as SimpleTooltip
2026-08-19 02:14:37 +00:00
yuneng-jiang
5012d11a18
refactor(ui): style the logging settings from semantic tokens (#37385)
team/LoggingSettings.tsx carried 34 hardcoded palette classes and
common_components/PremiumLoggingSettings.tsx another 9, so both render
light-only regardless of theme. Map the neutrals onto foreground,
muted-foreground, muted and border, the red affordances onto destructive,
and swap the hand-rolled chips for the shadcn Badge primitive.

The three event-type options carried decorative green, red and blue dots.
The design system has no success or info token, so the dots are dropped
and the option labels, which already say "Success Only", "Failure Only"
and "Success & Failure", carry the meaning on their own.

This is groundwork, not a visible change: nothing in the dashboard ever
applies the .dark class today, so the dark palette is unreachable. The
files no longer hardcode colour and will follow the theme once one exists.
2026-08-18 18:52:41 -07:00
yuneng-jiang
b19d59be09
refactor(ui): move the model info edit form off antd Form (#37392)
* test(ui): characterize the model info and cache control submit payloads

Pins the antd behaviour these forms have today, ahead of moving them onto
react-hook-form: the full model info PATCH body, the sticky touched-field
semantics that decide which pricing keys ship, the mounted-only cache control
keys, and the string-typed injection point index.

* refactor(ui): move the model info edit form off antd Form

The deployment edit form on the model info view now runs on react-hook-form
with a zod resolver and shadcn controls, extracted into ModelInfoEditForm so
the view keeps the payload builder and the form keeps the fields.

Cache control injection points become a presentational value/onChange child,
which lets the model info view host it through react-hook-form while the add
model form keeps hosting it through antd. That child never wrote to a real
store on either side: it registered under cache_control_points while both
parents read cache_control_injection_points, so its form prop was inert.

antd marks a field touched on change and never clears it, and neither
touchedFields nor dirtyFields reproduces that, so the four pricing keys that
gate on it track first change explicitly.

The PTU rules move from antd validator wrappers to pure predicates that both
surfaces share, since the add model form still feeds the wrappers to its own
antd form.

* refactor(ui): trim comments and type the model record prop on the edit form

Cuts the explanatory comments that the house rules do not allow, keeping
only the three that record non-obvious library behaviour plus the eslint
directive, and narrows the modelData prop to the two fields the form reads.

Corrects the claim in 5b7ecede4e that the cache control child registered
the wrong key. At the staging tip the Form.List registration is on
cache_control_injection_points and is live, which is why this PR rehosts
it into advanced_settings. The dead part is the three
getFieldValue("cache_control_points") readers, whose key nothing
registers, so updateCacheControlPoints dereferences undefined and the
caught error reaches the console on every role, index or remove change.
2026-08-18 17:40:49 -07:00
ryan-crabbe-berri
bb8324c119
refactor(ui): drop @tremor/react and the theming scaffolding it needed (#37394)
The last tremor component import left the dashboard when the primitive
sweep merged, so the package, its v3 compatibility shim, its @theme token
block and the palette safelist it needed at runtime all have no consumer.

Removing the safelist is what shrinks the shipped stylesheet: tremor built
class names at runtime, so Tailwind had to emit every bg/text/border/ring/
stroke/fill utility across 22 palettes and 11 shades in case one was used.
Nothing in the app constructs a class name that way any more, so the
scanner finds every utility on its own.

The date-fns overrides pin also goes. It only existed because tremor and
react-day-picker@8 peered on date-fns 3 while Base UI wanted 4, and the
lockfile still resolves a single hoisted 4.4.0 without it.
2026-08-18 17:38:12 -07:00
yuneng-jiang
564ea1cf73
feat(ui): add success, warning and info status tokens (#37393)
The dashboard had no shared tokens for non-destructive status colours, so
components reached for raw Tailwind shades instead. Add --success, --warning
and --info alongside the existing --destructive, in both :root and .dark, and
register them in @theme inline so the usual utilities resolve.

Light values are picked for legibility as foreground text rather than by
copying a fixed shade number. Tailwind's ramps are not perceptually aligned
across hues, so amber-600 and green-600 sit at 66.6% and 62.7% lightness and
fail WCAG AA on white (3.19:1 and 3.22:1). green-700, amber-700 and blue-600
land at 52.7%, 55.5% and 54.6%, the same band as --destructive at 57.7%, and
clear AA. Dark mode uses the -400 shades, matching --destructive.

The .dark values are populated even though nothing can apply that class yet.
They are the artifact the later theme switch work will turn on.

Alert moves its info and warning variants onto the tokens. The tint is /5
rather than /10 because /10 drops both below AA. The error variant keeps its
existing shades: it involves no new token, and its current 9.21:1 is better
than anything the token form would give it.
2026-08-18 17:36:10 -07:00
Mateo Wang
55777d0e80
Merge pull request #35110 from shivijain2323/feature/bedrock-mantle-quota-project-itr1
feat(proxy): add project-level ITPM and OTPM quotas
2026-08-18 16:54:33 -07:00
ryan-crabbe-berri
669c1334b4
refactor(ui): move the agent, guardrail, prompt, policy and skill forms off tremor (#37320)
* refactor(ui): move the agent, guardrail, prompt, policy and skill forms off tremor

The agent info Save Changes button used to rely on tremor's implicit
submit inside the antd Form, so it now carries an explicit type="submit".
Every converted TabsContent is keepMounted to keep tremor's always-mounted
panel semantics, pinned by a new guardrail info test. Prunes the tremor
no-restricted-imports suppressions these nine files no longer need.

* fix(ui): keep the line tab strip on the agent, guardrail and prompt info views

tremor's TabList defaulted to the line variant while shadcn's TabsList
defaults to the filled pill, so the bare conversion turned three underlined
tab strips into segmented pills. Restores the line variant plus the bottom
border and the tab padding the strips used to have.

* test(ui): pin the agent settings submit and the prompt raw json tab

Agent Settings only saves because Save Changes carries an explicit
type="submit" now that the button is a Base UI button, so a test drives the
edit and asserts the patch call fires. The prompt info tabs are keyed by slug
now, which also makes Raw JSON render for prompts with no template, so a
second test renders that case and asserts the serialized response is visible.
2026-08-18 23:40:44 +00:00
ryan-crabbe-berri
087d82ffca
refactor(ui): move the model info view and pass-through endpoint forms off tremor (#37308)
* refactor(ui): move the model info view and pass-through endpoint forms off tremor

The pass-through settings form's Save button relied on tremor's implicit
submit, so it now carries an explicit type="submit" because the Base UI
button defaults to type="button". The include-subpath switch inside the
antd Form.Item is wired through onCheckedChange plus form.setFieldsValue,
since the Base UI switch does not read the onChange antd injects. Both
tab strips keep every panel mounted so the edit forms survive a tab
switch, pinned by a new mount-contract test on the model info view. Also
prunes the six tremor no-restricted-imports suppressions these files no
longer need.

* test(ui): pin the model info overview panel to its own dom node across tab switches

* fix(ui): keep the line tab strip on the model info and pass-through views

Both tab strips were bare tremor TabLists, which defaulted to the line
variant, so the straight rename turned them into filled segmented pills.
They now use the same full-width line strip the agent, guardrail and
prompt info views ship.
2026-08-18 23:19:23 +00:00
yuneng-jiang
1a1467b9d6
Merge pull request #37381 from BerriAI/litellm_/elastic-goldstine-104755
refactor(ui): move the MCP tool test form off antd
2026-08-18 16:13:32 -07:00
yuneng-jiang
673f036680
Merge pull request #37376 from BerriAI/litellm_/funny-cerf-33d2bd
refactor(ui): move the model alias manager onto design tokens and shadcn controls
2026-08-18 16:11:42 -07:00
yuneng-jiang
edf3167f79
Merge pull request #37383 from BerriAI/litellm_/competent-lewin-1c8fd9
refactor(ui): move the team member search modal off antd Form
2026-08-18 16:11:28 -07:00
yuneng-jiang
32b9eeed38
Merge pull request #37372 from BerriAI/litellm_/modest-hodgkin-5775d7
fix(ui): show select labels on the trigger instead of raw values
2026-08-18 16:11:26 -07:00
mateo-berri
c435c25da2 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_pr35110_itpm_otpm
# Conflicts:
#	type-discipline-budget.json
2026-08-18 16:11:12 -07:00
Yuneng Jiang
c2e0daa50a
fix(ui): reseed the MCP tool test form when the schema changes
The antd form reset on [form, actualSchema, tool], so it reseeded whenever
the schema changed and not only when a different tool was picked. Keying the
migrated form's remount on tool.name alone narrowed that: a same-named tool
whose schema changed would have kept its old indexed values and submitted
them under the new schema's keys, and any field the new schema added would
never get its default. The key now covers the schema content as well.

Reachability, so nobody reads more into this than is there: selectedTool is a
state snapshot set on click, so this is not reachable through the current
parent. It is a latent divergence rather than a live bug, and it is fixed
because the contract for this migration is zero functional change.

The schema default helpers move into the pure module beside the other
argument logic. They arrived carrying the original's explicit any and built
their result by mutating it; they are now typed with unknown, built by
spread, and covered directly by unit tests rather than only through a render.

Also takes the last five hardcoded neutrals onto tokens. The earlier pass
scanned the gray family only and did not see the slate ones in the tool name
chip, which had no dark variant. Counting colour utilities that have no
dark: counterpart reads 0 for this file now, against 64 before the migration.
2026-08-18 15:59:36 -07:00
Yuneng Jiang
09ad62a40d
fix(ui): stop the placeholder option clearing the picked member identity
The Base UI combobox only renders a selected value that is present in its
item list, so the port synthesizes an item for the current value when the
search results no longer contain it. That synthetic item carried an empty
user, and selecting it ran the same handler as a real result, wiping both
the email and the user id before submit.

The synthetic item now carries no user at all and the select handler
ignores it, so reselecting the value already in the field leaves both
identity fields alone. antd needed none of this: its Select renders a
value that is absent from its options.
2026-08-18 15:58:16 -07:00
Yuneng Jiang
c71b6ed51b
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/competent-lewin-1c8fd9 2026-08-18 15:49:12 -07:00
Yuneng Jiang
e8f698ad65
test(ui): pin the section-gated team create and update payloads
The team create and edit forms send a different set of keys depending on
which collapsible sections the user opened, because a closed section is
unmounted and its values never reach the request. Nothing covered that,
so a form rewrite could change the request body without failing a test.

Pins the exact key set the create form sends with every section closed,
the keys Additional Settings adds once opened, and that a value typed
then re-hidden is dropped while a reopened one is restored. Does the same
for the team member and search tool sections on the edit form, asserting
absence at the wire level rather than just comparing values.

Also hardens two option queries in the member modal suite onto the option
role, and lifts the duplicated mock seeding in the team info suite into
one function both blocks call.
2026-08-18 15:49:05 -07:00
yuneng-jiang
657ded533c
test(ui): raise vitest test and hook timeouts for CI headroom (#37370)
* test(ui): raise vitest test and hook timeouts for CI headroom

The UI unit suite runs about 3x slower on the CI runner than locally, which
put the slowest cases right on the 30s per-test limit. TeamInfo's pass
through routes case takes ~8s locally and has been failing on staging at the
timeout across consecutive runs even though it passes reliably when run
directly.

Raise testTimeout to 60s and set hookTimeout to 30s so the current slow cases
have headroom. This is a stopgap while the suite gets split into proper tiers,
not a fix for the underlying per-test cost.

* test(ui): query agent form panels with findByRole like the rest of the file

The panel helper was the only synchronous query in add_agent_form's
integration test; every other lookup already retries via findBy. On the CI
runner the second case has been failing with "Unable to find an accessible
element with the role button and name /Cost Configuration/" against a modal
whose body had not rendered.

Make the helper retry like its siblings and await it at each call site.
2026-08-18 22:41:16 +00:00
Yuneng Jiang
aa95809c82
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/elastic-goldstine-104755 2026-08-18 15:38:40 -07:00
Yuneng Jiang
fc32eb081a
refactor(ui): move the MCP tool test form off antd
The tool test panel drove its argument fields through an antd Form, so the
call payload was whatever rc-field-form happened to have mounted. It now runs
on react-hook-form with shadcn controls, and the payload itself lives in
toolCallArguments.ts as a pure function of the schema fields plus the entered
values.

Fields bind by index rather than by name, because an MCP tool's JSON schema
can name a property anything: a key containing a dot would be one flat key to
antd but a nested path to react-hook-form. Binding to args.0, args.1 and
zipping back to the real keys at submit time keeps the emitted arguments
identical whatever the server calls its properties.

Coercion, the blank filter, the required and JSON rules, and the params
wrapper for nested-object schemas all keep their previous behaviour, and the
neutral colours in the panel move onto tokens so it reads correctly in dark
mode.
2026-08-18 15:38:38 -07:00
ryan-crabbe-berri
eef41c9987
refactor(ui): move the admin, SSO, SCIM, alerting and fallback forms off tremor (#37315)
* refactor(ui): move the admin, SSO, SCIM, alerting and fallback forms off tremor

Swaps the tremor Button, Card, Callout, Grid, Divider, Text, Title, TextInput, Table parts, Badge, Icon and Switch in these nine files for the shadcn layer and lucide icons, keeping antd in place. The SCIM create-token button keeps an explicit type="submit"; the two SCIM copy buttons sit outside the antd form so they stay plain buttons, and the alerting form's Enterprise Feature upsell button is a link wrapper rather than a save action, so it deliberately stays type="button" while the form keeps its own Update Settings submit. The teal login callout on the admin panel maps to the info Alert variant since no teal variant exists. Prunes the nine tremor no-restricted-imports suppressions these files no longer need.

* fix(ui): keep the alerting settings name column left aligned

tremor's TableCell hardcoded text-left, so the align="center" attribute never took effect. The shadcn cell has no text-align of its own, so translating that attribute into text-center would have centered the field name and its description for the first time.

* fix(ui): restore the CloudZero key reveal toggle and the SCIM divider gap

tremor's TextInput drew its own show/hide button whenever the type was
password and the field was not disabled, so the straight pass-through to
the plain shadcn Input silently deleted that affordance from the CloudZero
API key field. Rebuilds it with the InputGroup reveal pattern that
email_settings.tsx already uses, behind a small local control so the antd
Form.Item keeps its id, value and onChange wiring and the field still
matches its shadcn sibling in the same form.

Also puts the SCIM separator back on my-6: tremor's Divider was
"w-full mx-auto my-6", and the conversion shipped my-4, tightening that
gap by 8px on each side. The disabled SCIM token field stays a bare Input
because tremor suppressed its toggle when disabled too.
2026-08-18 22:36:35 +00:00
Yuneng Jiang
c1b2df8e4f
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/funny-cerf-33d2bd 2026-08-18 15:35:37 -07:00
Yuneng Jiang
fae700da36
refactor(ui): tokenise the access group selector and drop its stale binding note
The selector takes value and onChange, which is exactly what lets it sit
inside both an antd Form.Item and a react-hook-form FormField, so the doc
bullet naming only antd was about to describe half the truth. The props
interface already states the contract.

That bullet was also the only Form.Item match in the file, and it has twice
inflated the migration's canonical tag count, which now needs no subtraction.

Its four hardcoded colours move to tokens for the same reason as the rest of
the sweep. Its antd Select stays.
2026-08-18 15:35:35 -07:00
Yuneng Jiang
78670789a0
refactor(ui): replace the agent form's last antd Form.Item with its own field label
The agent forms moved to react-hook-form in #37357, which deliberately kept
the antd Select for the agent type picker because its dropdownRender footer
and two-tier options have no shadcn equivalent. The Form.Item wrapped around
it survived as a side effect rather than for that reason: it carries no name
and no rules, and the file renders no antd Form at all, so it bound nothing
and validated nothing.

It was also the only label in the file rendering antd's required asterisk,
while AgentFormField renders genuinely required fields without one. Moving it
to the file's own Field, FieldLabel and labelWithHint makes it match, and
gives the label a control to point at.
2026-08-18 15:35:35 -07:00