Commit graph

10206 commits

Author SHA1 Message Date
G30
a7d81ebdcf
fix: keep a user's chats listed in the admin chats view after deleting one (#30604) 2026-09-23 23:32:23 -04:00
Classic298
869a5b1035
fix: stop voice mode listening after leaving the chat page (#30405) (#30417)
Leaving the chat with voice mode active (Admin Panel, Workspace, Notes) left a hidden recorder running in the tab. The voice mode flag stays on when the chat page unmounts, so the overlay's teardown restarted recording on the destroyed component, which kept transcribing speech and sending prompts from other pages. Returning to the chat also opened an extra microphone stream that was never closed.

The overlay now marks itself destroyed on teardown and never starts or restarts recording after that, so leaving the chat shuts the microphone down fully.

Resetting the voice mode flag in the chat page cleanup was considered, but it changes behaviour for the controls panel on return and still runs after the overlay's own teardown.

Verified in Chromium with a fake microphone (14s per phase, transcription requests / live mic tracks):

| Phase | before | after |
|---|---|---|
| Call on chat | 3 / 1 | 3 / 1 |
| After moving to Workspace | 3 / 1 | 0 / 0 |
| Voice mode reopened | 3 / 3 | 3 / 1 |

Fixes #30405
2026-09-23 23:30:30 -04:00
CosmicFlavour
53c7ed3370
i18n: add missing french translation in fr_FR (#30403) 2026-09-23 23:30:05 -04:00
Aleix Dorca
c86c46e034
i18n: Update catalan translation.json (#30415)
* Update catalan translation.json

* i18n: Update catalan translation.json and typos
2026-09-23 23:29:51 -04:00
Classic298
d66e5dc403
fix: make the admin analytics tab scrollable inside the settings modal (#30429)
The analytics dashboard renders inside the settings modal, whose tab area clips overflowing content. Unlike the other tabs, analytics had no scroll area of its own, so on any instance with more than a handful of models or users the bottom of the model and user ranking tables was cut off and could not be reached.

The analytics wrapper now scrolls vertically, using the same scroll classes as the other settings tabs. Verified in a browser with 30 models and 45 users at 1400x900, 1280x720 and 700x900: before, wheel scrolling moved nothing; after, both tables scroll to their last row with a single scrollbar and no horizontal overflow.

Fixes #30428
2026-09-23 08:46:22 -05:00
Classic298
24c01bd76d
fix: stop voice call hanging in "speaking" when TTS fails (#30372)
Some checks are pending
Python CI / Ruff Format (3.11) (push) Waiting to run
Python CI / Ruff Format (3.12) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args: free_disk:false name:main suffix:]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args: free_disk:false name:main suffix:]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Waiting to run
Create and publish Docker images with specific build args / merge (map[name:cuda suffix:-cuda]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:cuda126 suffix:-cuda126]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (, main) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda126, cuda126) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-ollama, ollama) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-slim, slim) (push) Blocked by required conditions
Frontend Build / Unit Tests (push) Waiting to run
Create and publish Docker images with specific build args / merge (map[name:main suffix:]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:ollama suffix:-ollama]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:slim suffix:-slim]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / notify-helm-charts (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda, cuda) (push) Blocked by required conditions
Frontend Build / Format & Build (push) Waiting to run
When the configured TTS provider fails during a voice call, the text answer arrives but the overlay stays in "speaking" with no audio and no error until the user taps to interrupt. The sentence that failed never reaches the audio cache, so the playback loop re-queues it every 200 ms forever.

A failed sentence now marks its message as failed. The playback loop drops that message's unplayed sentences, the rest of the turn requests no more TTS, and the overlay returns to listening once the text finishes. The OpenAI-compatible path now shows the provider error once per turn, the same way Read Aloud and the Kokoro path already do. The next turn tries TTS again.

Failure is tracked per message so an outage (the report shows 16 parallel requests all failing) costs one toast and no further requests. The trade-off is that a one-off failure mutes the rest of that reply.

