The desktop app

Updated July 2026

DousenDesktop (a Qt6/C++ system-tray application) is the hub of the pipeline on every artist machine. It lives in the system tray for the whole session and does four jobs at once: it shows your Jira tasks and lets you open/submit Perforce changelists against them, it launches your DCCs with the Dousen addon already wired in, it runs the local HTTP bridge relay that lets those DCCs (and Unreal) talk to each other, and it keeps your login session and the app itself up to date in the background.

This page is both a walkthrough and a reference — the panel table, settings table, and offline-behavior tables below are meant to be looked up, not just read once.

First launch: onboarding

The very first time DousenDesktop runs on a machine, a full-window OnboardingWindow takes over before the tray icon or main window ever appears. It has a welcome screen followed by six steps — Server (Dousen Cloud or self-hosted URL) → Jira (base URL, email, API token) → Version Control (Perforce or Git) → Workspace (Perforce depot path and client mapping, skipped for Git) → Unreal Project (browse or auto-discover a .uproject) → DCC Applications (scan for installed DCCs to register). The full step-by-step, including the Perforce workspace auto-creation flow, is in Quick start.

Finishing onboarding sets config.onboardingComplete = true and the wizard never runs again automatically. From the sign-in screen (see below) a Re-run Setup link reopens it on demand — useful if a machine needs to be repointed at a different server or Perforce workspace from scratch.

Signing in

Once onboarding is done, DousenDesktop checks for a cached, still-valid session; if none exists it shows the sign-in overlay before the rest of the app is usable. What it looks like adapts to config.deploymentMode:

The Choose Studio picker dialog listing available studios and the signed-in user's role in each one.
Shown when an account belongs to more than one studio, before login completes.

Sign in with Browser is a PKCE (S256) OAuth flow: DousenDesktop starts a one-time local callback server on an OS-assigned loopback port, opens your default browser to the control plane's authorize page, and waits for the redirect. If the tab is closed or the flow is abandoned, the listener times out after 5 minutes and the button resets — nothing stays listening in the background past that.

Dousen enforces one active device per account: a 60-second heartbeat and every token refresh carry a device_id, and a server-side conflict comes back as an HTTP 409. When that happens the previous machine's session is cleared, a tray notification reads "Session active on another device. Please log in again," and it drops back to the sign-in screen. Legitimate refreshes otherwise happen silently — you're never interrupted just because a token expired.

For admins: single-device enforcement is per account, not per machine. A shared workstation (a review station, a render box artists take turns on) will boot its previous occupant every time someone else signs in with their own credentials. Give a shared machine its own dedicated Dousen account and license seat instead of having artists sign in as themselves on it.

The main window

Opening Dousen (from the tray icon, or automatically after sign-in) shows the main window: a header, an apps bar, three tabs, and a status bar.

The DousenDesktop main window on the My Tasks tab, showing a Jira task list, a detail pane with Open in Jira, Unshelve, Open CL, and Submit buttons, DCC launcher tiles, and the Perforce status bar.
The main window's default layout — apps bar, task list and detail pane, and status bar.
RegionWhat it does
HeaderProject picker, Settings button, Sign Out button. The picked project drives which conventions, validators, and pipeline settings your DCCs fetch, and is persisted to config.activeProjectId.
Apps barOne launch tile per configured DCC (AppLauncherBar) plus a + tile that opens Settings on the DCC Apps tab. A tile shows a connected dot while that app is registered on the bridge; see Launching DCCs.
My Tasks tabYour assigned Jira issues and a detail pane with changelist actions. See below and Jira tasks.
Bridge tabCurrently connected apps and the last 20 bridge events. See below and Bridge API.
Logs tabThe app's own log stream, with a Clear button.
Status barPerforce state on the left — P4: <stream>, P4: <client> (classic), or P4: not connected — and the app version on the right. See Perforce.

Closing the window (the X button) just hides it to the tray — the bridge relay and background polling keep running. Use the tray menu's Quit to actually exit the process.

My Tasks

TaskListWidget owns a Jira client, a poll timer, and a split view: a task list on the left, a detail card on the right. Selecting a task loads its Jira description (rendered HTML, images included) and publishes a task.context bridge event so any DCC you launch next starts with that task already selected.

The list fetches 50 issues per page (assignee = currentUser() AND statusCategory != Done); a Load more tasks… button under the list fetches the next 50 when more exist. From the detail pane you can:

The tab polls Jira on the configured task poll interval (Settings → Connection, default 30s) and re-fetches the shelved-changelist row on every tick. If Jira becomes unreachable, an offline circuit breaker backs the poll interval off instead of retrying — and failing — every tick:

