hermes-agent/apps/desktop/src/lib/version-status.ts
Brooklyn Nicholson cb9e9b7272 refactor(desktop): resolve version/update labels in one place
The statusbar derived its client and backend version labels inline, in two
near-identical blocks. Move the wording into a pure resolver so every surface
that names an install agrees on the label, the commit diff, and the tooltip.
2026-07-29 00:24:38 -05:00

106 lines
3.5 KiB
TypeScript

/**
* Pure derivation of how the app names an update target: the label
* (`v0.4.2`, `backend v0.4.2 (+12)`, `v0.4.2 · update`), its tooltip, and
* whether an update is waiting.
*
* The statusbar and the command palette both name the same two targets, so the
* wording lives here once — a palette row and its statusbar item can't drift
* into describing the same install differently.
*/
import type { UpdateTarget } from '@/lib/update-copy'
export interface VersionStatusCopy {
backendLabel: (version: string) => string
backendVersion: (version: string) => string
branch: (branch: string) => string
clientLabel: (version: string) => string
commit: (sha: string) => string
commitsBehind: (count: number, branch: string) => string
desktopVersion: (version: string) => string
restart: string
unknown: string
update: string
updateInProgress: string
}
export interface VersionStatusInput {
/** True while an apply is in flight (including the restart hand-off). */
applying: boolean
/** Latest line from the apply stream — leads the tooltip while applying. */
applyMessage?: string
behind?: number
branch?: string
copy: VersionStatusCopy
/** Remote mode: the client is one of two versions on screen, so it says so. */
remote: boolean
/** The apply reached the restart stage — labels `restart`, not `update`. */
restarting: boolean
/** Client only: short commit sha of the running build. */
sha?: null | string
target: UpdateTarget
/** Backend only: an update the commit count can't express (pip installs). */
updateAvailable?: boolean
version?: null | string
}
export interface VersionStatusResult {
/** Secondary text beside the label — the commit sha, when it adds anything. */
detail?: string
/** An update is waiting: callers tint the row with it. */
hasUpdate: boolean
label: string
tooltip?: string
/** Nothing identifies this target yet — callers hide the row. */
unknown: boolean
}
export function resolveVersionStatus({
applyMessage,
applying,
behind = 0,
branch,
copy,
remote,
restarting,
sha = null,
target,
updateAvailable,
version = null
}: VersionStatusInput): VersionStatusResult {
const client = target === 'client'
const busy = applying || restarting
const available = behind > 0 || (!client && !!updateAvailable)
// A client with no version still identifies itself by sha; a backend can't.
const named = version ?? (client ? sha : null) ?? copy.unknown
const base = !client
? copy.backendLabel(named)
: remote
? copy.clientLabel(named)
: (version && `v${version}`) || named
// Commits behind is the precise diff; `(update)` is the fallback for a
// backend that knows it's stale but can't count (pip, non-git checkout).
const hint = busy ? '' : behind > 0 ? ` (+${behind})` : available ? ` (${copy.update})` : ''
const tooltip = [
busy && (applyMessage || copy.updateInProgress),
!busy && behind > 0 && copy.commitsBehind(behind, (client ? branch : 'main') || '...'),
!busy && behind <= 0 && available && copy.update,
version && (client ? copy.desktopVersion(version) : copy.backendVersion(version)),
client && sha && copy.commit(sha),
client && branch && copy.branch(branch)
]
.filter(Boolean)
.join(' · ')
return {
detail: client && version && sha && !busy && !remote ? sha : undefined,
hasUpdate: !busy && available,
label: busy ? `${base} · ${restarting ? copy.restart : copy.update}` : `${base}${hint}`,
tooltip: tooltip || undefined,
unknown: !version && !(client && sha)
}
}