feat(identity): regenerate control + degraded-identity warning (W7 P1)

Complete P1: surface the identity in Settings and let the user manage it.

- New UiEvent::IdentityStatus { node_id, persisted, error }, sent at startup
  and after a regenerate, so the app always knows its own node id and whether
  the key is persisted.
- CoreCommand::RegenerateIdentity: mints + persists a fresh key (identity::
  regenerate), swaps the core's live key for the next join (same 'applies on
  next join' semantics as SetNetworkMode), and replies with a fresh status.
- Settings 'Identity' section: shows your permanent ID, a left-aligned
  Regenerate button behind a confirm modal (destructive — discards the old id,
  warns friends will stop recognising you), and a standing red warning banner
  when the key isn't persisted (disk/permission failure -> ephemeral fallback),
  explaining the id won't survive the next launch.

238 lib tests green, clippy clean, release builds. Screenshot-verified: the
Identity section, the confirm modal, and the degraded warning (chmod 000 the
key file -> 'Permission denied (os error 13)' banner; Regenerate clears it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 04:05:55 -04:00
co-authored by Claude Opus 4.8
parent 4ca497360b
commit a99c789716
3 changed files with 245 additions and 8 deletions
+11
View File
@@ -48,6 +48,10 @@ pub enum CoreCommand {
/// Watch a peer's screen share: spawn a pixelpass viewer for `ticket` and
/// open it in a local player.
ViewShare(String),
/// Mint a fresh persistent identity (W7), discarding the old one. Takes effect
/// on the next room join (the endpoint is rebuilt then). The core replies with
/// an updated [`UiEvent::IdentityStatus`].
RegenerateIdentity,
}
#[derive(Debug, Clone)]
@@ -78,5 +82,12 @@ pub enum UiEvent {
ScreenShareStarted,
/// Our own screen share stopped (or failed to start).
ScreenShareStopped,
/// Our node identity (W7): the current node id string, and whether it is
/// PERSISTED to disk. Sent once at startup and again after a regenerate.
/// `persisted = false` means the key file couldn't be read/written and we're
/// running on an ephemeral fallback — a degraded state the UI must surface,
/// since the id (and thus friend recognition) won't survive the next launch.
/// `error` carries the reason when degraded, for the UI explainer.
IdentityStatus { node_id: String, persisted: bool, error: Option<String> },
Error(String),
}