Backoff levelPoll intervalTrigger
0 (normal)configured interval (default 30s)Default state; restored immediately on the next successful fetch.
1max(configured, 60s)First network failure (no HTTP response at all — distinct from an auth failure).
2 (cap)max(configured, 5m)A further failure while already backed off.

A single persistent banner — "Offline — can't reach Jira. Retrying automatically." — replaces per-tick error toasts while this is active, and clears with a "Connection restored" toast on recovery. A Jira auth failure (bad email/API token) is handled differently: polling stops outright rather than backing off, since retrying with the same stale credentials only repeats the failure — fix the token in Settings → Jira to resume.

The last successfully-fetched task list is cached to disk (TaskCache) and shown immediately on launch, before the first live fetch completes — so your tasks are visible even if Jira is briefly unreachable at startup, not just after the app has been open a while.

Bridge

BridgeStatusWidget shows a chip per app currently registered on the bridge relay and a timeline of its last 20 routed events (timestamp, event type, source/target app, file path), refreshed every 2 seconds. It's the fastest way to confirm a publish from a DCC actually reached DousenDesktop before you go looking for the reason it didn't show up elsewhere. The relay itself — its HTTP endpoints, auth model, and event schema — is a separate reference: see Bridge API.

The Bridge tab showing connected-app chips for Blender, Maya, Substance Painter, and Unreal Editor above a timeline of task.context, dcc.open, mesh.export, and texture.export events with their depot paths.
Each row lists the event type, source/target app, and file path.

Logs

A plain-text view of the app's own log stream (capped at 2000 blocks), with a Clear button. This is the first place to look when a launch or a bridge event doesn't do what you expect — see the troubleshooting callout under Launching DCCs.

The tray icon

The tray context menu offers four items:

Menu itemBehavior
Open DousenShows and focuses the main window (same as clicking/double-clicking the icon).
Manage Account…Opens <server>/portal/account in your browser. Shows an info toast instead if no server is configured yet.
Check for UpdatesRuns an update check immediately; see Automatic updates.
QuitActually exits the process — closing the window only hides it.

When one or more DCCs are registered on the bridge, the icon gains a green numeric badge — the connected-app count, capped at displaying 9+. Unreal counts toward it while its plugin is actively polling the relay (even without an explicit /bridge/register call), but never twice: if a live UE session has also registered by id, the poll-based check is skipped so it isn't double-counted. The badge re-evaluates every 3 seconds; hovering the icon shows the connected app names.

Notifications

Two kinds of bridge event surface as a desktop notification: Mesh exported/Texture exported (filename and source app) and Opening in DCC (another app, typically Unreal's content-browser "Open in DCC" entry, asked to open a file here). A 2-second batch window collapses a burst into one summary — e.g. "3 asset(s) exported, 1 DCC open request(s)" — so a big publish doesn't fire a notification per file.

Launching DCCs

Always launch DCCs from the apps bar rather than starting them directly — AppRegistry::launch() hands the process everything its Dousen addon needs to come up already wired in:

Context fieldPurpose
bridgePortThe relay port the addon polls (Settings → Connection, default 8766).
coreUrlDousenCore base URL, for the addon's own REST calls (conventions, validators catalog).
sourceRootThe active project's configured source root, anchoring relative/depot paths.
tokenThe current DousenCore JWT (env var DOUSEN_TOKEN), so the addon's settings/conventions fetches authenticate.
taskId / taskTitle / projectId / changelistIdPopulated when a task is selected in My Tasks, so the panel opens already scoped to it.

Executable paths come from Settings → DCC Apps, auto-filled by the Discover DCCs scanner or entered by hand; if a launch fails with "Executable not found", fix the path there.

The flow also runs in reverse. When another app publishes a dcc.open event — Unreal's "Open in DCC" content-browser entry is the main producer — SourceAssetTracker fetches that asset's source record from DousenCore and resolves a local path by storage type: Perforce runs p4 sync then p4 where for the client-mapped path; Google Drive/OneDrive joins the configured mount path (Settings → Connection) with the stored relative ref; local uses the last-known path as-is. It then matches the event's preferred DCC id against Settings → DCC Apps and launches it, starting the app first if it isn't already running.

If a DCC won't launch from a dcc.open event, check the Logs tab first — SourceAssetTracker logs exactly why it stopped: "Could not resolve local path for source asset" (mount path or P4 workspace not configured), "Source file not found on disk" (the resolved path doesn't exist yet — common with Drive/OneDrive sync lag), or "No registered DCC found for id" (no app in Settings → DCC Apps matches the id the event asked for). Then confirm in the Bridge tab that the event actually arrived — if it never shows up, the problem is upstream of DousenDesktop: the source app never published, or it's pointed at the wrong relay port.

