# Desktop releases and auto-update

> How the QuickDesk desktop client discovers updates via /version/latest, what the stable and beta channels do, and how to publish or roll back a release.

The QuickDesk desktop client does not ship its own update schedule. On
launch, its updater asks the account server one question: *what version
should I be running?* The answer comes from a single **update pointer**
that you control from the **Releases** screen.

Publishing a release moves that pointer. Promoting an older release
moves it back. Nothing else about the client changes.

## The update pointer and /version/latest

The client's updater calls `/version/latest` on the account server at
launch. The account server answers with the currently published
release:

| Field     | Meaning                                                        |
| --------- | -------------------------------------------------------------- |
| `version` | The semantic version string the client should be running.       |
| `channel` | `stable` or `beta`.                                             |
| `url`     | The download URL the updater fetches the build from.            |

The Releases screen shows exactly this record at the top of the page,
with a copy button for the `url` so you can verify by hand that the
artifact the fleet will download is the one you expect.

If nothing has ever been published, there is no pointer. The screen says
so directly — *"No version published — the client updater is a no-op."*
A client that launches in that state does not update; it keeps running
whatever build it has.

The pointer is a single value, not a per-client assignment. There is no
staged or percentage rollout in this screen: whatever is live is what
every polling client is told to run.

## Version format

The screen validates the version field before it will let you publish.
A version must be a semantic version — three dot-separated numbers, with
an optional pre-release suffix and an optional build-metadata suffix:

```
2.31.0
1.4.9
3.0.0-rc.2
1.4.9+build.77
```

The field is also capped at 64 characters. Release notes are optional
and capped at 2000 characters.

This validation is deliberately strict at the console layer. The value
you type here becomes the pointer every desktop auto-updater reads, so a
typo is not a local mistake — it ships to the whole fleet.

## Channels: stable vs beta

A release is published to one of two channels, `stable` or `beta`. The
channel travels with the pointer and is returned to the client on
`/version/latest`, so the client knows which track the version it is
being offered belongs to.

The console renders the distinction so it is hard to miss:

- `stable` is shown with the neutral / ok tone.
- `beta` is shown with the warning tone, both on the live pointer and on
  every row in the history table.

The channel is a property of the release, chosen at publish time from
the dropdown next to the version field. It is recorded in history
alongside the version, so you can see at a glance whether the currently
live build came off the stable track or the beta track.

## Publishing a release

From the Releases screen:

1. Enter the **version** (semver — see above).
2. Choose the **channel**, `stable` or `beta`.
3. Optionally add **release notes**. These show under the version in the
   history table.
4. Press **Publish**.

This calls `adminQuickdesk.releases.publish` with the trimmed version,
the selected channel, and the trimmed notes. On success the form clears
and both the live pointer and the history table refetch.

If validation fails, nothing is sent — the field errors appear inline
and the mutation is not fired. If the server rejects the publish, the
error message is shown beneath the form.

Publishing appends a row to history *and* moves the live pointer. The
newly published version is the one `/version/latest` starts serving.

## Promoting an earlier release (rollback)

Every row in the history table that is not currently live has a
**Promote** button. Promoting calls
`adminQuickdesk.releases.promote` with that release's `id` and points
the live updater at that version.

This is the rollback path. You do not republish an old version under a
new number — you promote the existing history row, and the pointer moves
back to it. The row that was live loses its **live** tag; the promoted
row gains it.

Because the live release is identified by matching the pointer's version
against the history rows, exactly one row carries the **live** tag at a
time, and that row has no Promote button.

## Where the pointer and history live

Two different stores back this screen, and the split matters when you
are debugging:

| What                | Where it lives                  | Read by                                        |
| ------------------- | ------------------------------- | ---------------------------------------------- |
| The live pointer    | A Redis key                     | `/version/latest`, served to every client       |
| The release history | The QuickDesk client releases table | `adminQuickdesk.releases.history`           |

The pointer is the hot path — it is what the account server reads on
every client launch poll. History is the durable record: version,
channel, publish time, and the operator who published it
(`published_by`, shown in the **By** column; a dash when unattributed).

The console reads the pointer with
`adminQuickdesk.releases.latest` and the record with
`adminQuickdesk.releases.history`, which the screen requests with a
limit of 50 rows.

A consequence of the split: history tells you what has been published,
but only the pointer tells you what clients are being served right now.
Trust the banner at the top of the screen and the **live** tag, not the
most recent publish timestamp.

## How long a change takes to reach clients

A publish or promote takes effect on the pointer immediately — the next
`/version/latest` response carries the new version.

But the client only asks **on launch**. A desktop client that is already
running has already made its poll for this session and will not see the
change until it next starts up. So the propagation time across the fleet
is not a server-side delay; it is however long it takes your users to
restart the app.

Plan rollbacks accordingly. Promoting a known-good version stops the bad
build from reaching anyone *new*, but it does not reach back into
sessions that already updated.

## Related

- [Authentication](/concepts/authentication) — how the console authenticates to the control plane
- [Telemetry](/platform/telemetry) — operational data streams from the gateway
