feat: name your machines from web settings (#1214)

Machines are labelled by hostname with no way to give them a friendlier
name. `MachineMetadataSchema` has declared `displayName` all along and the
whole read path already honours it (`displayName → host → id`), but nothing
could ever write it: the CLI never sends the field, the hub exposed no route
that sets it, and the web UI had no editor.

Add the missing write path:

- `PATCH /api/machines/:id` with `{ displayName }`, guarded by the existing
  `requireMachine`. An empty value removes the key so the label falls back to
  the hostname; the empty string is never stored.
- `machineCache.renameMachine` merges that one key into the stored metadata
  and lets `refreshMachine` publish `machine-updated`, which `useSSE` already
  invalidates on — so every connected client relabels without new plumbing.
- A `/settings/machines` page listing online machines with inline rename,
  placed between Voice and About so the existing preference pages keep their
  order. Each row keeps the hostname visible, so a renamed machine is still
  identifiable.

The merge reads the raw stored metadata rather than the cached `Machine`
view. That view is narrowed by `MachineMetadataSchema`, which strips unknown
keys and yields `null` for a row that fails validation — reachable, since the
CLI's `machine-update-metadata` handler accepts `z.unknown()`. Merging
against it would have written those fields out of existence.

The row's save is guarded by a ref rather than `isPending`: disabling the
focused input forces a blur, so Enter otherwise reaches `save` twice and
fires two PATCHes, the second of which can lose the version race and report
a failure for a rename that succeeded.

`mergeMachineMetadata` already preserves hub-side fields on CLI
re-registration, so a reconnect does not clobber the name.

Closes #1210
This commit is contained in:
Haoqing Wang
2026-07-29 10:04:33 +08:00
committed by GitHub
parent e32fe146c3
commit 8d1f84e20b
14 changed files with 750 additions and 0 deletions
+14
View File
@@ -231,6 +231,20 @@ export const RenameSessionRequestSchema = z.object({
export type RenameSessionRequest = z.infer<typeof RenameSessionRequestSchema>
/**
* An empty string clears the custom name, so unlike session rename there is no
* `min(1)`: the machine falls back to its hostname. The length ceiling is
* enforced after trimming, so it is not expressed here.
*/
export const RenameMachineRequestSchema = z.object({
displayName: z.string()
})
export type RenameMachineRequest = z.infer<typeof RenameMachineRequestSchema>
export const MACHINE_DISPLAY_NAME_MAX_LENGTH = 64
/**
* Scratchlist v2 (tiann/hapi#893) per-entry caps.
*