Per-addon behavior once launched — panel UI, export hooks, validator wiring — is documented per DCC in DCC addons.

Settings reference

The Settings dialog (header → Settings) is entirely artist-scoped and local to the machine. Project-wide configuration — naming conventions, validator rule sets, pipeline settings YAML — lives in the web portal and is not edited here; see Settings reference for that side. Five tabs:

The desktop Settings dialog with tabs for Connection, DCC Apps, Projects, Unreal, and Jira.
All five tabs are artist-scoped and stored locally on the machine.
TabFields
ConnectionServer URL; request timeout (1–120s, default 10s); task poll interval (5s–1h, default 30s); bridge HTTP relay port (1024–65535, default 8766); Google Drive / OneDrive mount paths; Test Connection.
DCC AppsThe configured DCC list (id, name, executable, icon, launch arguments, environment variables) plus a Discover DCCs scanner that adds any newly-found installs, and a Pulse connected indicator toggle for the apps-bar dots.
ProjectsPer-project local config, not synced to the server: the .uproject override, source root, source location type (local / Perforce / Google Drive / OneDrive), and P4 server/workspace. New projects are created on DousenCore from here too.
UnrealFallback .uproject path; Install Plugin (copies the bundled DousenUE plugin into the project's Plugins/ folder and registers it in the .uproject); headless-import toggle and UnrealEditor-Cmd path for importing while the editor is closed.
JiraBase URL, account email, API token (stored via TokenStore), Test Connection. See Jira tasks.

Automatic updates

UpdateChecker polls an HTTPS JSON manifest — version, per-platform download URL/size, SHA-256, and a base64 Ed25519 signature — 5 seconds after launch (unless a check already happened within the last 4 hours) and every 4 hours after that; Check for Updates in the tray menu runs one immediately regardless of the interval.

When the manifest's version is newer than the running build, UpdateDialog shows the version jump and changelog with three choices: Skip Version (remembered — that version is silently ignored on future automatic checks, though a manual check still reports it), Remind Later, or Download & Install.

The UpdateDialog showing the version jump, a changelog, and Skip Version, Remind Later, and Download and Install buttons.
Skipping a version only silences it on future automatic checks, not a manual one.

A download is only trusted after two checks both pass: the SHA-256 of the downloaded bytes must match the manifest, and the manifest's Ed25519 signature over those bytes must verify against the public key compiled into the build. Either the manifest URL or the download URL being non-HTTPS, a missing SHA-256, a missing signing key in the build, or a bad signature all fail closed — the update is refused rather than installed unverified. Once verified, the installer is written to your Downloads folder and Install & Restart runs it silently (/S on Windows) or hands it to the OS's file handler on Linux/macOS, then quits the app.

Working offline

DousenDesktop is designed to survive short network interruptions without losing artist work, across two independent mechanisms:

Jira tasks

Covered above under My Tasks: the last-fetched task list is cached to disk and shown immediately on launch, and a network-level Jira outage backs off the poll interval rather than erroring on every tick.

DousenCore operations

OperationQueue persists fire-and-forget calls to DousenCore that fail because the server is unreachable, and replays them once it's back:

Queued operationWhen it's queued
upsertSourceAssetA mesh/texture export's metadata upsert, when the live call can't reach DousenCore.
openChangelistOpening a Perforce CL against a task, when the live call can't reach DousenCore.

Both are idempotent server-side, so replaying them on reconnect is safe; a queue entry is removed only once the server gives a definitive response (success or a 4xx rejection — an operation the server has actively refused isn't retried forever). submitChangelist is deliberately not queued: it needs the server-assigned changelist id from a prior openChangelist, so it stays a live, online-only call. The queue is flushed automatically on every point where connectivity is re-established: login, a successful token refresh, and the periodic 30-second refresh-if-needed check.

Offline queuing is not indefinite. The queue records the last time it actually reached a licensed DousenCore; once that is more than 14 days in the past (mirroring DousenCore's own license grace period), new operations are refused rather than queued, and the artist must reconnect to a licensed server before pipeline actions resume. Offline mode extends the pipeline through short outages — it is not a way to run without a valid license.

Pre-configuring machines (admins)

Admins can skip per-machine onboarding with a config bundle — a small JSON file with the keys org_id, control_plane_url, and server_url. Two delivery methods:

The bundle URL and the server_url inside it must be HTTPS — plain HTTP is accepted only for localhost/127.0.0.1 (a dev convenience). Applying a bundle switches the app into cloud deployment mode.