feat(apple): surface host online status on the home grid
ci / web (push) Failing after 36s
ci / docs-site (push) Failing after 39s
ci / rust (push) Successful in 1m19s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
apple / swift (push) Successful in 1m24s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
deb / build-publish (push) Successful in 2m52s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (push) Successful in 5m25s

Saved host cards now show a presence dot — green when the host is advertising on
the LAN right now, grey when not seen. Cross-references each StoredHost against the
live mDNS discovery set (HostDiscovery). No host changes: the host already
advertises _punktfunk._udp with a stable id + cert fingerprint, which the client
already browses.

- StoredHost.matches(DiscoveredHost): fingerprint-first (survives a DHCP address
  change), address:port fallback. The discovered-section dedup now uses the same
  match, so a saved host whose IP changed no longer also shows up as a stranger.
- HostCardView gains an isOnline presence dot (accessibility-labelled).
- HomeView.isOnline recomputes on every @Published discovery change, so the dot
  tracks hosts joining/leaving the network live.

Online detection is LAN-scoped by design: a remote/cross-subnet host that doesn't
advertise here shows grey ("not seen"), not a false "offline". Swift-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-13 14:32:56 +02:00
parent dad5a08c1f
commit 47112f44b7
3 changed files with 45 additions and 8 deletions
@@ -148,6 +148,7 @@ struct HomeView: View {
private func hostCard(_ host: StoredHost) -> some View {
HostCardView(
host: host,
isOnline: isOnline(host),
isConnecting: model.phase == .connecting && model.activeHost?.id == host.id,
isMostRecent: host.id == mostRecentHostID,
isBusy: model.isBusy,
@@ -176,12 +177,18 @@ struct HomeView: View {
.padding(.top, store.hosts.isEmpty ? 0 : 8)
}
/// Discovered hosts not already saved (matched by address+port) the saved grid shows the
/// rest, so this section only surfaces genuinely-new hosts on the network.
/// A saved host is "online" iff a live mDNS advert currently matches it (see
/// `StoredHost.matches`). Recomputed on every discovery change (the @Published set), so the
/// dot tracks hosts appearing/leaving the network live.
private func isOnline(_ host: StoredHost) -> Bool {
discovery.hosts.contains { host.matches($0) }
}
/// Discovered hosts not already saved the saved grid shows the rest, so this section only
/// surfaces genuinely-new hosts on the network. Same match as the online dot, so a saved host
/// whose IP changed (still fingerprint-matched) doesn't also appear here as a stranger.
private var discoveredUnsaved: [DiscoveredHost] {
discovery.hosts.filter { d in
!store.hosts.contains { $0.address == d.host && $0.port == d.port }
}
discovery.hosts.filter { d in !store.hosts.contains { $0.matches(d) } }
}
/// The host of the most recent session its card carries the accent ring.