# io.Connect Desktop 10.4

Source: https://docs.interop.io/desktop/getting-started/changelog/platform/10-4/index.html

## io.Connect Desktop 10.4

*Release date: 28.08.2026*

| Components | Version |
|------------|---------|
| Electron | [43.2.0](https://releases.electronjs.org/release/v43.2.0) |
| Chromium | 150.0.7871.129 |
| Node.js | 24.18.0 |

The following libraries are bundled with **io.Connect Desktop** 10.4 and will be used for [auto injection](https://docs.interop.io/desktop/getting-started/how-to/interop-enable-your-apps/javascript/index.md#auto_injection):

| Injected Library | Version |
|------------------|---------|
| [`@interopio/desktop`](https://www.npmjs.com/package/@interopio/desktop) | [6.22](https://docs.interop.io/desktop/getting-started/changelog/libraries/interopio-desktop/index.md#622) |
| [`@interopio/fdc3`](https://www.npmjs.com/package/@interopio/fdc3) | 2.11 |

## Breaking Changes

> ### Intent Resolver & Displays API
>
> The [Intent Resolver](https://docs.interop.io/desktop/capabilities/data-sharing/intents/overview/index.md#intent_resolver) now uses the [Displays API](https://docs.interop.io/desktop/capabilities/more/apis/index.md#displays-displays_api) in order to operate properly.
>
> As a result, if the Intent Resolver UI is enabled (which is the default), the Displays API will be enabled as well, even if you have explicitly disabled it in the configuration object for initializing the [`@interopio/desktop`](https://www.npmjs.com/package/@interopio/desktop) library. In this case, the library will log a warning in the console.
>
> To disable the Displays API, you must now also disable the Intent Resolver UI:
>
> ```javascript
> import IODesktop from "@interopio/desktop";
>
> const config = {
>     displays: false,
>     intents: { enableIntentsResolverUI: false }
> };
>
> const io = await IODesktop(config);
> ```

## New Features

> ### OpenID Connect Authentication
>
> **io.Connect Desktop** now supports [authentication via the OpenID Connect (OIDC) protocol](https://docs.interop.io/desktop/getting-started/security/index.md#authentication-openid_connect). Authentication is performed by a trusted identity provider and control is returned to the io.Connect platform via a loopback HTTP server or a custom protocol scheme. This enables you to authenticate your users against any identity provider supporting OIDC (Microsoft Entra ID, Auth0, Okta, Google, Keycloak, and more) without having to implement your own authentication controller.
>
> The claim extracted from the ID token becomes the identity of the authenticated user, which is then published to the io.Connect Gateway and to your apps.
>
> To enable OIDC authentication, set the `"authController"` property to `"oidc"` in the `"ssoAuth"` top-level key of the `system.json` [system configuration](https://docs.interop.io/desktop/developers/configuration/system/index.md) file of **io.Connect Desktop**:
>
> ```json
> {
>     "ssoAuth": {
>         "authController": "oidc",
>         "options": {
>             "authority": "https://login.microsoftonline.com/<tenant-id>/v2.0",
>             "clientId": "<client-id>",
>             "scopes": ["openid", "profile", "email", "offline_access"],
>             "usernameClaim": "preferred_username",
>             "redirect": { "type": "loopback" }
>         }
>     }
> }
> ```
>
> It's required to provide the OAuth 2.0 client ID, settings for redirecting after authentication, and either `"authority"` (for identity providers that support OIDC discovery), or both `"authorizationEndpoint"` and `"tokenEndpoint"` (for identity providers that don't support it).
>
> The `"options"` object has the following properties:
>
> | Property | Type | Description |
> |----------|------|-------------|
> | `"authority"` | `string` | Base issuer URL of the OIDC provider to be used for OIDC discovery at `/.well-known/openid-configuration`. Use this property for identity providers that support OIDC discovery. For providers that don't support OIDC discovery, use `"authorizationEndpoint"` and `"tokenEndpoint"` instead. |
> | `"authorizationEndpoint"` | `string` | Explicit authorization endpoint URL. Use this together with `"tokenEndpoint"` if the identity provider doesn't support OIDC discovery. For providers that support OIDC discovery, use `"authority"` instead. |
> | `"clientId"` | `string` | **Required.** OAuth 2.0 client identifier registered with the identity provider. |
> | `"endSessionEndpoint"` | `string` | Explicit end session endpoint to be used when the io.Connect platform initiates a logout. Use this when the identity provider doesn't advertise an end session endpoint in its discovery settings. If you don't specify an end session endpoint, the discovered one will be used. If neither is available, the io.Connect platform will only drop the local tokens and the identity provider session will be left intact (the user won't be logged out). |
> | `"extraAuthParams"` | `object` | Additional provider-specific query parameters to be appended to the authorization request. |
> | `"issuer"` | `string` | Issuer identifier of the OIDC provider (the base URL of the identity provider). Required when using explicit authorization and token endpoints for identity providers that don't support OIDC discovery. |
> | `"jwksUri"` | `string` | JWKS endpoint of the OIDC provider for verifying the signature of the ID token. Required when using explicit authorization and token endpoints for identity providers that don't support OIDC discovery. |
> | `"loginSurface"` | `"system-browser"` \| `"embedded-window"` | Specifies where the identity provider login will be displayed. If set to `"system-browser"` (recommended), the login will be displayed by opening the default system browser. If set to `"embedded-window"`, the login will be opened in an io.Connect Window (discouraged practice, blocked by some identity providers like Google). Defaults to `"system-browser"`. |
> | `"logoutOnShutdown"` | `boolean` | If `true`, a logout request for ending the identity provider session will be sent when the io.Connect platform shuts down. The next platform launch will require a new login. Defaults to `false`. |
> | `"redirect"` | `object` | **Required.** Redirection endpoint settings to be sent to the identity provider for redirecting after authentication. It's possible to configure a loopback HTTP server or a custom protocol scheme. |
> | `"scopes"` | `string[]` | OAuth 2.0 scopes to request from the identity provider during authentication. Defaults to `["openid", "profile", "email"]`. |
> | `"timeoutMs"` | `number` | Interval in milliseconds to wait for the authentication process to complete. Defaults to `300000`. |
> | `"tokenEndpoint"` | `string` | Explicit token endpoint URL. Use this together with `"authorizationEndpoint"` if the identity provider doesn't support OIDC discovery. For providers that support OIDC discovery, use `"authority"` instead. |
> | `"usernameClaim"` | `string` | The name of the claim to be extracted from the ID token and to be used as the identity of the authenticated user. Defaults to `"sub"`. |
> | `"window"` | `object` | Settings for the io.Connect Window when using an embedded window to display the login. |
>
> The `"redirect"` object accepts settings either for a loopback HTTP server, or for a custom protocol scheme.
>
> Use a loopback HTTP server on the local machine as a redirection endpoint. This is the recommended approach, supported by most identity providers:
>
> ```json
> {
>     "ssoAuth": {
>         "options": {
>             "redirect": {
>                 "type": "loopback",
>                 "host": "localhost",
>                 "port": 9372,
>                 "path": "/callback"
>             }
>         }
>     }
> }
> ```
>
> The `"redirect"` object for a loopback HTTP server has the following properties:
>
> | Property | Type | Description |
> |----------|------|-------------|
> | `"host"` | `"127.0.0.1"` \| `"localhost"` | Loopback host to use in the redirect URI. Defaults to `"127.0.0.1"`. |
> | `"path"` | `string` | Path for the loopback redirection URI. Defaults to `"/callback"`. |
> | `"port"` | `number` | Fixed loopback port. Use a fixed port for identity providers that require exact URI matching for redirection. Can be omitted for identity providers that don't enforce exact port matching, in which case the OS will assign an available port on startup. |
> | `"type"` | `string` | **Required.** Type of the redirection endpoint. Must be set to `"loopback"`. |
>
> > ⚠️ *Note that some identity providers accept only one of the two supported loopback hosts, so make sure that the value of the `"host"` property matches the redirect URI registered with the identity provider.*
>
> > ⚠️ *Note that Microsoft Entra ID, Okta, Google, and Keycloak ignore the loopback port, so you can omit the `"port"` property and let the OS assign an available one on startup - no fixed port has to be reserved. Auth0 requires a fixed port.*
>
> Use a custom protocol scheme for redirection after authentication only when the provider rejects loopback redirection URIs. The io.Connect platform will automatically register the specified scheme with the OS before initiating login:
>
> ```json
> {
>     "ssoAuth": {
>         "options": {
>             "redirect": {
>                 "type": "custom-scheme",
>                 "scheme": "ioconnect",
>                 "path": "callback"
>             }
>         }
>     }
> }
> ```
>
> The `"redirect"` object for a custom protocol scheme has the following properties:
>
> | Property | Type | Description |
> |----------|------|-------------|
> | `"path"` | `string` | Path for the custom protocol scheme. Defaults to `"callback"`. |
> | `"scheme"` | `string` | **Required.** Custom protocol scheme to be used as a redirection endpoint. |
> | `"type"` | `string` | **Required.** Type of the redirection endpoint. Must be set to `"custom-scheme"`. |
>
> > ⚠️ *Note that on macOS the OS routes only custom protocol schemes declared in the app bundle. Using any other custom protocol scheme other than the ones declared requires repackaging the app. You can also use a loopback redirection endpoint instead.*
>
> The `"window"` object has the following properties:
>
> | Property | Type | Description |
> |----------|------|-------------|
> | `"height"` | `number` | Height in pixels for the login window. |
> | `"mode"` | `"flat"` \| `"tab"` \| `"html"` \| `"frameless"` | io.Connect Window mode for the embedded login window. Defaults to `"html"`. |
> | `"userAgent"` | `string` | String to be used for the `User-Agent` request header. |
> | `"width"` | `number` | Width in pixels for the login window. |
>
> To initiate a logout on demand, invoke the `"T42.GD.Execute"` Interop method with a `"logout"` command. This will end the identity provider session, drop the local tokens, and by default restart **io.Connect Desktop**, so that a new login will be required:
>
> ```javascript
> const methodName = "T42.GD.Execute";
> const logoutArgs = { command: "logout", args: { restart: true } };
>
> await io.interop.invoke(methodName, logoutArgs);
> ```
>
> When an OIDC or an SSO authentication controller is configured, every request to a REST data store will now carry an `Authorization: Bearer <access-token>` header and an `auth` header holding the io.Connect authentication info. This allows your REST service to verify the token and to serve per-user data.
>
> > ⚠️ *Note that opening the identity provider login in an embedded window is a discouraged practice and is blocked by some identity providers like Google. When using an embedded window, a loopback redirection endpoint must specify a fixed `"port"`.*

> ### Isolated Browser Sessions for Apps
>
> It's now possible to start an app instance in an [isolated browser session](https://docs.interop.io/desktop/developers/configuration/application/index.md#isolated_browser_sessions_for_apps), so that its cookies, cache, local and session storage are separated from the default session and from all other isolated sessions. This is useful when you need to run several instances of the same app authenticated as different users, or when an app must not share its browsing data with the rest of the platform.
>
> To start an app in an isolated browser session, use the `"session"` property of the `"details"` top-level key in the [app definition](https://docs.interop.io/desktop/developers/configuration/application/index.md):
>
> ```json
> {
>     "name": "my-app",
>     "type": "window",
>     "details": {
>         "url": "https://example.com/my-app",
>         "session": {
>             "partition": "persist:my-app"
>         }
>     }
> }
> ```
>
> The `"session"` object has the following properties:
>
> | Property | Type | Description |
> |----------|------|-------------|
> | `"inheritCookies"` | `object` | Settings for copying cookies from the default session into the isolated session when it's created. Has no effect without `"partition"`. |
> | `"partition"` | `string` | Key of the isolated session. Prefix the value with `"persist:"` to store the session data on disk and preserve it between restarts. Without the prefix, the session is kept in memory only and is discarded when **io.Connect Desktop** exits. |
>
> The browser session configuration is preserved when the app instance is saved in Global and Workspace [Layouts](https://docs.interop.io/desktop/capabilities/windows/layouts/overview/index.md) - the browser session with which a window has been created is saved in the Layout and takes precedence over the session defined in the app definition when the Layout is restored.
>
> Apps that specify the same `"partition"` value share the same isolated browser session. A window opened by an app inherits the session of the app that opened it.
>
> If your app must reuse an existing authenticated session (e.g., an SSO session established in the default browser session), use the `"inheritCookies"` object to copy the relevant cookies into the isolated browser session at the moment it's created:
>
> ```json
> {
>     "details": {
>         "session": {
>             "partition": "persist:my-app",
>             "inheritCookies": {
>                 "domains": ["login.example.com"]
>             }
>         }
>     }
> }
> ```
>
> The `"domains"` property accepts a list of domains whose cookies will be copied from the default session. Each value matches the specified domain and its subdomains.
>
> All platform functionalities that operate on a browser session are applied to the isolated browser sessions as well. This includes NTLM authentication, web permissions, downloads, request and response header manipulation, URL filtering, proxy settings, and the injected io.Connect library.
>
> > ⚠️ *Note that cookies are copied only once, when the isolated browser session is created. Later changes to the cookies in the default session aren't propagated.*
>
> > ⚠️ *Note that the [Cookies API](https://docs.interop.io/desktop/capabilities/more/apis/index.md#cookies-cookies_api) always operates on the default browser session, regardless of the session of the calling window. Only the cookie storage is isolated, not the API.*

> ### Transparency & Hardware Acceleration
>
> By design, the Workspaces App and the Web Group App web pages are rendered over the app windows participating in the respective Workspace or web group. This requires the platform to ensure that the app windows below the web pages remain visible and can receive mouse events, which in turn determines how custom popups (menus, panels, overlays) displayed over the app windows must be implemented.
>
> The `-webkit-app-region` CSS property is now used in both [platform modes](https://docs.interop.io/desktop/developers/configuration/system/index.md#platform_modes) for defining which areas of the Workspaces App and the Web Group App are transparent (the app window below is visible and receives the mouse events) and which are opaque (the area remains rendered and clickable over the app windows):
>
> ```css
> /* This area will be passed through, so the app window below will be visible and will receive mouse events. */
> .app-window-area {
>     background-color: transparent;
>     -webkit-app-region: transparent;
> }
>
> /* This area will be opaque, making the custom popup visible and clickable over the app windows. */
> .my-custom-popup {
>     background-color: grey;
>     -webkit-app-region: opaque;
> }
> ```
>
> > ⚠️ *Note that the `-webkit-app-region` property isn't inheritable, so you must declare it explicitly on each element that has to define an area. Also, this property controls only how the platform treats the area, not how it looks - to make an element visually transparent or opaque, you still have to use standard CSS for background color and opacity.*
>
> > ℹ️ *For details on the transparent and opaque areas and how to define them, see the [Capabilities > Windows > Workspaces](https://docs.interop.io/desktop/capabilities/windows/workspaces/overview/index.md#extending_workspaces-transparent__opaque_areas) and the [Capabilities > Windows > Window Management](https://docs.interop.io/desktop/capabilities/windows/window-management/overview/index.md#extending_web_groups-transparent__opaque_areas) sections.*
>
> #### Default Platform Mode
>
> In the default platform mode, the Workspaces App and the Web Group App web pages are now rendered over the app windows, as they already are in the advanced platform mode, and the platform uses the transparent and opaque areas defined via the `-webkit-app-region` CSS property to determine which areas to pass through to the app windows below.
>
> As a result, custom popups, menus, and panels can now be implemented as regular HTML elements in both platform modes, and the dedicated [external popup windows](https://docs.interop.io/desktop/capabilities/windows/workspaces/overview/index.md#extending_workspaces-custom_popups-external_window_popups) that were required in the default platform mode since **io.Connect Desktop** 10.0 are no longer necessary.
>
> Configuration changes aren't required - in the default platform mode hardware acceleration is always enabled and this mechanism is always active.
>
> The following table summarizes what you have to do depending on your setup:
>
> | Setup | Action |
> |-------|--------|
> | You are using the default Workspaces App and Web Group App. | Nothing - the default apps already contain the necessary `-webkit-app-region` declarations. |
> | You have a custom Workspaces App or Web Group App using [`@interopio/workspaces-ui-react`](https://www.npmjs.com/package/@interopio/workspaces-ui-react) or [`@interopio/groups-ui-react`](https://www.npmjs.com/package/@interopio/groups-ui-react) earlier than 4.5.1. | Nothing - the platform will detect the library version and will continue to use external popup windows. |
> | You have a custom Workspaces App or Web Group App using [`@interopio/workspaces-ui-react`](https://www.npmjs.com/package/@interopio/workspaces-ui-react) or [`@interopio/groups-ui-react`](https://www.npmjs.com/package/@interopio/groups-ui-react) 4.5.1 or later and the `useExternalWindowPopup()` hook. | Nothing - your existing popups will continue to work. You can optionally migrate them back to popups created from HTML elements. |
> | You have a custom Workspaces App or Web Group App using [`@interopio/workspaces-ui-react`](https://www.npmjs.com/package/@interopio/workspaces-ui-react) or [`@interopio/groups-ui-react`](https://www.npmjs.com/package/@interopio/groups-ui-react) 4.5.1 or later with your own custom elements displayed over app windows, or you override or replace the default styles. | Mark your custom elements with `-webkit-app-region: opaque` or `-webkit-app-region: transparent`. |
>
> #### Advanced Platform Mode
>
> The `"UseAppRegion"` value for the `"mode"` property of the `"transparency"` object under the `"hardwareAcceleration"` property of the `"windowManagement"` top-level key in the `system.json` [system configuration](https://docs.interop.io/desktop/developers/configuration/system/index.md#window_management-hardware_acceleration) file of **io.Connect Desktop** is now available in the 10.x release line.
>
> When [hardware acceleration](https://docs.interop.io/desktop/developers/configuration/system/index.md#window_management-hardware_acceleration) is enabled, the platform must cut the app window areas out of the Workspaces App and the Web Group App web pages so that the app windows below remain visible and can receive mouse events. This may cut out custom popups (menus, panels, overlays) displayed over the app windows.
>
> Setting the `"mode"` property to `"UseAppRegion"` instructs the platform to honor the transparent and opaque areas defined via the `-webkit-app-region` CSS property when determining which areas to cut out:
>
> ```json
> {
>     "windowManagement": {
>         "hardwareAcceleration": {
>             "enabled": true,
>             "transparency": {
>                 "mode": "UseAppRegion"
>             }
>         }
>     }
> }
> ```
>
> > ⚠️ *Note that hardware acceleration and the `"transparency"` settings are valid only in the advanced platform mode. In the default platform mode, hardware acceleration is always enabled and these settings won't have any effect.*
>
> > ⚠️ *Note that the `"UseAppRegion"` mode requires [`@interopio/workspaces-ui-react`](https://www.npmjs.com/package/@interopio/workspaces-ui-react) version 4.5.1 or later and [`@interopio/groups-ui-react`](https://www.npmjs.com/package/@interopio/groups-ui-react) 4.5.1 or later for the Workspaces App and the Web Group App respectively.*

> ### Workspace Tab Title Overflow
>
> Two new [CSS variables](https://docs.interop.io/desktop/developers/platform-styles/index.md#workspaces-workspace_tab) are available for controlling how the title of a Workspace tab behaves when it's longer than the available space:
>
> | Variable | Description |
> |----------|-------------|
> | `--workspace-tab-title-mask-image` | Mask image applied to the title of a Workspace tab, used for the text fade-out effect on text overflow. Set to `none` to disable the text mask. |
> | `--workspace-tab-title-overflow` | Controls how the title of a Workspace tab is rendered when it overflows the available space. |
>
> The existing `--workspace-tab-text-mask` variable is no longer applied to the Workspace tab titles directly - it now only defines the fade-out gradient and is used as the default value of `--workspace-tab-title-mask-image`. Any custom gradient specified for `--workspace-tab-text-mask` will continue to be applied as before.
>
> ```css
> #root {
>     /* The title will be truncated with an ellipsis instead of fading out. */
>     --workspace-tab-title-mask-image: none;
>     --workspace-tab-title-overflow: ellipsis;
> }
> ```
>
> > ⚠️ *Note that this feature requires [`@interopio/workspaces-ui-react`](https://www.npmjs.com/package/@interopio/workspaces-ui-react) version 4.6 or later.*

> ### Workspace Selection Metric
>
> [**io.Insights**](https://docs.interop.io/insights/general-overview/index.md) now publishes the `"workspace_selected"` [platform metric](https://docs.interop.io/insights/signals/metrics/index.md#platform_metrics-workspaces) for **io.Connect Desktop**. Use it to find out how often your users switch between Workspaces and which Workspace Layouts they actually work with.
>
> The metric is a counter which is incremented each time a Workspace becomes the selected one - when the user clicks its tab, when it's restored individually or as part of a Global Layout, and when a new Workspace is created. Selecting an already selected Workspace, renaming a Workspace, reordering the Workspace tabs, or focusing an app within the Workspace don't increment the metric.
>
> The metric has a `"layout"` attribute holding the name of the Workspace Layout, or `"unknown"` if the Workspace isn't tied to a saved Layout.

> ### Tracing Commands Sent from io.Manager
>
> [**io.Insights**](https://docs.interop.io/insights/general-overview/index.md) now publishes a [span](https://docs.interop.io/insights/signals/traces/io-connect-desktop/index.md) for each command that **io.Connect Desktop** receives from [**io.Manager**](https://docs.interop.io/manager/overview/index.md) and executes locally - e.g., retrieving system information, collecting logs, refreshing, restarting or stopping apps, restarting the platform, or executing code.
>
> The span is named `"interopio.desktop.manager.command"` and has the following attributes in addition to the [common span attributes](https://docs.interop.io/insights/signals/traces/io-connect-desktop/index.md#platform-common_span_attributes):
>
> | Attribute | Type | Description |
> |-----------|------|-------------|
> | `commandId` | `string` | ID of the command as assigned by **io.Manager**. |
> | `commandType` | `string` | Type of the command - e.g., `"GetSystemInfo"`, `"GetLogs"`, `"RestartApps"`, `"ExecuteCode"`. |
>
> The span is created as a child of the **io.Manager** span that has triggered the command and shares its trace ID. This allows you to follow an entire operation as a single trace in your observability backend (from the request in **io.Manager** to its execution in **io.Connect Desktop**), instead of having to correlate two unrelated traces manually.
>
> Both attributes can be used in the [span filtering context](https://docs.interop.io/insights/configuration/io-connect-desktop/index.md#traces-span_filters), which allows you to control the tracing of individual command types:
>
> > ⚠️ *Note that **io.Manager** must also be [configured to publish traces](https://docs.interop.io/manager/opentelemetry-support/traces/index.md). By default, a command that arrives without trace context from **io.Manager** won't start a new trace and no command span will be created for it.*

> ### Clearing Cache & Storage Data
>
> The `iodesktop` object injected in the global `window` object has been extended with two new methods for [clearing cache and storage data](https://docs.interop.io/desktop/capabilities/more/apis/index.md#clearing_cache).
>
> Use the `clearStorageData()` method to clear the storage data for the app. Pass an object with a `storages` property to clear only specific types of storage:
>
> ```javascript
> // Clearing all storage data.
> await iodesktop.clearStorageData();
>
> // Clearing only the cookies and the IndexedDB data.
> await iodesktop.clearStorageData({ storages: ["cookies", "indexdb"] });
> ```
> > ⚠️ *Note that the object accepted as an argument by the `clearStorageData()` method is fully aligned with the Electron storage data settings. For more details on the available properties and their accepted values, see the [Electron documentation](https://www.electronjs.org/docs/latest/api/session#sesclearstoragedataoptions).*
>
> Use the `clearAuthCache()` method to clear the cached HTTP authentication credentials:
>
> ```javascript
> await iodesktop.clearAuthCache();
> ```
>
> As with the already available `clearCache()` and `clearHostResolverCache()` methods, your app must have the `"allowClearingCache"` top-level key in its [app definition](https://docs.interop.io/desktop/developers/configuration/application/index.md) set to `true` in order to use these methods:
>
> ```json
> {
>     "allowClearingCache": true
> }
> ```
>
> All cache clearing operations are also available to the end users via the tray icon menu of **io.Connect Desktop**. The "Diagnostics" section has been reorganized into "System", "Cache", and "Debugging" submenus, with the "Cache" submenu holding the "Clear Preload Scripts Cache", "Clear Network Cache", "Clear Host Resolver Cache", "Clear Storage Data", and "Clear Auth Cache" options.

> ### Download File Path
>
> The `displayInfo` object inside the [`DownloadItem`](https://docs.interop.io/desktop/reference/javascript/io.connect%20desktop/downloaditem/index.md) object has been updated with a new `path` property that holds the full path of the downloaded file - the directory of the saved file and the actual name with which the file was saved. The `path` property is populated once the download has been completed successfully and is `undefined` for downloads that are still in progress or have failed.
>
> The `DownloadItem` object is used by the [`list()`](../../../../reference/javascript/io.connect desktop/downloadsmanager/index.html#DownloadsManager-list) and [`onUpdate()`](https://docs.interop.io/desktop/reference/javascript/io.connect%20desktop/downloadsmanager/index.md#DownloadsManager-onUpdate) methods of the API exposed via the `downloads` property of the global `iodesktop` object. You can use the new `path` property to determine the location of the downloaded file after the download has been completed.
>
> The following example demonstrates how to retrieve the path of a completed download:
>
> ```javascript
> const handler = item => {
>     if (item.displayInfo.state === "completed") {
>         console.log(`Download saved to "${item.displayInfo.path}".`);
>     }
> };
>
> iodesktop.downloads.onUpdate(handler);
> ```

> ### Window Title Priority
>
> The title of an io.Connect window can be set from several sources - the user (by editing the window title manually), the io.Connect API, the HTML document of the app, and the app definition. It's now possible to define explicitly the [title priority](https://docs.interop.io/desktop/capabilities/app-management/overview/index.md#app_titles-title_priority) - the order in which the platform will consider these sources when determining the effective window title.
>
> To specify the priority order, use the `"titlePriority"` property of the `"details"` top-level key in the [app definition](https://docs.interop.io/desktop/developers/configuration/application/index.md):
>
> ```json
> {
>     "name": "my-app",
>     "type": "window",
>     "title": "My App",
>     "details": {
>         "url": "https://example.com/my-app",
>         "titlePriority": ["custom", "user", "config", "document"]
>     }
> }
> ```
>
> The first source in the array that has a value will be used as the window title. Sources omitted from the array will be entirely ignored. Sources with no value (`undefined` or an empty string) will be skipped. If none of the listed sources has a value, the window title will be an empty string.
>
> The `"titlePriority"` property accepts an array with the following values:
>
> | Value | Description |
> |-------|-------------|
> | `"config"` | The title specified in the app definition. |
> | `"custom"` | The title specified dynamically via the io.Connect API. |
> | `"document"` | The title specified in the HTML document of the app. |
> | `"user"` | The title specified by the user by editing the window title manually. |
>
> If the `"titlePriority"` property isn't specified, the default priority order is `["user", "custom", "document", "config"]`. If the `"syncTitleWithDocumentTitle"` property is set to `"preserveCustomTitle"`, the default priority order is `["custom", "user", "document", "config"]`.
>
> > ⚠️ *Note that the `"titlePriority"` property takes precedence over the `"syncTitleWithDocumentTitle"` property. If `"titlePriority"` isn't specified, the existing title behavior will be preserved.*

> ### Suppressing System Error Dialogs
>
> **io.Connect Desktop** displays native OS dialogs on critical errors - e.g., when the system configuration is invalid, when no apps are available for the user, or when another instance of the platform is already running. It's now possible to [suppress system errors](https://docs.interop.io/desktop/developers/configuration/system/index.md#suppressing_system_error_dialogs) for unhandled exceptions and unhandled `Promise` rejections in the main process, so that **io.Connect Desktop** will exit silently and the error will only be written to the platform log files.
>
> To suppress the system error dialogs, use the `"suppressSystemErrors"` top-level key in the `system.json` [system configuration](https://docs.interop.io/desktop/developers/configuration/system/index.md) file of **io.Connect Desktop**:
>
> ```json
> {
>     "suppressSystemErrors": true
> }
> ```
>
> The same behavior can be achieved by starting **io.Connect Desktop** with the `--noDialogs` command line argument.
>
> On Windows:
>
> ```cmd
> io-connect-desktop.exe --noDialogs
> ```
>
> On macOS:
>
> ```cmd
> open -a "io.Connect Desktop" --args --noDialogs
> ```
>
> When a dialog is suppressed, the platform logs a warning containing the title and the content of the dialog and exits with an exit code corresponding to the error.
>
> > ⚠️ *Note that only the native dialogs originating from the platform are suppressed. Native dialogs displayed by apps running in the platform aren't affected.*

> ### OS Information for Remote Stores
>
> The metadata that **io.Connect Desktop** sends to remote stores (app, Layout, and app preferences) in the `gd` request header has been extended with information about the operating system of the user machine. This allows your REST service to make OS-aware decisions when serving app definitions, Layouts, or app preferences.
>
> The `gd` header holds a stringified JSON object which now contains an additional `"os"` property with the following shape:
>
> ```json
> {
>     "os": {
>         "arch": "x64",
>         "platform": "win32",
>         "release": "10.4.0"
>     }
> }
> ```
>
> The `"os"` object has the following properties:
>
> | Property | Type | Description |
> |----------|------|-------------|
> | `"arch"` | `string` | CPU architecture of the OS (e.g., `"x64"`, `"arm64"`). |
> | `"platform"` | `string` | Platform identifier of the OS (e.g., `"win32"`, `"darwin"`). |
> | `"release"` | `string` | Release version of the OS. |

## Improvements & Bug Fixes

> - Upgraded to Electron 43.2.0 (Chromium 150).
>
> - Reworked the per-window zoom implementation to use the native zoom control of the underlying browser engine. As a result, disabling the zoom for an app now reliably prevents it from being zoomed, and the zoom factor of an app is preserved when the app navigates or is reloaded. This also improves the handling of zoom in the standalone platform windows (the splash screen, the "Open" app, alerts, and dialogs) - the zoom in these windows is now isolated and changing it no longer affects the apps hosted on the same domain, and vice versa.
>
> - Improved the handling of the per app response header settings - the rules defined in an app definition are now applied to the apps loaded in `<iframe>` elements within the app as well, including cross-origin ones. Previously, the only way to modify the response headers for an app loaded in an `<iframe>` element was to define the rule globally.
>
> - Improved the positioning of the Intent Resolver on multi-monitor setups when using vertical monitors or different scaling.
>
> - Improved closing Workspaces and apps in them in order to prevent visual artifacts during the close operation.
>
> - Improved the platform mechanism for reporting the visibility of web apps hosted in the advanced platform mode via the standard [`document.visibilityState`](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState) property and the [`visibilitychange`](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event) event. Now, an app is reported as hidden when it's fully covered by another window, when its io.Connect window group or Workspace is minimized, or when it isn't the selected tab in a tab group, in a Workspace window tab group, or in a deselected Workspace. Irregularly shaped io.Connect window groups are excluded from the visibility checks against the windows behind them. The platform [throttles](https://docs.interop.io/desktop/developers/configuration/application/index.md#background_throttling) these hidden apps automatically and your apps can now also rely on the standard visibility APIs in order to pause expensive operations.
>
> - Fixed an issue with the width of the Workspace tabs and the Workspace window tabs, where the platform applied a maximum width programmatically, overriding the custom values specified via CSS variables.
>
> - Fixed an issue with retrieving the selected tab of an io.Connect window tab group on platform startup, where several windows in the same tab group could report themselves as the selected one.
>
> - Fixed an issue related to the order in which auto-started apps are launched in the case where an app is registered while a boot sequence stage is already running.
>
> - Fixed an issue with the dialog for unsaved Layout changes, where saving or discarding the changes threw an error if the current Layout had been removed in the meantime.
>
> - Fixed several issues on macOS related to hiding and showing the Workspaces App instances - the Workspaces App instance is now correctly displayed with its saved bounds and state, and initializing a hidden frame no longer makes it visible.
