* fix(voice): keep reasoning blocks out of speech and stop skipping the answer
Voice mode speaks the assistant content as it streams, and since the
streaming merge started preserving local <details> wrappers, TTS only
stripped blocks that were already closed. An open reasoning or tool_calls
wrapper was read aloud, and once its </details> landed the block vanished
from the split, shifting every later chunk left past the monotonic chunk
cursor. The answer itself was then never spoken.
TTS now sanitizes through the shared semantic_details helpers and treats an
unterminated semantic opener as a hard stop, so the speakable text only ever
grows. The streaming feed re-anchors on the text of the last chunk it handed
to playback instead of trusting a bare index.
* feat(voice): start calls on the speakerphone when nothing is plugged in
A voice call runs the audio session in communication mode, so a phone with
no accessory attached routes playback to the earpiece. Held like a call it
is fine, held like a speakerphone it is barely audible.
The coordinator now scans the attached audio devices at call start and
engages the loudspeaker only when it finds nothing to play through. A failed
scan leaves the route alone, since blaring an answer over someone's headset
is worse than a quiet earpiece. It keeps watching for the rest of the call,
so pulling headphones out moves playback to the speaker and connecting a
headset takes it back off. Pressing the speaker button ends the automatic
switching for that call.
* fix(voice): serialize audio route and TTS feed changes
Route changes are several platform calls deep, so two of them running at
once interleaved and the slower one got the last word. A headset pulled
out during a reroute could leave the call on the earpiece, and an
automatic reroute could land after the user pressed the speaker button or
after the call ended.
Queue route changes one at a time and re-read the state each one assumed
before it applies, so a stale reroute stands down instead of overwriting
a newer decision. Deactivation drains the queue before tearing the route
down.
Streaming TTS feeds had the same shape of problem: a second feed could
append its chunks in between the ones an earlier feed was still handing
to playback, so sentences could be spoken out of order. Chain the feeds.
* fix(voice): catch uppercase semantic details wrappers
The complete-block pattern in the TTS sanitizer already ignored case, but
the shared opener and block patterns did not, so an uppercase
`<DETAILS TYPE="reasoning">` that had not closed yet was read aloud.
Tag and attribute names are case-insensitive in HTML and this is model
output, so match either case everywhere.
* fix(voice): ignore a default route scan that came back too late
The accessory scan at the start of a call can still be out when the user
presses the speaker button or hangs up. Its result would then resubscribe
to device changes and push the call onto the loudspeaker after teardown.
Stamp the scan with a call generation and drop the answer if the call it
belongs to is over or the user has since chosen a route.
* fix(voice): only treat routes a call can actually use as accessories
A voice call runs the session in communication mode, which cannot route
to A2DP or AirPlay, so a device offering only those is not somewhere the
call can play and should not suppress the loudspeaker default. Headsets
that also speak HFP still register as bluetoothSco.
Accessory detection also required only a matching device type, so a
plugged-in microphone counted as somewhere to play. Require an output.
Manual speaker toggles are rejected once teardown has started, where
honouring them would put communication mode back after the route was
handed back.
* fix(voice): anchor the speech cursor on spoken text, not one chunk
The cursor was re-anchored by searching the fresh split for the text of
the last chunk handed to playback. When an answer repeats a sentence, that
search can match the wrong copy and skip everything in between, and when
the server rewrites the answer the anchor disappears and the cursor stops
moving at all.
Carry the text already spoken instead and walk the new split against it.
The match is positional, so repeated sentences are unambiguous, and a
rewrite resumes at the point the two versions stop agreeing rather than
replaying or skipping.
* fix(tts): strip nested details blocks before speaking
A non-greedy `</details>` match stops at the first close tag, so a
wrapper nested inside another left the outer block's tail in the text
handed to TTS: `<details><details>x</details>secret</details>` spoke
`secret`. Replace the pattern with a depth-counting walk shared from
`semantic_details.dart`, which also keeps the existing behaviour of
withholding the tail of a semantic wrapper that has not closed yet.
* fix(voice): re-check the route owner before publishing a reroute
The speaker button can be pressed, or the call can end, while the
platform calls behind an automatic reroute are still in flight.
Publishing afterwards left the speaker control showing a route nobody
chose.
* fix(tts): keep scanning inside an unclosed ordinary details
An ordinary `<details>` still waiting for its close tag kept the whole
suffix, so a reasoning wrapper opened inside it reached TTS. Keep the
tag and carry on from just past it: nested complete blocks are stripped
and a nested open wrapper still truncates.
* fix(voice): let a newer device event own the published route
A device event that lands while an earlier reroute is mid-flight claims
_accessoryAttached before queueing its own work. The earlier reroute
then published its now-stale value, so the speaker control showed the
old route until the newer operation caught up.
* fix(tts): speak on the call route during a voice call
Android routes voice-communication and media output separately. Device
TTS spoke as USAGE_MEDIA while the call held focus as
USAGE_VOICE_COMMUNICATION in MODE_IN_COMMUNICATION, so it ignored the
call's speakerphone choice and went silent once the app was backgrounded
mid-call.
The engine now gets voice-communication audio attributes (and the
matching legacy stream param) while a call is up, and goes back to the
media stream for read-aloud.
* fix(voice): only report a route change the platform took
A refused reroute still published to speakerphoneRouteChanges and stuck
in _speakerphoneEnabled, so the speaker button pointed at a route nobody
was hearing and the enabled == _speakerphoneEnabled guard dropped the
next identical device event as already handled.
The route calls now answer whether they landed, the flag rolls back when
they did not, and neither the device-change path nor the user toggle
publishes without a successful move.
* fix(tts): hand the engine back to read aloud after a call
Disposing the voice-mode provider mid-call left TtsManager in voice-call
mode, so a later read-aloud spoke on the call route: earpiece, at call
volume. Provider disposal now clears the flag, and so does reset().
reset() also stops rewinding _sessionCounter. A feed queued on the
previous chain only checks the active session's id, so reusing an id let
it append its old text to the next session, and the id names the server
chunk temp dir and background lease that the same stale work tears
down.
* fix(voice): light the speaker button only on a confirmed route
The default route is picked before the audio session exists, so the move
to the loudspeaker happens on the next configureFor* pass. The snapshot
was set from the pick, not the move, so a refused reroute still lit the
speaker button while the call stayed on the earpiece.
applyDefaultSpeakerphoneRoute now only sets the preference. The
configure pass reports whether the platform took it and announces it on
speakerphoneRouteChanges, which is already the one path the snapshot
follows. A refused move puts the flag back so the next device event can
try again.
* fix(voice): queue configure-pass routing behind the other reroutes
The listening, speaking and barge-in passes made the same platform route
calls as the button and device-change reroutes, off the same
_speakerphoneEnabled flag, without going through _routeSerial. A pass
that started before a headset was pulled out could finish after the
reroute and put the call back on the route it had just left.
They now queue with everything else, so each one reads the route
decision when it runs rather than when it was scheduled, and the default
route is only announced while the loudspeaker is still the current
choice.
* test(tts): assert with package:checks
The rest of the suite asserts with package:checks; this file was the odd
one out on expect(), which a review flagged while reading the new voice
call tests.
* fix(tts): stop a rewrite replaying the sentences it left alone
The cursor resumes at the point the old and new splits stop agreeing, so
a server that revises one sentence in the middle of an answer queues
everything after it a second time. The listener hears the tail twice.
Skip forward through the text playback already heard, in order, and only
speak the chunks that are not in it. Scanning forward rather than
searching the whole string keeps a sentence that genuinely repeats later
in the answer spoken once for each time it appears.
* fix(tts): hold the session open until the last feed lands
finishStreaming marks the response finalized before its own feed reaches
the serial chain. If the engine finishes the chunk it is speaking in that
window, playback sees a finalized session with nothing left queued, ends
it, and the feed carrying the rest of the answer finds no session to
append to. The answer stops mid-sentence.
Count the feeds still queued or running and treat playback as waiting
while any remain.
* fix(voice): keep automatic routing after a refused speaker press
The speaker button claimed the route before the platform had taken the
move, and kept the claim even when the move was refused. Nothing had
changed, but the call ignored every accessory event from then on: plug a
headset in afterwards and it stayed on the old route.
Count presses that are queued or on the wire so an automatic reroute
behind one still stands down, and only make the override permanent once
the platform reports the move applied.
* fix(voice): release the call route when stopping tts throws
The engine is shared with read-aloud, and the hand-back sat after the
stop call in the same teardown step. A stop that threw skipped it and
left read-aloud speaking on the call route. Give it its own step.
* fix(voice): hand the route back when the coordinator is disposed first
Riverpod gives no order to provider disposal, so the coordinator can go
before the controller that would have called deactivate. Its dispose only
cancelled the device watch, leaving the phone in communication mode with
the call's route still selected.
Disposal now runs the same teardown as hanging up, and that teardown is
safe to run twice. Session activation also joins the route queue, so a
configure pass already in flight cannot reactivate the session after
teardown drained the queue, and a default-route scan started during a
teardown is rejected instead of riding its generation bump through.
* fix(voice): keep the route shut once the coordinator is disposed
Teardown lifts the shutter again so the next call can route. A disposed
coordinator has no next call, so a deactivate arriving behind disposal
left the speaker button able to put the phone back into communication
mode after the coordinator was gone.
* fix(voice): retry an accessory move the platform refused
The transition is claimed in the accessory snapshot before the route
operation starts, so the burst of events a single headset sends collapses
into one move. A refused move left the claim standing, and since the
hardware never moved there is no fresh transition to come: the next
notification about the same headset matched the snapshot and was dropped
as old news, stranding the call on its previous route.
* fix(voice): keep a stale configure pass and overlapping teardowns apart
Session configuration is awaited before the activation step joins the
route queue, so a teardown that both starts and finishes inside that
window puts the shutter back up and the queued step reactivates a call
that is over. Each pass now carries the call generation it was started
for and stands down when it no longer matches.
Overlapping teardowns had the same shape: hanging up and disposal each
ran their own finally, and whichever finished first lifted the shutter
while the other was still restoring the platform route. The shutter now
waits for the last one out.
* fix(tts): keep heard sentences the held-back chunk hides
Mid stream the trailing chunk stays put until finalization, so a rewrite
arriving then leaves the sentences behind it out of the returned spoken
text. Finalization has nothing left to match them against and queues them
again. Carry the unconsumed heard history along instead.
* fix(voice): retry a default route the platform refused
A bare phone repeats the same device list rather than announcing a
transition, so the snapshot the refused default was picked from makes the
next notification look like old news and the call stays on the earpiece.
* fix(tts): stop a stale feed holding the next session open
A feed already inside the engine's speak call outlives the session that
queued it. Its count carried over to the next session, which then waited
for a chunk that was never coming and never finished speaking.
* fix(voice): drop a speaker press the teardown overtakes
A press queued before the call ended still reached the platform behind the
teardown, and answered the caller that the route had moved.
* fix(tts): hold server completion for a feed still on its way
finishStreaming marks the response finalized before its own feed reaches
the chain. The device path already waits that window out; the server path
ended the session there instead and dropped the rest of the answer.
* fix(tts): keep a stale fetch off the next session's bookkeeping
A fetch that outlives its session cleared the marker the next session had
put down for the same chunk index.
* fix(tts): keep a dead session's fetch error to itself
A failed fetch from a session that is over was reported as an error
against whatever is playing now.
* feat: add Apple model backends and context compaction
* Add Apple on-device and PCC direct providers
* dev: show all onboarding backends in debug builds
* Fix short response pin-to-top settlement
* Polish Direct Connections context settings
* Fix onboarding selection row spacing
* Address PR review feedback
* Harden context and native request bounds
* Strengthen compaction regression coverage
* Close final context and image review gaps
* Validate structured array bounds
* fix: gate reasoning effort by model support
Automatic previously omitted the params key, so OpenWebUI's shallow settings merge retained the old reasoning_effort value. Explicitly replace the params map when clearing and fail closed at the request boundary for models that do not advertise support.
* address reasoning effort review feedback
* fix: hydrate workspace reasoning effort details
The earlier fix trusted the OpenWebUI model catalog, but current OpenWebUI deliberately strips workspace model params from /api/models. Fetch and cache the selected workspace model detail so its configured custom effort wins over the user-level fallback and reaches the native selector.
* fix: avoid workspace effort hydration race
Do not fall back to the user-level effort while private workspace model params are still loading. Omitting the override during that window lets OpenWebUI apply its model configuration, and the held-response regression test prevents the first-send race from returning.
* refactor: generate workspace effort provider
* fix: retry workspace effort after failures
* fix: handle unavailable workspace effort
* fix: hide unhydrated native effort controls
* fix: progressively hydrate native reasoning effort
* fix: guard native effort callback rollback
* fix: serialize native effort updates
* test: distinguish native effort update callbacks
* feat: bundled JSON release notes for 4.0, polished changelog sheet, remove IAP
- Move release-note content from ARB to assets/release_notes/<locale>.json
with locale-fallback repository and schema-checked validator
- Polish the release notes sheet (staggered reveal, version badge,
feature glyph bullets, action cards) and add the 4.0 note in 13 locales
- Prune pre-4.0 notes
- Remove in_app_purchase and the tip jar entirely; keep the Buy Me a
Coffee donation link on all platforms
* Refresh release notes sheet and localized copy
* Add release notes banner and native review support
* Redesign release announcement sheet
* Target release announcement to 4.0.1
* Address release announcement review feedback
* Tighten release note locale handling
* Optimize performance and harden lifecycle handling
* Address PR review feedback
* Clean up duplicate App Intent image retries
* Fix follow-up pin-to-top routing
* Restore no-jump pin dismissal on user scroll
A later timeline optimization kept the synthetic pin spacer active through every manual scroll, undoing the guarded dismissal from #560 and allowing iOS range correction to snap the viewport. Restore the phantom-free range guard and cover both unsafe and safe dismissal offsets so the regression cannot recur silently.
* Rebuild chat turn anchoring like T3 Code
Create the anchor from the exact optimistic user-message ID, replace the full-screen phantom range and capped physics with measured anchored end space, and use item-level layout correction until real content fills the viewport. Cancel automatic corrections on the first user gesture so streaming growth cannot reintroduce scroll jumps.
* Fix Android cookie-clear verification and allow https-upgrade capture origins
The verified cookie clear treated Android's unimplemented getAllCookies as
failure, blocking SSO sign-in on empty stores and permanently arming the
incomplete-logout fence. Exact-origin capture checks silently dropped token
capture for http-configured servers upgraded to https by their proxy; capture
now also trusts the default-port https upgrade of the configured origin.
Also stabilize the background-validation sanitization test's poll deadline.
* Restore same-origin redirect recovery, pool warmup, and native fixes
- Replay credential-safe 3xx hops (same origin or default-port https
upgrade) for idempotent methods on the shared API client; cross-origin
hops still surface to the caller.
- Warm the completion client's actual connection pool at startup again;
checkHealth's request-scoped probe no longer touches it.
- Graceful ApiService dispose so provider rebuilds cannot abort in-flight
SSE streams; cap connectivity failure backoff at the healthy interval.
- iOS: thread the trusted origin into native sheet avatar loads so auth
headers are attached again; let oversized STT tap buffers fall back to
one-off copies instead of being dropped.
* Harden PR re-application: auth, upload/share, and provider regression fixes
Auth: logout preserves connection prerequisites (custom headers, mTLS)
while still revoking session credentials (legacy apiKey, captured proxy
Cookie headers); config-header edits and legacy apiKey migration no
longer sign the user out; cold-start background validation retries for
~7s to cover slow tunnels; interactively reissued byte-identical tokens
are accepted after logout; SSO button failures surface visibly.
Uploads/share: native-share durable keys derive from payload id +
ordinal instead of mutable content checksums; Hermes/direct-model
shares route through the local composer path instead of retrying
forever; pre-connection network failures defer instead of failing
terminally; orphaned receipt-held rows are garbage collected once
native storage is confirmed drained; disposed-queue persistence reports
failure so staged files survive; legacy staging roots are reclaimable.
Providers/UI: queued-completion banner watches every ownership-fence
input so retry/cancel cannot silently no-op; drawer keeps previous rows
during pagination reloads; authenticated image cache keys derive from a
stable server+token digest so the disk cache survives restarts while
accounts stay isolated.
* Make share staging indeterminate-ownership test hermetic under concurrency
The test snapshotted the process-global staging temp root, so files staged
or cleaned by concurrently running suites broke exact set equality. Assert
only that this test's own artifact never appears.
* Stabilize streaming UI and pending share persistence
Persist Android pending-share state atomically with migration coverage. Keep prompt anchoring and streaming haptics stable across row remounts, and reduce markdown streaming churn while hardening placeholder cleanup.
* Make streaming Markdown preparation incremental
* Reuse stable Markdown render inputs
* Avoid cumulative structured stream rebuilds
* Instrument and streamline structured output
* Hide dismissed sidebar native chrome
* Reduce streaming platform view retention
Refactor chat timeline anchoring and streaming rendering, extend Hermes session and history support, and preserve stable native navigation and Liquid Glass controls.
Validated with flutter analyze and flutter test (4,012 passed; 2 skipped).
Adds capability-aware Workspace management for Models, Knowledge, Prompts, Skills, and Tools, and unifies settings navigation across Android and iOS. Includes native-sheet routing fixes, adaptive Workspace back navigation, Hermes settings integration, localization, and regression coverage.
- Introduced background lease management for microphone access in the AudioRecordingService to ensure uninterrupted recording during background execution.
- Refactored the service to utilize a new AudioRecordingBackgroundCoordinator for managing microphone leases on Android.
- Updated error handling to release background leases appropriately during recording operations.
- Enhanced the audio recording configuration to support high-quality audio capture with improved compatibility across platforms.
- Introduced new methods in MainActivity and AppDelegate to manage pending share imports, including status checks and payload retrieval.
- Updated share handling logic to accommodate staged share payloads, improving the user experience for shared content.
- Enhanced the share receiver service to support new import status tracking and error handling for shared attachments.
- Implemented a skeleton UI for file attachments during import, providing visual feedback to users.
- Updated tests to validate the new share import features and ensure robust handling of shared content across platforms.
- Introduced avatarBytes property to the PlatformNativeSheetModelOption data structure in Kotlin, Swift, Dart, and other related files.
- Updated serialization and deserialization logic to handle the new avatarBytes property.
- Enhanced the NativeSheetBridge and hydration service to support avatarBytes, allowing for improved avatar handling.
- Added a new fetchImageBytes method in ApiService to retrieve image data as bytes.
- Updated tests to validate the integration of avatarBytes in various components.
- Added the receive_sharing_intent package to manage shared content from other applications.
- Updated pubspec.yaml and pubspec.lock to include the new dependency.
- Refactored MainActivity and AppDelegate to remove deprecated share handling logic and streamline intent processing.
- Introduced ShareExtension for iOS to handle shared content effectively.
- Enhanced share receiver service to utilize the new package for processing shared media files.
- Updated tests to cover new sharing functionality and ensure proper handling of shared content.
- Removed unnecessary FOREGROUND_SERVICE_CAMERA permission as it is not utilized by Conduit.
- Added FOREGROUND_SERVICE_PHONE_CALL permission to support phone call functionality.
- Adjusted CallkitNotificationService to specify phoneCall and microphone as foreground service types, ensuring compliance with Play Console requirements.
- Updated the `PlatformNativeSheetModelOption` to include a `tags` field, allowing for better categorization and filtering of models.
- Enhanced serialization and deserialization logic to handle tags in both Dart and Swift implementations.
- Modified UI components to display tags, improving user experience by providing additional context for each model.
- Updated search functionality to include tags, enabling users to find models more efficiently based on associated tags.
- Added tests to ensure correct extraction and handling of tags from model data.
- Added an exception for `conduit_platform_apis.g.dart` in .gitignore to ensure it is tracked.
- Updated analysis options to exclude `pigeons/**` and added `riverpod_lint` plugin.
- Bumped versions of several dependencies in pubspec.lock, including `analyzer`, `flutter_riverpod`, and `json_serializable`, while removing outdated dependencies like `custom_lint`.
- Introduced methods for persisting and restoring pending share payloads in MainActivity, improving share functionality.
- Implemented intent sanitization for home widget launches to prevent unintended behavior when re-launching from history.
- Updated share handling logic to ensure accurate delivery and cleanup of share payloads across the application.
- Removed the deprecated HomeWidgetPlugin, streamlining the widget integration process.
- Refactored related services to support new share staging cleanup processes, enhancing file management during share operations.
- Bumped version of home_widget to 0.9.2 in pubspec files.
- Removed share_handler dependency from pubspec.yaml and related references.
- Refactored ConduitWidgetProvider and MainActivity to utilize new share handling logic.
- Implemented a new share receiver service to manage shared content more effectively.
- Updated iOS ShareExtension to handle shared files and text with improved constraints.
- Added geolocation dependencies in `pubspec.lock` and updated `pubspec.yaml` to include `geolocator`.
- Updated AndroidManifest.xml to request location permissions.
- Enhanced iOS Podfile to bypass location permission prompts for `geolocator_apple`.
- Modified Info.plist to clarify location usage description.
- Implemented `updateUserInfo` method in `ApiService` to handle user location updates.
- Refactored chat providers to resolve user location when needed and updated tests to validate new functionality.
- Updated Gradle wrapper to version 8.14.3 for improved performance and features.
- Upgraded Android Gradle Plugin to version 8.11.1 and Kotlin plugin to version 2.2.20 for better compatibility and enhancements.
- Added properties to maintain legacy Kotlin/AGP behavior until Flutter plugins fully migrate to built-in Kotlin.
- Refactored chat page and related widgets to utilize new scroll cache extent handling for improved performance and layout consistency.
- Configured resolution strategy in `build.gradle.kts` to use version 1.1.1 of `glance-appwidget` to prevent resolution to alpha versions that require newer Android tooling.
feat(chat): enhance chat page layout and button styles
- Updated chat page layout to ensure proper positioning of input elements, improving responsiveness.
- Adjusted button styles in `assistant_message_widget.dart` and `terminal_tab.dart` to differentiate between Android and other platforms, enhancing visual consistency.
- Added `textHeightBehavior` to `MiddleEllipsisText` for better text rendering control.
- Improved button styles in `openwebui_sources.dart` to adapt based on platform, ensuring a cohesive user experience.
- Included `android:networkSecurityConfig` attribute in AndroidManifest.xml to enhance app security.
- Updated socket TLS handling in `socket_tls_override_impl_io.dart` to utilize a custom HTTP client adapter for improved WebSocket connections.
- Refactored the socket connection logic to streamline the handling of server configurations and TLS settings.
- Updated the launch mode of MainActivity in AndroidManifest.xml from singleInstance to singleTask to improve task management.
feat(chat): enhance image picking functionality in FileAttachmentService
- Added platform-specific image picking logic for Android and iOS using ImagePicker and FilePicker.
- Implemented error handling for image picking failures to improve user experience.
- Introduced a new method for picking images with the ImagePicker for better compatibility across platforms.
- Introduced a new `StreamingLease` data class to encapsulate streaming lease details, including ID, kind, and microphone requirements.
- Refactored `BackgroundStreamingService` and `BackgroundStreamingHandler` to utilize the new lease management system, enhancing clarity and maintainability.
- Updated methods to handle multiple streaming leases, improving background execution handling for chat and voice streams.
- Enhanced socket management by removing reliance on legacy stream IDs and implementing a more structured approach to background execution.
- Add support for attaching previously uploaded server files via new
ServerFilePickerSheet, including UI integration in chat composer and
file attachment service updates
- Implement note pinning with isPinned field in Note model,
NotePinToggler provider, sorted notes list with pinned section first,
and UI updates in notes list and editor
- Improve Android background streaming lifecycle management using
DefaultLifecycleObserver to start/stop foreground service based on
activity visibility
- Update Android notifications to use ic_hub icon, set priority to LOW,
and handle notification channel recreation safely
- Prevent chat history truncation by removing unnecessary
syncConversationMessages calls after chat completion and follow-up
generation
- Add model terminal auto-selection provider and preserve direct server
tool selections when switching models
- Update FileInfo model with enhanced metadata parsing for OpenWebUI 0.9+
and legacy formats, add Note model tests
- Add new tests for
Change theme colors from dark gray (#0D0D0D, #0A0A0A) to pure black (#000000) across Android splash screens, iOS launch backgrounds, and Flutter theme configuration. Add Android configuration to force dark mode off and configure system UI behavior for proper splash screen display.