Commit Graph

9616 Commits

Author SHA1 Message Date
G30 771540f3de feat: sortable title and updated at columns on the knowledge workspace page (#27457) 2026-07-26 18:19:31 -04:00
Paulo Rodrigues 9c352e37b8 i18n: align the pt-PT locale with European Portuguese conventions (#27408)
The European Portuguese (pt-PT) catalogue mixes Brazilian and European forms
for the same concepts. In every case below the file ALREADY uses the European
term elsewhere, so these read as inconsistencies within the locale.

Corrects 74 string values. No keys are added, removed, renamed or reordered,
and no empty strings are filled (982 before, 982 after).

Genuinely Brazilian forms (Priberam tags these [Brasil] explicitly):
  arquivo -> ficheiro   (file sense only; Priberam: "Equivalente no portugues
                         de Portugal: ficheiro")
  usuario -> utilizador (Priberam sense 3: "[Brasil] ... = UTILIZADOR")
  acessar -> aceder     (Priberam: entire entry tagged [Brasil])
  equipe  -> equipa     (Priberam: "Grafia em Portugal: equipa")
  midia   -> multimedia (Priberam: every sense [Brasil])
  "em um" -> "num"
  "esta digitando" -> "esta a digitar": European Portuguese prefers
    estar a + infinitive over estar + gerund (Microsoft pt-PT Style Guide p.34)

European UI convention, where the file is inconsistent with itself. These
words are NOT Brazilian; the change is for consistency, not correctness:
  senha -> palavra-passe (0 vs 12 in this file)   salvar -> guardar (3 vs 19)
  compartilhar -> partilhar (6 vs 29)             excluir -> eliminar (4 vs 19)
  conexao -> ligacao (3 vs 27)                    gerenciamento -> gestao
  desenvolvedor -> programador
  Settings -> "Definicoes" (Settings label sense only, 15 vs 3)
  the explicit pronoun "voce" -> European formal verb forms

Outright defects:
  "Confirme sua a nova palavra-passe" -> "Confirme a sua nova palavra-passe"
  "e sua especificacao" -> "e a sua especificacao" (missing article)
  "Compartilhamento Publico" -> "Partilha Publica" (gender agreement)
  "Archive" -> "Arquivar", not "Arquivo". All four call sites are button
    labels/tooltips invoking archiveChatHandler(), so this is an action and
    needs the verb, matching its siblings "Archive All" -> "Arquivar Tudo".

Deliberately NOT changed:
  - 12 strings whose English key says "Config"/"configuration". "Configuracao"
    is correct European Portuguese; only the Settings label becomes
    "Definicoes".
  - "You" -> "Voce" (the chat label), the string referencing that label, and
    the default system prompt.
  - "confiavel" is left alone: Priberam gives it no regional label and
    professionally translated pt-PT (GNOME) ships it.
2026-07-26 18:18:39 -04:00
G30 c9c97835bf fix(ui): keep dropdowns positioned and stable as their content resizes (#27460) 2026-07-26 18:17:53 -04:00
G30 527b0d3b7f fix(ui): stop the chat input attach menus from fetching twice on open (#27461) 2026-07-26 18:14:06 -04:00
Classic298 f2ff310b2a fix: gate gray-500 muted text contrast behind High Contrast Mode (#27556)
Follow-up to #27497, reopened as a high contrast mode change, and the third and last of the contrast set after #27554 and #27555.

The grey scale in src/tailwind.css is achromatic oklch(L 0 0), so relative luminance is exactly L³ and text-gray-500 is 2.77:1 on white, against the 4.5:1 required by WCAG 1.4.3 and the 3:1 required of icons by 1.4.11. Around 290 sites use text-gray-500 dark:text-gray-400 for secondary labels, descriptions, counters and icons. Dark mode already passes at 6.46:1 and is left alone.

Rather than rewriting the class literal at every site, the remap is two CSS rules that only apply when the existing High Contrast Mode setting is on, so the default theme is untouched. Light mode resolves to gray-600 (5.75:1).

The split is not cosmetic. The `text-gray-500` utility is overridden inside `@layer utilities` with `:where()` so the rule sits below `hover:text-gray-*` and `dark:hover:text-gray-*` in specificity and hover feedback keeps working. The `.app-muted`, `.app-icon-muted` and `.tiptap table` classes in src/app.css are declared unlayered, which means no layered rule can reach them, so their override is unlayered too. They are `@apply text-gray-500 dark:text-gray-400` and fail identically, so leaving them out would have left the slash command menu and tiptap tables below 4.5:1 with the setting on.

Verified in a browser against Tailwind's emitted rules and layer order: with the setting on, the utility, .app-muted and .tiptap table all resolve to gray-600 in light while a focusable element carrying hover:text-gray-700 still resolves to gray-700 on interaction; dark mode and the setting-off case are unchanged in both themes.

One site is deliberately left out: EmbeddedChatHistoryDropdown.svelte uses text-gray-500/70, a separate class token that the selector does not match.
2026-07-26 18:11:47 -04:00
Timothy Jaeryang Baek 0e0d08382a refac 2026-07-26 18:11:12 -04:00
Timothy Jaeryang Baek 1f0dc90abe refac 2026-07-26 18:06:03 -04:00
Classic298 e3cce68ef2 fix: gate placeholder contrast fix behind High Contrast Mode (#27555)
Follow-up to #27496, reopened as a high contrast mode change.

Placeholder text is the lowest contrast text in the product. The grey scale in src/tailwind.css is achromatic oklch(L 0 0), so relative luminance is exactly L³, and placeholder:text-gray-300 is 1.58:1 on white against the 4.5:1 required by WCAG 1.4.3. The large text exemption does not apply, the largest of these is text-lg. Placeholders are frequently the only format hint a field gives, for example admin/Settings/General.svelte uses e.g.) "http://localhost:3000".

Rather than deleting the ~200 per-component placeholder utilities and rewriting the base rule, the remap now happens in two CSS rules that only apply when the existing High Contrast Mode setting is on, so the default theme is untouched:

- placeholders resolve to gray-600 (5.75:1) in light mode
- placeholders resolve to gray-500 (6.46:1) in dark mode

The rules sit in `@layer utilities` and are anchored on `input`/`textarea`, which puts them above both the base rule in src/tailwind.css and every per-component `placeholder:text-*` utility, so no call site has to change. Placeholders stay distinguishable from real input values, which are text-gray-700 (8.46:1) in light and dark:text-gray-300 (11.39:1) in dark.

The chat composer placeholder is a tiptap ::before, so neither the base rule nor any utility reaches it. It is hardcoded #676767, which is 5.66:1 in light but only 3.17:1 on the dark canvas, so only the dark side is remapped, to gray-500. That rule stays outside the layer because the rule it overrides is unlayered too.

The root layout toggles a `high-contrast` class on documentElement from `$settings.highContrastMode`, alongside the existing theme classes, so every route is covered and the class is removed again when the setting is turned off.

Verified in a browser against Tailwind's emitted rules and layer order, on inputs both with and without per-component placeholder utilities: with the setting on, placeholders resolve to gray-600 in light and gray-500 in dark, the composer placeholder resolves to gray-500 in dark even with the prefers-color-scheme override treated as unconditional, and with the setting off nothing changes in either theme. Also checked against the oled-dark theme (gray-500 on #000 is 7.57:1) and the dark:bg-white/[0.03] input surface (6.01:1).

Note: the `high-contrast` class toggle is the same hunk as in the muted text contrast branch. Whichever lands first, the other rebases cleanly by dropping it.
2026-07-26 18:02:57 -04:00
Timothy Jaeryang Baek 7bfc4bb2c2 refac 2026-07-26 18:02:33 -04:00
G30 202f47ece8 fix(ui): stop sidebar chat rows flickering while the pointer moves across them (#27474) 2026-07-26 17:57:44 -04:00
Kylapaallikko 8c9c64250d Update fi-FI translation.json (#27469)
Added missing translations.
2026-07-26 17:57:00 -04:00
Timothy Jaeryang Baek 421834b2de refac 2026-07-26 17:51:49 -04:00
G30 878cebac07 fix(ui): persist ollama connection deletions immediately (#27483) 2026-07-26 17:38:49 -04:00
Classic298 a0ee66c145 fix: name the group permission switches (WCAG 4.1.2) (#27513)
`admin/Users/Groups/Permissions.svelte` contains **64** `<Switch>` instances and not one of them passes `ariaLabel`, `ariaLabelledbyId` or `id`. bits-ui renders the switch as a `<button role="switch">` whose subtree is a text free thumb, so all 64 have **no accessible name**. The visible label is a sibling `<div>` with no association to the control.

This is the worst remaining case in the admin area: 64 toggles in one dialog, many with near identical adjacent labels (Import Models / Export Models / Import Prompts / Export Prompts / Import Tools / Export Tools). A screen reader user hears 64 consecutive "switch, on" and "switch, off" with no way to tell which permission is which.

Breaks WCAG 4.1.2 Name, Role, Value (Level A).

Fix: pass the row's own label to each switch. The `ariaLabel` expression is the **same `$i18n.t()` key** as the visible text two lines above it, so the accessible name equals the visible label in every locale, which also satisfies 2.5.3 Label in Name and keeps voice control working.

`ariaLabel` rather than `ariaLabelledbyId`, which is what `chat/Settings/Interface.svelte` uses for the same row shape. The difference is that `Interface.svelte` is a singleton, whereas this component is rendered from `EditGroupModal`, which is instantiated in three places including once per group in `GroupItem.svelte`. Only one can be visible today, but nothing enforces that, and 64 hardcoded ids would fail silently the day two coexist, since `aria-labelledby` resolves to the first matching id. `aria-label` has no such failure mode and needs half the edits.

All 64 mappings were checked individually rather than assumed. The nearest preceding label is the correct one in every case, including the three rows wrapped in a `<Tooltip>` (whose `content` attribute precedes the label in source order) and the ~60 `{#if}` / `{:else if}` explanatory strings (which always follow their switch). All 64 resulting labels are distinct.

The nested sub toggles are unambiguous on their own because upstream already labelled them fully ("Import Models" rather than "Import"), so no extra scoping is needed.

Two known follow ups, deliberately not bundled:

- The warning tooltips on Tools Access, Skills Access and Automations ("Warning: Enabling this will allow users to upload arbitrary code on the server.") are attached to a non focusable wrapper `<div>`, so keyboard and screen reader users never receive them. That needs a change in `common/Tooltip.svelte` or an `ariaDescribedbyId` on `Switch`, not a naming change.
- This file is a ~14 line block repeated 64 times where only the label and permission key vary, and it wants a shared `PermissionRow` component. Extracting it here would bundle a large structural refactor into an accessibility fix and make the diff unreviewable against the claim, so it is left alone.

The diff is +164/−65 rather than 64 changed lines, because 33 of the switches exceed the 100 column print width and Prettier reflows them to the multi line form. The file is Prettier clean and compiles with no new warnings.

Severity: Serious.

### Contributor License Agreement

<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.

Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->

- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.

> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
2026-07-26 17:38:35 -04:00
Classic298 ba4c92c4f0 fix: make sidebar section headers keyboard operable (WCAG 2.1.1, 4.1.2) (#27489)
On latest `dev`, each sidebar section header in `Sidebar/Section.svelte` is a real `<button>` carrying `aria-expanded` and `aria-controls`, but it has **no activation handler**. The toggle comes only from `on:pointerup` on the wrapper inside `common/Collapsible.svelte`.

Keyboard activation dispatches a synthetic `click`, never `pointerup`, and that wrapper's own `on:click` handler calls `stopPropagation()`. So pressing Enter or Space on the header does nothing at all, while `aria-expanded` tells assistive technology this is a working disclosure control.

This affects every section in the sidebar: Models, Notes, Channels, Folders and Chats. Section state is persisted to `localStorage`, so a user whose section was collapsed on a previous visit has no keyboard way to open it again, and the content stays unreachable.

Breaks WCAG 2.1.1 Keyboard (Level A), and 4.1.2 Name, Role, Value (Level A), because the exposed expanded state belongs to a control that cannot be operated.

Fix: handle activation on the header button itself, where focus actually lands, and stop the now duplicate pointer path so a mouse click does not toggle twice. The existing inline `onChange` body is extracted to `setOpen` so the `change` dispatch and the `localStorage` write stay in one place and fire exactly once per toggle in both input modes. The adjacent "+" (`onAdd`) button already stops both `pointerup` and `click`, so it still does not toggle the section.

`Collapsible`'s wrapper cannot simply become a `<button>` instead, because its slot receives buttons from this component and others, so the fix belongs here.

`common/Folder.svelte` and `Sidebar/RecursiveFolder.svelte` have the same latent defect and are not touched by this PR.

Severity: Critical. Sidebar navigation cannot be expanded without a mouse.

### Contributor License Agreement

<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.

Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->

- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.

> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
2026-07-26 17:38:06 -04:00
Classic298 15f724b0f2 fix: give SensitiveInput a unique default id (WCAG 1.3.1, 4.1.2) (#27488)
On latest `dev`, `SensitiveInput` defaults to `export let id = 'password-input'`. The id is used both for the input itself and as the `for` target of the screen reader label rendered just above it.

There are 80 `<SensitiveInput>` usages in `src/` and only 4 pass an explicit id, so the remaining 76 all render `id="password-input"` together with `<label for="password-input">`. These collide on the same page in completely ordinary configurations: `admin/Settings/Audio.svelte` renders 4 at once with `STT_ENGINE === 'openai'` and 4 more with `TTS_ENGINE === 'openai'`, `admin/Settings/Documents.svelte` has 11, and `admin/Settings/WebSearch.svelte` has 33.

`for` resolves to the first matching element, so every label after the first points at the wrong input. In practice a screen reader user tabbing to the OpenAI TTS API key field hears the label belonging to the STT key field from a different section, and every one of those fields announces the same name. Browser password managers and any `getElementById` lookup collapse onto the first element the same way.

Breaks WCAG 1.3.1 Info and Relationships (Level A), because the programmatic label/field relationship is wrong, and 4.1.2 Name, Role, Value (Level A), because the fields do not expose their correct accessible name.

Fix: default the id to a per instance unique value. A Svelte prop default is evaluated per component instance, so each `SensitiveInput` gets its own stable id, and the 4 call sites that pass an explicit id are unaffected. `uuid` is already a direct dependency and `import { v4 as uuidv4 } from 'uuid'` is the existing pattern in the codebase, including `common/Collapsible.svelte`, which already generates a DOM id this way.

Note for self hosted setups: a `#password-input` selector in `static/custom.css` would stop matching. That selector already matched up to 8 elements at once on the Audio settings page, so it was never a reliable hook.

Severity: Serious. Every API key field in Admin Settings is mislabelled for assistive technology.

### Contributor License Agreement

<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.

Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->

- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.

> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
2026-07-26 17:37:45 -04:00
Classic298 d3802f7660 fix: make reasoning and details disclosures keyboard operable (WCAG 2.1.1, 4.1.2) (#27490)
On latest `dev`, the `title !== null` branch of `Collapsible` renders its header as a bare `<div>` whose only handler is `on:pointerup`, with the two Svelte a11y warnings suppressed above it.

`pointerup` is never dispatched by keyboard activation, and the `<div>` has no `role`, no `tabindex` and no `aria-expanded`. The header is therefore not focusable, not activatable and not announced as a control. This is the header for "Thinking..." / "Thought for N seconds", "Analyzing..." / "Analyzed", and every `<details>` block rendered from model output, via `Messages/Markdown/MarkdownTokens.svelte`, `Messages/StructuredOutputRenderer.svelte` and `chat/Controls/Controls.svelte`.

In practice a keyboard or screen reader user cannot expand any model reasoning trace, tool call detail or code interpreter block, and a screen reader reads the header as static text with no hint that anything is collapsed behind it.

Breaks WCAG 2.1.1 Keyboard (Level A), since the disclosure has no keyboard operation at all, and 4.1.2 Name, Role, Value (Level A), since it exposes neither a button role nor its expanded state.

Fix: render that header as a real `<button type="button">` with `aria-expanded` and the native `disabled` attribute, and toggle on `click`, which fires for both pointer and keyboard activation. This branch contains no `<slot />` and no interactive descendants, so a button is valid here. `block text-start` keeps the previous box and alignment behaviour, since a `<button>` otherwise defaults to `inline-block` and centred text. `disabled:cursor-default` replaces the old `{disabled ? '' : 'cursor-pointer'}` ternary, which became a no-op once this was a button, because `src/tailwind.css` applies `cursor-pointer` to every `button`. Verified in a browser that display, text alignment and rendered height match the previous `<div>`, and that a disabled header no longer shows a pointer cursor.

Switching from `pointerup` to `click` also means the header no longer toggles on right click, or when a drag starts outside it and ends inside.

The `{:else}` branch is deliberately left alone. Its `<slot />` receives buttons from `Sidebar/Section.svelte`, `common/Folder.svelte` and `Sidebar/RecursiveFolder.svelte`, so it cannot legally become a `<button>` and needs a different fix.

Severity: Critical. Model reasoning output is entirely unreachable without a mouse.

### Contributor License Agreement

<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.

Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->

- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.

> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
2026-07-26 17:37:34 -04:00
Classic298 65473b6ffa fix: stop Enter on Cancel from confirming ConfirmDialog (WCAG 3.2.2) (#27491)
On latest `dev`, `ConfirmDialog` registers `handleKeyDown` on `window` and treats **every** Enter keypress as Confirm, calling `event.preventDefault()` first so the focused control never gets its native activation.

The dialog also activates a focus trap with no `initialFocus`, so focus-trap falls back to the first tabbable node, which is the **Cancel** button. So the dialog opens with Cancel focused, and pressing Enter runs Confirm.

This is the confirm surface for Delete chat, Delete folder, Delete model, Delete knowledge base and ~40 other call sites. A keyboard user who tabs to Cancel and presses Enter deletes the thing they were trying to keep. Screen reader users are hit hardest, since they cannot see which button they are on and the control that means "back out safely" performs the irreversible action instead.

Two related paths have the same cause: Enter in the `input=true` textarea submits instead of inserting a newline, and a markdown link inside `message` (reachable via `eventConfirmationMessage` from tool `__event_call__` payloads, and via `web_search_confirmation_content`) becomes the first tabbable node, so Enter on that link confirms instead of following it.

Breaks WCAG 3.2.2 On Input (Level A): changing the focused control changes what the Enter key does, and activating a control performs a different action than the one it is labelled with. Also 2.1.1 Keyboard (Level A), since Cancel has no working keyboard activation.

Fix: let the focused control act on Enter itself, and only fall back to Confirm otherwise. Uses the same `target instanceof Element && target.closest(...)` guard already used in `Functions.svelte`, `Knowledge.svelte`, `Models.svelte`, `Prompts.svelte`, `Skills.svelte` and `Tools.svelte`. `select` is deliberately not in the list, because a native `select` does not act on Enter and excluding it would silently break confirm for the `inputType === 'select'` variant. Two stray `console.log` calls in the same function are removed.

Behaviour after this change: Enter on Cancel cancels, Enter on Confirm confirms, Enter in the textarea inserts a newline, Enter on a link follows it, and Enter anywhere else still confirms as before.

Severity: Critical. Silent, unrecoverable data loss triggered by the most ordinary keyboard interaction there is.

### Contributor License Agreement

<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.

Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->

- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.

> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
2026-07-26 17:37:21 -04:00
Classic298 2725ae6d6c fix: expose Checkbox as a checkbox with a name and state (WCAG 4.1.2) (#27494)
On latest `dev`, `common/Checkbox.svelte` renders a `<button type="button">` containing only `aria-hidden="true"` SVGs. It has no `role`, no `aria-checked` and no accessible name, and the component has no `$$restProps` spread, so a caller cannot supply a name either.

Assistive technology announces every one of these as an unnamed "button". A screen reader user cannot tell that the control is a checkbox, cannot tell whether it is on or off, and cannot tell what it toggles. The visible label is always an unassociated sibling element, for example `Capabilities.svelte` puts it in a preceding `<div>` with no `id`, and `Groups/Users.svelte` puts it in a different table cell from the checkbox.

Breaks WCAG 4.1.2 Name, Role, Value (Level A) on all three counts at once.

Fix: expose `role="checkbox"` and `aria-checked` on the control, add an `ariaLabel` prop, and pass the label text that is already in scope at each call site. `aria-checked` mirrors the component's existing icon logic exactly, so the indeterminate dash reports `mixed` rather than `false`. The `ariaLabel={ariaLabel || undefined}` shape matches the sibling `common/Switch.svelte`. Every label expression is the same one that renders the visible text next to the checkbox, so the accessible name always matches what is on screen.

Three call sites are deliberately left out of this PR, because they nest `Checkbox` inside another `<button>`, which is invalid HTML and independently broken:

- `workspace/Knowledge/KnowledgeBase.svelte` — the Checkbox's `on:change` sets `includeContent = true` and then the same click bubbles to the outer button, which flips it back with `includeContent = !includeContent`. Clicking the checkbox square is a no-op today, only the text label works. Giving it a confident name would advertise a control that does nothing.
- `workspace/common/MemberSelector.svelte` (two instances) — the inner Checkbox has no `on:change` at all and only works because its click bubbles to the row button. Naming it would create two focusable controls per row with the same name.

Both need the nesting resolved first, so that the row button carries the checkbox semantics. That is a behavioural fix and belongs in its own PR.

Severity: Serious. Affects model capabilities, default features, builtin tools, tool/filter/skill/action selectors and group membership.

### Contributor License Agreement

<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.

Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->

- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.

> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
2026-07-26 17:37:06 -04:00
Classic298 a7a2c7605b fix: give the rich text editor an accessible name (WCAG 4.1.2, 3.3.2) (#27503)
On latest `dev`, `RichTextInput` passes only `attributes: { id }` to tiptap, so the rendered contenteditable has an implicit `textbox` role and **no accessible name at all**.

The only label is the tiptap placeholder, which renders as CSS generated content in `src/app.css` via `content: attr(data-placeholder)`. Generated content never becomes an element's accessible name, so assistive technology announces the field as "edit text, blank".

This is the chat composer, the channel and thread composers, and the note editor, so it is the most used control in the product.

Breaks WCAG 4.1.2 Name, Role, Value (Level A), and 3.3.2 Labels or Instructions (Level A), since the only instruction is invisible to assistive technology.

Fix: expose the placeholder as `aria-label` on the editor element.

`attributes` is passed as a **function** rather than an object literal. The object form is evaluated once when the `Editor` is constructed and never rebuilt, but `placeholder` is deliberately runtime mutable: `channel/MessageInput.svelte` and `channel/Thread.svelte` swap it between "You do not have permission to send messages in this thread." and "Reply to thread..." once `channel` resolves, and it also changes when the interface language changes. With the object form the field would have been permanently named with whatever string happened to be set at mount, which for a channel the user *can* write to is the no-permission message. That would be worse than no name at all. ProseMirror supports the function form and re-evaluates it on every state update, and the component's existing `setPlaceholder` already dispatches an empty transaction, so the label now tracks the visible placeholder. It binds to `_placeholder`, the same value that feeds the visible text, so the two cannot diverge.

`aria-multiline` is deliberately not set. It is only valid on an explicit `textbox`/`searchbox` role, and adding `role="textbox"` would flatten the editor's inner structure so headings, lists and links inside rich text stop being exposed.

Severity: Critical. The application's primary input announces as an unnamed edit field.

### Contributor License Agreement

<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.

Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->

- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.

> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
2026-07-26 17:36:36 -04:00
Classic298 7effaa05d1 fix: name the switches in Admin Settings rows (WCAG 4.1.2, 1.3.1) (#27510)
`common/Switch.svelte` already accepts `id`, `ariaLabel` and `ariaLabelledbyId`, but **not one of the 148 `<Switch>` instances under `src/lib/components/admin/` passes any of them**.

`admin/Settings/AdminSettingRow.svelte` renders the row label as a plain `<div>` and the control in a **sibling** slot, so there is nothing tying them together. bits-ui renders the switch as a `<button role="switch">` whose subtree is a text free thumb, so it has no accessible name from any source.

A screen reader user working through Admin Settings hears a long run of "switch, on" and "switch, off" with no indication of what any of them controls.

Breaks WCAG 4.1.2 Name, Role, Value (Level A) and 1.3.1 Info and Relationships (Level A).

Fix: `AdminSettingRow` mints a per instance id, puts it on the label element, and hands it to the default slot, so each row's switch can point at the label that is already rendered next to it. This is the pattern `chat/Settings/Interface.svelte` already uses by hand in 45 places, hoisted into the shared row component so call sites stop hand authoring ids.

`aria-labelledby` rather than a wrapping `<label>`: per HTML-AAM a `<button>` takes its name from `aria-labelledby`, then `aria-label`, then its own subtree, never from an associated `<label>`. `chat/Settings/Subagents.svelte` already wraps two switches in a `<label>` and they are still unnamed, which is the same trap. Using the existing label element also guarantees the accessible name is byte identical to the visible text, which keeps voice control working.

The `description` paragraph deliberately sits outside the referenced element, so verbose help text is not pulled into the name.

Scope: this covers the **72** switches that live inside an `AdminSettingRow`, which is every switch that flows through the shared row component. There are no rows containing more than one switch, so nothing is silently skipped.

The remaining 76 admin switches are not in this component and are not touched. 64 of them are in `admin/Users/Groups/Permissions.svelte`, which hand rolls its own row markup, and the other 12 are per entity toggles in lists and dropdowns where the label is a dynamic row name. `Permissions.svelte` is the worst remaining case, 64 toggles with near identical adjacent labels, and it needs either its own labelling pass or a conversion to `AdminSettingRow` that changes its visual styling. Either way that is not an accessibility only diff and belongs in its own PR.

All 12 touched files compile with the Svelte compiler with no new warnings and are Prettier clean.

Severity: Serious. Admin Settings is unusable with a screen reader.

### Contributor License Agreement

<!--
🚨 DO NOT DELETE THE TEXT BELOW 🚨
Keep the "Contributor License Agreement" confirmation text intact.
Deleting it will trigger the CLA-Bot to INVALIDATE your PR.

Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA.
-->

- [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.

> [!NOTE]
> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
2026-07-26 17:35:34 -04:00
G30 4ac22b89fd fix: don't wipe sidebar folder chat list when a refresh overlaps an in-flight fetch (#27535) 2026-07-26 17:28:15 -04:00
G30 29499cb4ba perf: dedupe folder refetches and chat-list sweeps on sidebar folder selection (#27540) 2026-07-26 17:27:33 -04:00
G30 4d576c1aa2 chore: remove no-op stopPropagation statement from folder title click handler (#27542) 2026-07-26 17:27:12 -04:00
G30 65209b0235 fix: enforce a single open chat hover preview across sidebar chat items (#27549) 2026-07-26 17:26:49 -04:00
G30 ef197de0d7 fix: open settings deep links on client-side navigation and open Add Terminal settings directly (#27552) 2026-07-26 17:26:34 -04:00
Classic298 d1aa812d80 i18n: complete de-DE translations (#27448)
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
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
* i18n: complete de-DE translations

Fill in all 544 untranslated (empty) strings in the German locale and add
the two keys that were missing entirely ("Response Auto-Scroll" and
"Follow assistant responses as they are generated.").

Wording follows the conventions already used in the file: formal "Sie"
address for user-facing sentences, infinitive phrasing for labels and
buttons, third-person descriptive phrasing for setting descriptions, and
the established terminology (Kontextverdichtung, Erinnerungen,
Wissensspeicher, Werkzeuge, Chunk, Embedding, Skills, Pipelines).
Ambiguous strings were resolved against their usage in the Svelte
components, e.g. "at"/"Through" (schedule and heatmap tooltips),
"Runs"/"runs" (automation runs vs. tool invocations), "Current"
(active chat) and "Selected" (model filter).

* i18n: fix de-DE wording and two pre-existing plural bugs

Review pass over the German locale:

- "Claim" and "DN" are masculine: "Claim, der ..." instead of "Claim,
  das ...", "Passwort für den Bind-DN", "Base DN, der ...".
- "hinzufügen" governs the dative, matching the existing string
  "... fügen Sie sie zuerst dem Arbeitsbereich "Wissen" hinzu."
- "Beschränkt oder schließt Domains ... aus" was a zeugma; the separable
  prefix only belongs to "schließt".
- Sub-agent settings render as label + input + unit suffix on one line,
  so the label and suffix no longer repeat each other.
- The built-in tool descriptions are infinitive, so the notification one
  is too.
- Align wording with terms already used in the file: Assistentennachrichten,
  Benutzernachrichten, Vervollständigungen, Tool-Server, Wissensspeicher,
  lexikalisch. Normalize the few German typographic quotes to the ASCII
  quotes used everywhere else.
- The username setting claimed the chat shows "Sie", but "You" is
  translated as "Du".

Also fixes bugs that predate these translations: "Starting in {{count}}
minutes" had the raw "minutes_one"/"minutes_other" suffix in its value,
and the singular and plural of "Ran {{COUNT}} analysis/analyses" were
swapped.
2026-07-24 17:59:54 -05:00
joaoback 9d293935a9 i18n: add pt-BR translations for newly added UI items and consistency pass (#27430)
New **pt-BR** translations for items introduced in the latest releases, plus a consistency/quality pass across existing strings (grammar, tone, capitalization, pluralization). Placeholders and hotkeys preserved. No logic changes.
2026-07-24 12:37:39 -04:00
G30 06d2189b26 fix(chat): keep sidebar chat selection in sync with the active chat (#26977)
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
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
Frontend Build / Format & Build (push) Has been cancelled
Frontend Build / Unit Tests (push) Has been cancelled
Python CI / Ruff Format (3.11) (push) Has been cancelled
Python CI / Ruff Format (3.12) (push) Has been cancelled
When navigating from a chat to a non-chat route (e.g. the admin panel),
the previously-viewed chat stayed selected in the sidebar and
deleting/archiving it wrongly redirected back to the new-chat page.
Cloning a chat also left the source chat highlighted alongside the new
clone, so two chats appeared selected at once.

Two independent sources kept the stale selection:

- The chatId store was never cleared when the Chat component unmounted,
  so $chatId still pointed at the last-viewed chat (this drove the
  delete/archive redirect). Clear chatId/chatTitle in Chat's onDestroy.
- The sidebar's optimistic selectedChatId highlight, set on click, was
  only cleared on window blur (hence it appeared to fix itself after a
  tab switch) and never followed programmatic navigation. Bind it to the
  chatId store so it tracks the active chat for leave, delete and clone.
2026-07-24 01:45:37 -05:00
Timothy Jaeryang Baek b4d13793a3 refac 2026-07-24 02:14:56 -04:00
Timothy Jaeryang Baek d7513e4ce8 refac 2026-07-24 02:05:31 -04:00
Classic298 585b704597 fix: clear token cookie on 401 auth redirect to stop login flash loop (#26751)
Since v0.10.0 a global fetch interceptor redirects to /auth and clears
localStorage.token whenever an authenticated backend request returns 401.
The OAuth callback cookie ("token", set with httponly=False so the
frontend can read it) is left behind. The auth page's oauthCallbackHandler
then immediately signs the user back in from that cookie and navigates to
"/", where the next 401 triggers the redirect again. The result is an
endless /auth and / ping-pong that renders as uncontrollable screen
flashing, and as a PWA stuck on a flashing splash screen when SvelteKit's
update check turns each navigation into a full page reload. Affected
users could only recover by clearing cookies, which matches the reports.

Clear the token cookie together with localStorage when redirecting, so
/auth stays on the login form and the user can sign in again normally.

Fixes #26731
2026-07-24 00:59:43 -05:00
Timothy Jaeryang Baek 793a43d9c4 refac 2026-07-24 01:48:56 -04:00
Timothy Jaeryang Baek bd5d7b2e87 refac 2026-07-24 01:47:11 -04:00
Timothy Jaeryang Baek 212eec408c refac 2026-07-24 01:44:30 -04:00
Classic298 b6acd3cc45 fix: web speech STT repeating previous transcriptions and inserting text on cancel (#26793)
The VoiceRecording component stays mounted (hidden) between recordings, and the web speech engine accumulated every session's transcript into the never-reset transcription variable. Each new recording therefore confirmed all previous utterances again, so the inserted text repeated once per session and previously deleted text reappeared in the input.

Additionally, cancelling a recording (X button, Escape or a recognition error) called stopRecording(), which stops the SpeechRecognition instance and fires its onend handler, which unconditionally confirms the transcription. Cancelled recordings therefore still inserted the accumulated transcript.

Reset the transcription at the start of each web speech session and detach the onend handler on cancel so cancelled recordings no longer confirm.

Fixes #26784
2026-07-24 00:40:02 -05:00
Timothy Jaeryang Baek ce831f7b85 refac 2026-07-24 01:25:52 -04:00
Timothy Jaeryang Baek 1f5b0d816f refac 2026-07-24 01:19:28 -04:00
Timothy Jaeryang Baek cea991260f refac 2026-07-24 01:09:50 -04:00
G30 e212e3c7f4 fix(ui): resolve workspace model ID to base model on delete from model selector (#26819) 2026-07-24 00:03:31 -05:00
Timothy Jaeryang Baek 799748b886 refac 2026-07-24 01:00:00 -04:00
G30 1dc4fd3e9d fix(ui): hide redundant edit pencil on mobile in admin Models list (#27178)
On narrow viewports the admin Models list rows previously collapsed into
unusable vertical stacks; the Models page redesign on dev has since
absorbed the truncation fixes this branch carried (min-w-0 chain, real
truncate on the name, shrink-0 action group, inline access label).

The one remaining gap: the per-row edit pencil duplicates the row tap
(both open the model editor) while costing scarce horizontal space on
mobile. Hide it below the sm breakpoint; it remains on sm+ screens.
2026-07-23 23:58:07 -05:00
Viktor1 6ace7e5b3a i18n: add Slovenian language support (#26875)
* i18n: add Slovenian language entry

* i18n: add Slovenian translation
2026-07-23 23:57:22 -05:00
Timothy Jaeryang Baek 5f6a9d16b2 refac 2026-07-24 00:56:43 -04:00
Timothy Jaeryang Baek d637c2128c refac 2026-07-24 00:56:29 -04:00
Timothy Jaeryang Baek 239cb74007 refac 2026-07-24 00:56:25 -04:00
Timothy Jaeryang Baek b35e2d265a refac 2026-07-24 00:54:03 -04:00
Timothy Jaeryang Baek 8cbb7f765c refac 2026-07-24 00:47:12 -04:00
Timothy Jaeryang Baek de939a6562 refac 2026-07-24 00:44:06 -04:00
Timothy Jaeryang Baek 1e88367cc8 refac 2026-07-24 00:43:31 -04:00