The tray Selected an exit node with append=false, which the RouteSelector
treats as "drop the whole current selection" (default-on semantics), so
enabling an exit node also turned off every non-exit routed network the
user had on. Send append=true instead and let the daemon's SelectNetworks
handler deselect only the sibling exit nodes — matching the frontend's
toggleExitNode, which already used append=true.
Add a RouteSelector regression test covering the handler sequence.
The Wails notifications service connects to the D-Bus session bus in its
ServiceStartup, which Wails runs synchronously inside app.Run. daemonFeed.Watch
was started before app.Run, so the first daemon SubscribeEvents message (which
replays the cached available-update state) fanned out to the tray's update-state
listener and fired an OS notification before that startup ran. The notifier's
*dbus.Conn was still nil, so SendNotification nil-dereferenced deep in godbus and
the panic was fatal to the whole process (observed on Linux Mint).
Move daemonFeed.Watch into the ApplicationStarted hook so it runs after the
service-startup loop, and route every notification send through a new
safeSendNotification helper that recovers from a panic and logs it, so a broken
or unavailable notification bus degrades to a skipped toast instead of crashing.
Use the shared logrus logger alias and carry the JS origin in a dedicated
"ui" log field instead of inlining a [ui ...] tag in the message, keeping
frontend logs distinct from the Go-caller source.
The Wails v3 tray is a pure DBus StatusNotifierItem implementation and no
longer links libappindicator (a Fyne-era dependency). Drop the
libayatana-appindicator runtime and build deps, and move the rpm package
and dev-dependency docs onto the GTK4 / WebKitGTK 6.0 stack that the
default (non-gtk3) build actually links against.
* Made the docker check first for getting-started.sh, better atomic support for install.sh
* Check for docker socket perms
* Added fallback for systems without rpm-ostree or bootc.
* macOS fix for docker socket check
* Change error message for docker group.
No longer using a blanket recommendation for the docker group.
Replace the doc comments above the empty no-op stubs (bindTrayClick,
startTrayTheme, noopSessionWatcher.Dismiss/Close) with short in-body
comments explaining why each is empty.
SwitchProfile now publishes the same profile-list-changed event that
AddProfile/RemoveProfile already emit. The daemon emits no dedicated
profile RPC event, and the React ProfileContext only refreshes on
EventProfileChanged (unlike the tray, which also re-fetches on every
status-string transition via loadProfiles). So a CLI-driven
"netbird down; profile select X; netbird up" refreshed the tray (the
down/up status flips trigger loadProfiles) but left the React profile
dropdown stale, since the select path never surfaced an event.
Publishing the marked INFO/SYSTEM event from SwitchProfile closes that
gap: dispatchSystemEvent re-emits EventProfileChanged, which
ProfileContext.refresh already subscribes to. No proto change.
The recursive ** skip pattern did not take effect with the codespell
shipped by the action, so de/common.json still tripped on real German
words. Use single-star globs (matched per path segment across versions)
and skip every translated locale, keeping only en as source of truth.
* [client] Surface session deadline rejections via SystemEvent and add timer arm debug logs
When sessionwatch.Watcher.Update rejects a deadline (pre-epoch, too far
in the future, or past the clock-skew tolerance) it silently zeroes the
status recorder, leaving the UI with no "expires in" row and no
indication of why. Publish a SystemEvent_ERROR on the AUTHENTICATION
channel so the rejection appears in the UI event feed and the user
knows re-login may be required.
Also add Debugf log lines in armTimerLocked so that warning and
final-warning timer fire-times are visible in logs without having to
add instrumentation after the fact.
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
* [client] Remove verbose arm-timer debug logs from sessionwatch
The per-arm Debugf lines added noise on every deadline update.
Rejection logging already happens at the call site in engine_authsession.go;
the watcher itself needs no extra instrumentation.
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
* [client] Leave userMessage empty on deadline-rejected event; use metadata key
Daemon-layer PublishEvent userMessage strings are not localized — the UI
reads metadata keys and builds its own locale-aware copy (same pattern
as the session-warning events in event.go). Drop the hardcoded English
sentence from the deadline-rejected event and instead surface the
rejection reason via a new MetaSessionDeadlineRejected metadata key so
the UI can detect and localize it.
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
* [client] Revert silent deadline-rejected event; restore userMessage
MetaSessionDeadlineRejected had no UI consumer: the tray only does
metadata-driven localisation for MetaSessionWarning events; all other
SystemEvents display userMessage directly (tray_events.go). Leaving
userMessage empty made the rejection invisible to the user.
Restore the English userMessage so the generic event path shows
something, and remove the unused MetaSessionDeadlineRejected constant.
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
* [client] Localize session deadline rejected notification via metadata key
Follow the same pattern as session-warning events: the daemon emits an
empty userMessage and puts the signal in a typed metadata key
(MetaSessionDeadlineRejected); the UI tray detects the key and builds a
locale-aware OS notification from i18n strings.
Changes:
- sessionwatch/event.go: add MetaSessionDeadlineRejected constant
- engine_authsession.go: empty userMessage, use the new metadata key
- ui/authsession/warning.go: re-export MetaDeadlineRejected for UI consumers
- ui/tray_events.go: gate on isDeadlineRejected alongside isSessionWarning;
new branch calls t.notify with localized title/body
- i18n locales (en/de/hu): add notify.sessionDeadlineRejected.{title,body}
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
---------
Co-authored-by: Claude <noreply@anthropic.com>
private services on a custom domain didn't resolve on clients — the synthesized DNS zone was anchored to the cluster, and the account's custom domains weren't even
loaded.
- account.go — SynthesizePrivateServiceZones now keys zones by a resolved apex (privateServiceDomainZone): cluster suffix → registered account.Domains (filtered by matching
TargetCluster, longest wins) → skip if none. One zone per apex; custom-domain services group under their registered domain.
- sql_store.go — GetAccount now loads account.Domains on both loaders (gorm Preload("Domains") + pgx goroutine via ListCustomDomains; errChan buffer bumped 12→16). This was
the reason the deploy didn't work — the relation was empty in prod.
- Tests — custom-domain zone synthesis cases (apex resolution, free+custom separation, sibling collapse, cluster mismatch, mixed cluster/custom/public) + GetAccount
domain-preload tests on sqlite and Postgres.
* [management] Copy private field on shallowCloneMapping
added test to ensure clone handles new fields
* Remove unnecessary debug logs from proxy service
* Increase Wasm binary size limit to 60MB in build validation