Adds critical accessibility fixes across various app components:
- auth/+page: provide alt text for logo, turn on screenReader support for password input, add aria-required, hide decorative SVGs from AT
- AppSidebar: wrap navigation icons in a <nav> structure, provide ARIA labels for Home and Chat icons
- s/[id]/+page: convert structural divs into semantically accurate h1 heading and time element, wrap message display in main region
- OnBoarding: replace flawed aria-labelledby with direct aria-label on start button
- NotificationToast: provide role='status' and aria-live='polite' for proper screen reader broadcasting
- ChangelogModal: add required heading semantics for structure
- AddFilesPlaceholder: provide heading element role for standalone text content
- ImportModal: provide aria-label for close button
Addresses WCAG 4.1.3, 1.1.1, 3.3.2, and 1.3.1.
Add aria-hidden='true' to 112 SVG icon components in src/lib/components/icons/ that were missing this attribute. Decorative icons that convey no semantic meaning should be hidden from the accessibility tree to prevent screen readers from attempting to read meaningless SVG markup (WCAG 1.1.1 Non-text Content, WCAG 4.1.2 Name, Role, Value).
The remaining 60 icon files already had aria-hidden='true' set. All 172 icon components now consistently declare aria-hidden='true' on their root svg element.
- Replace incorrect aria-roledescription='model-item' with role='option' and aria-selected on ModelItem.svelte. The previous attribute was not a valid ARIA role description and provided no useful information to screen readers.
- Add contextual aria-label to each model item button (e.g. 'Select GPT-4 model') instead of just the raw model name, making the action clear to screen reader users.
- Add role='listbox' and aria-label='Available models' to the scrollable model list container in Selector.svelte so screen readers announce the container's purpose and navigate items correctly.
- Make the model selector trigger button's aria-label dynamic: it now announces 'Selected model: GPT-4' when a model is selected, falling back to 'Select a model' when nothing is selected.
- Add aria-label to the eject (unload) button in ModelItem.svelte so screen readers announce its purpose.
- Add aria-label to the cancel download button in Selector.svelte with the specific model name being canceled.
- Improve model profile image alt text from generic 'Model' to contextual '{{modelName}} profile image'.
The signup_handler function checks has_users() before inserting a new user
and assigns the admin role based on that check. With multiple uvicorn workers,
concurrent signup requests during first-user registration can all observe an
empty user table before any insert completes, causing multiple accounts to
receive the admin role.
Fix: insert with the default role first, then check user count after the
insert. Only promote to admin if this is the only user in the database.
This eliminates the TOCTOU window between the check and the insert.
* perf: eliminate 2 redundant full chat deserialization on every message send (#162)
Problem:
Every message send triggered get_chat_by_id_and_user_id which loads the
entire Chat row — including the potentially massive JSON blob containing
the full conversation history — even when the caller only needed a
simple yes/no ownership check or a single column value.
Two call sites in the message-send hot path were doing this:
1. main.py ownership verification: loaded the entire chat object including
all message history JSON, then checked `if chat is None`. The JSON blob
was immediately discarded — only the existence of the row mattered.
2. middleware.py folder check: loaded the entire chat object including all
message history JSON, then read only `chat.folder_id` — a plain column
on the chat table that requires zero JSON parsing.
Fix:
- Added `chat_exists_by_id_and_user_id()`: uses SQL EXISTS subquery which
returns a boolean without loading any row data. The database can satisfy
this from the primary key index alone.
- Added `get_chat_folder_id()`: queries only the `folder_id` column via
`db.query(Chat.folder_id)`, which tells SQLAlchemy to SELECT only that
single column instead of the entire row.
Both new methods preserve the same error handling semantics (return
False/None on exception) and user_id filtering (ownership check) as
the original get_chat_by_id_and_user_id.
Impact:
- Best case (typical): eliminates deserializing 2 full chat JSON blobs per
message send. For long conversations (hundreds of messages with tool
calls, images, file attachments), this blob can be multiple megabytes.
- Worst case: no regression — the new queries are strictly cheaper than
the old ones (less data transferred, less Python object construction,
no Pydantic model_validate overhead).
- The 3 remaining full chat loads in process_chat_payload (load_messages_from_db,
add_file_context, chat_image_generation_handler) are left untouched as
they genuinely need the full history and require separate analysis.
* Address maintainer feedback: rename method and inline call (#166)
- Rename chat_exists_by_id_and_user_id -> is_chat_owner
- Remove intermediate chat_owned variable; call is_chat_owner directly in if condition
feat: add citation sources for fetch_url tool results
URL fetches now produce clickable citation sources in the UI, matching
the existing behavior of search_web and knowledge file tools. When a
model calls fetch_url during native tool calling, the fetched URL
appears as a citable source with a content preview, giving users full
transparency into what pages the model referenced.
* feat: add sortable columns to groups admin panel
Make the Group and Users column headers in the admin groups list clickable to sort groups alphabetically by name or numerically by member count. Clicking a column toggles ascending/descending order, indicated by a chevron icon. When no sort is active, the default API order (by updated_at) is preserved.
* Update Groups.svelte
* Update Groups.svelte
fix: gate model default features on global config and user permissions
If you disabled code interpreter globally and in user permissions but
enabled it as a default feature on a model, the code interpreter pill
still appeared in the chat input. Same issue for web search and image
generation.
The setDefaults function in Chat.svelte activated model default features
based solely on the model's capability flag, ignoring whether the feature
was globally enabled or allowed by user permissions. Added the same
global config and user permission checks already used by the integrations
menu visibility and the features object sent to the backend.
Previously loaded the entire ChatModel (including the full conversation JSON
blob) just to extract the title string. Now queries only the Chat.title
column directly, which is already a top-level DB column.