Verified with the real fetch/playback code in a harness: base loops forever with no toast; with the fix, one toast, the loop ends, later sentences are not requested, a new turn plays normally, and a late failure from a previous turn does not affect the next one.

Fixes #30052
2026-09-22 16:57:10 -04:00
Classic298
15f350b417
fix: keep an open chat's tools when clicking a folder name (#30376)
Clicking a folder name in the sidebar while a chat was open switched that chat to the folder's default model for a moment before the page changed. That reset its tools and skills to the folder model's set and saved them as the chat's draft, so on returning to the chat the folder model's tools were shown and sent with the next message. The same happened to a chat just started from the home page.

The folder's default model is now only applied while the chat has no messages yet, the same rule new chats already follow when a folder page opens. Folder pages, new chats in a folder and editing a folder's default model behave as before.

Verified in a browser against a mock upstream: on dev the next request after the folder click carried the folder model's tool_ids and skill_ids; with the fix it carries the chat's own model's set, for both an existing chat and one started from the home page.

Fixes #30226
2026-09-22 16:47:09 -04:00
Classic298
d603b57995
fix: FireFox regional setting (#30377)
* fix: keep en-US as the last i18n fallback when a stored locale has no bundle

Settings labels rendered as raw keys such as settings.admin.connections.title
instead of "Connections" from the second page load on, while every other
label looked fine.

On the first visit the language detector saves whatever the browser reports
into localStorage, including bare codes like "en" that have no locale bundle.
The same value is saved by ?lang=en or DEFAULT_LOCALE=en. On the next load
that stored value became the only fallback language, so the missing bundle
left nothing to fall back to. Plain keys still looked right because the key
is the English text; only the settings.* keys have a distinct value.

en-US now stays at the end of the fallback list whenever a stored locale is
passed in. The stored locale keeps precedence, real locales are unaffected,
and the en-US bundle was already loaded for every language for the settings
merge, so there is no extra request.

Fixes #30348

* fix: match browser language codes to a locale bundle

Firefox reports German, Dutch and Polish as bare codes (de, nl, pl), both Chrome and Firefox report Japanese as ja, and Chrome in Latin America reports es-419. No bundle is keyed by any of these. The page layout used to match them on the first visit, but since 67ac1a4e9 (0.11.4) awaits the i18n init, the detector has already cached the raw code by the time that check runs, so it never does. Those users get an English interface, a language dropdown with nothing selected and English date formats, and the cached code keeps them there.

The detector now maps a reported code onto a bundle before anything else sees it. A code we ship stays as it is, matched case-insensitively. Otherwise the language's xx-XX bundle is used when there is one, so de and de-AT become de-DE, es-419 becomes es-ES, and fr becomes fr-FR even though fr-CA comes first in the list. A bare code with no xx-XX bundle takes the first one for its language, so ja becomes ja-JP. Any other regional code passes through untouched, so zh-HK is not sent to Simplified zh-CN, and no locale that works today changes.

Restoring the layout check would leave every browser that cached a raw code under 0.11.4 stuck. Matching in the detector migrates them on their next load, because the matched code is what gets cached. {{USER_LANGUAGE}} reports that matched code too, so es-419 users now send es-ES.

Verified against the real bundles on first and repeat loads, including the untouched cases fr-CA, pt-BR, zh-TW, zh-HK, ar-BH, en-GB and uz-Latn-UZ.
2026-09-22 16:46:58 -04:00
G30
f2702e1f0f
fix: fall back to the user's system prompt when the chat's own is cleared (#30333) 2026-09-22 11:37:59 -04:00
MiRHaDi
845b791609
i18n: translate Persian counters and preserve prompt variable (#30329) 2026-09-22 11:37:48 -04:00
Badar Rahman
69fcad0c04
i18n: complete Indonesian (id-ID) translations (#30328)
Co-authored-by: DarRahman <darrahman@users.noreply.github.com>
2026-09-22 11:36:35 -04:00
Classic298
fecbeac7d1
Merge pull request #30354 from Classic298/i18n-en-us-fallback
fix: keep en-US as the last i18n fallback when a stored locale has no bundle
2026-09-22 11:34:44 -04:00
Nas
9530cc15c1
i18n: restore placeholder names that were translated or lost their braces (#30326)
{{ models }} -> {{ modelli }} etc. in 10 locales, {{name}} -> {{nombre}} in
gl-ES, {{file}} -> {{arxiu}} in ca-ES, and single/unbalanced braces in kab-DZ,
nb-NO and ca-ES. 22 strings in 13 locales, tokens only.
2026-09-21 15:16:04 -04:00
Classic298
46ea826e01
refac: keep rendered diagrams and SVG on same-origin resources (#30271)
Mermaid diagrams and the shared SVG sanitizer now accept only same-origin and data: references. Image URLs, class styles and directive config are checked on the parsed diagram before it renders, and the sanitizer drops attribute values and stylesheet rules that point at another origin. use elements keep local #id references only.

SVGPanZoom and the SVG file preview call the shared sanitizer instead of keeping their own configs, so SVG artifacts and uploaded SVG files follow the same rule. Uploaded SVGs that reference external sprites now render those parts blank, which is the point of the change.

A diagram that references an external resource now reports an error instead of rendering, and an SVG artifact keeps everything except the rules that reference one.
2026-09-21 15:15:28 -04:00
Timothy Jaeryang Baek
ee3ece1e2b refac 2026-09-21 14:43:04 -04:00
Timothy Jaeryang Baek
c07fa08b99 refac 2026-09-21 11:45:49 -04:00
Timothy Jaeryang Baek
bf476452a8 chore: format 2026-09-21 11:35:30 -04:00
G30
b988f06ce0
fix: drop every local reference to a channel message that was deleted (#30314) 2026-09-21 11:32:14 -04:00
G30
4b61b86a83
fix: apply the knowledge File content filter on the first click (#30211) 2026-09-21 11:30:42 -04:00
Timothy Jaeryang Baek
9fba2843b1 refac 2026-09-21 11:24:40 -04:00
Timothy Jaeryang Baek
30881dbcc9 refac 2026-09-21 11:09:09 -04:00
G30
9688d32764
fix: keep the background image chosen while creating a folder from the sidebar (#30218) 2026-09-21 10:36:49 -04:00
G30
bc50026f7b
fix: make the Delete Chat shortcut work whenever a chat is open, not only while its sidebar row is rendered (#30165) 2026-09-21 10:21:19 -04:00
Timothy Jaeryang Baek
a9541c18ca refac 2026-09-21 10:19:59 -04:00
Classic298
488b3c571c
perf: stop note co-editing echoing every remote update back to the server (#28185)
Every client in a note re-broadcasts each update it receives, with a full content snapshot attached, and the server appends each echo to the document log and writes the note again. Traffic and note writes therefore scale with the number of people who have the note open: every extra participant adds one more full echo of every keystroke.

Remote updates are now applied with the `'server'` origin the state path already uses, which the local listener ignores, so the echo stops. The echo did carry one thing worth keeping: the receiving client holds the merged document, which the sender had not seen yet, so each receiver now sends a content-only message once the edits settle, debounced 500ms, and flushes a pending one when the editor is torn down. The backend accepts an update message with no `update` field for that case, where it previously raised and dropped the save.

Replaying keystrokes at 120ms with a real Yjs document, socket messages fall 48.8% with two clients, 65.6% with three and 79.2% with five, with byte counts tracking the same on notes up to 50KB. Server-side update appends drop by a factor of the client count. The cost is one snapshot upload per receiving client per typing pause, which the server's own debounce then collapses into a single extra note write however many people are watching. A snapshot has to come from a client because the server cannot rebuild the markdown, HTML and JSON shape the note record stores. It also moves the merge 500ms later than the echo delivered it, so if two edits cross on the wire and every editor then loses its connection and closes inside that window, the note keeps what it was last sent and one of the two edits is lost. A client still connected at teardown flushes its pending snapshot, and any other editor left in the note closes the gap.
2026-09-21 08:53:27 -04:00
G30
d9ea46f92d
fix: hide Insert into note on a note the viewer may only read (#30223) 2026-09-21 08:32:17 -04:00
G30
440d13e198
fix: refresh the prompt editor after a version is set as production (#30233) 2026-09-21 08:31:51 -04:00
G30
c2f11edb2f
fix: keep a workspace model's base model when cloning it from the admin Models settings and hide Clone for arena models (#30080) 2026-09-21 08:15:12 -04:00
Classic298
f5967aebd7
fix: restore chat input draft after reload in chats started from the home page (#29762)
Typing a message, uploading a file or toggling web search in a chat started from the home page was lost on reload: the input came back empty and the uploaded files were gone for good.

Drafts are kept per chat in sessionStorage, but the key was built from the route prop, which stays empty for these chats because creating the chat only swaps the URL with history.replaceState and never re-runs the route load. Drafts were written under the new-chat key while a reload of /c/<id> read the per-chat key. Falling back to the active chat id lines the two up, the same fallback loadChat already uses for this reason.

The Placeholder submit handler also cleared the bare key rather than the current one, which left a stale draft behind once a chat's messages had all been deleted.

Verified against the base commit on a local instance: on base the draft lands under chat-input and is lost on reload, with the change it lands under chat-input-<id> and both the text and the uploaded file come back. Image attachments stay excluded from drafts by design.

The model selection half of #29760 has a different cause and is not fixed here.

Refs #29760
2026-09-21 08:08:45 -04:00
Classic298
f16e9eabe3
feat: fall back to an available model when a chat's models are gone (#29757)
Retiring a model leaves every chat that used it stuck. The chat still stores the old model id, so it loads with nothing usable selected and refuses to send until the user picks a replacement by hand, in every old conversation. There is no admin-side way to move those chats across.

Loading a chat now drops model ids that no longer exist, and an empty selection falls into the fallback this function already applies to new chats: the user's default model, then the admin-configured default, then the first available model. A chat that still has one live model keeps it. The filter runs before the single-model permission clamp so a restricted user keeps a live model the chat already has instead of losing it to the clamp.

The model list cannot distinguish a retired model from one whose connection is momentarily unreachable, so during such an outage a chat pinned to that connection will open on the fallback model and record it when saved. Models defined in the workspace are unaffected, since they stay in the list while their connection is down.
2026-09-21 08:08:27 -04:00
G30
5399e3977d
fix: enforce the account form's required fields the way the admin user dialog does (#30296) 2026-09-21 08:08:02 -04:00
G30
aa65f55692
fix: insert the whole emoji when its shortcode is a codepoint sequence (#30213) 2026-09-21 08:07:45 -04:00
G30
7eebbbb891
fix: stop the MinerU API key blocking a save in local mode (#30299) 2026-09-21 08:04:19 -04:00
andreadegiovine
e9017c7da4
i18n: update and improve it-IT (#30297)
* i18n: update and improve it-IT

* i18n: update and improve it-IT

---------

Co-authored-by: andreadegiovine <andreadegiovine@gmail.com>
2026-09-21 08:04:08 -04:00
Timothy Jaeryang Baek
85146206f6 refac 2026-09-21 00:59:46 -04:00
G30
14be67348a
fix: open the new group dialog empty after a group is created (#30280) 2026-09-21 00:56:53 -04:00
Timothy Jaeryang Baek
97e013a661 refac 2026-09-21 00:51:28 -04:00
G30
394fcc7e24
fix: keep an unsaved code block edit visible after it is collapsed (#30284) 2026-09-20 23:44:16 -05:00
G30
9758f33b6c
fix: save a model whose description is switched from default back to custom (#30249) 2026-09-20 23:42:44 -05:00
G30
669d37bea1
fix: stop offering reactions on a channel the viewer may only read (#30241) 2026-09-20 23:42:32 -05:00
G30
b256115fa2
fix: save the context compaction threshold from general settings (#30270) 2026-09-20 23:42:22 -05:00
G30
f2220d2e40
fix: keep citation chips inside bold, italic and linked text (#30278) 2026-09-21 00:33:47 -04:00
G30
4c00e08aa1
fix: keep a message's attachments when its edit is cancelled (#30281) 2026-09-21 00:31:26 -04:00
Classic298
8e4cc946ce
fix: emit the resolved file path in terminal file events (#30282)
When a model calls display_file, write_file or replace_file_content with a relative path, Open Terminal resolves it against the session working directory and returns the absolute path, but the event sent to the browser carried the raw argument instead. The file panel matches that string against the file browser root, a relative path never matches, so the preview never opens, the panel jumps to the root and the session working directory is rewritten to the root. With the root turned off (OPEN_TERMINAL_FILE_BROWSER_ROOT=filesystem) there is nothing to clamp to and the relative string is sent to the terminal as the new working directory, moving it silently. The tool call itself succeeds either way, so the failure only shows up as a panel that will not open the file the model just wrote.

Both events now carry the path from the tool result and fall back to the argument when the result cannot be read, which is what build_terminal_file_tool_result already does for the chat file attachment. The same one-line rule is applied to the direct tool server path in the frontend, where the browser runs the tool itself and the write_file branch beside it was already correct.

Checked against a live Open Terminal: relative arguments now emit the absolute path, absolute ones are unchanged, non-existent files and inline displays still emit nothing, unreadable or error results still fall back to the argument, and run_command is untouched.

Related to #30051
2026-09-21 00:31:14 -04:00
G30
17e7e8f5e9
fix: show a code run's error output alongside what it printed (#30286) 2026-09-21 00:30:55 -04:00
G30
f847c6a588
fix: apply the new text to speech engine's default voice and model when the engine changes (#30289) 2026-09-21 00:30:26 -04:00
Classic298
043784c2d0
fix: route external webhook avatar URLs through the profile image endpoint (#29892)
Some checks failed
Python CI / Ruff Format (3.12) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args: free_disk:false name:main suffix:]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args: free_disk:false name:main suffix:]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Has been cancelled
Python CI / Ruff Format (3.11) (push) Has been cancelled
Frontend Build / Format & Build (push) Has been cancelled
Frontend Build / Unit Tests (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:cuda suffix:-cuda]) (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:cuda126 suffix:-cuda126]) (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:main suffix:]) (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:ollama suffix:-ollama]) (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:slim suffix:-slim]) (push) Has been cancelled
Create and publish Docker images with specific build args / notify-helm-charts (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (, main) (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda, cuda) (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda126, cuda126) (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (-ollama, ollama) (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (-slim, slim) (push) Has been cancelled
The channel webhooks modal rendered a webhook's stored profile_image_url straight into an img tag, so opening it sent every viewer's browser to whatever external host that URL named, leaking client IP, User-Agent and Referer no matter how the server was configured.

External URLs now render through the same webhook profile image endpoint the message list already uses, leaving it to the server to decide whether the browser is sent to that host. Locally picked images and the paths Open WebUI assigns itself still render inline, so previewing an upload before saving is unchanged, and the value written back on save is untouched.

This only takes effect together with the companion backend change that gates the endpoint on ENABLE_PROFILE_IMAGE_URL_FORWARDING. Until that lands the endpoint still redirects and the browser still reaches the external host. Also worth knowing: the endpoint matches the scheme case-sensitively, so a URL stored as HTTPS:// falls back to the default image here, which is already what the message list shows for it.

Verified in a browser against a local origin standing in for the external host: with forwarding on the avatar still renders through the endpoint in both places it appears, with forwarding off the browser makes no request to that origin, and picking a new file still previews immediately before saving.
2026-09-19 17:03:15 -05:00
Timothy Jaeryang Baek
8b3ee28272 refac 2026-09-19 17:45:05 -04:00
G30
881f7c9400
fix: save only the changed key when pinning, reordering or picking a default outside the Settings modal (#30183) 2026-09-19 17:34:02 -04:00
Timothy Jaeryang Baek
67adde3193 refac
Co-Authored-By: G30 <50341825+silentoplayz@users.noreply.github.com>
2026-09-19 17:26:40 -04:00