Rebranding.BusymateHelper → Busymate on every UI surface (DashboardClient, SettingsShell, <head> title, login card, VersionBadge tooltip). iOS app name + bundle id stay BusymateHelper / com.busymatehelper.app — only the dashboard's display string changed.
Mobile header now renders Busymate v1.0 (N) inline under the app name on mobile, matching the desktop topbar.
/api/version whitelisted in proxy.tsPUBLIC_EXACT so BuildWatcher can poll it across all session states (pre-login, expired session). Previous behavior 307'd to /login, breaking auto-reload after a deploy.
Phantom-device delete at Settings → per-device → Settings tab now actually works:
DeviceSettingsTab.removeDevice was wired to onRemoved() taking no args. The parent just closed the modal. State, IDB, and per-device Maps kept the device name forever, so deleting felt like "nothing happens" — particularly on phantom rows (cached name, no DB row → 0 rows deleted → no trigger → no broadcast).
Plumbed onRemoved(name) through DeviceModal → DeviceTabs → DeviceSettingsTab so the cleanup fires after a successful API delete.
Rename duplicate-after-refresh fixed:
Broadcast UPDATE handler already remapped in-memory state — but every IDB-cached row still carried device: "<old-name>". Reload restored the stale-named entries into state before the live fetch arrived, so the device useMemo unioned both names.
New helper renameCachedEntriesForDevice(workspaceSlug, from, to) in app/lib/entriesCache.ts walks the workspace's rows with a cursor and rewrites the device field. Called from the rename handler.
Deploy script corrected from ssh ubuntu@p.bds.bot ... (OVH, decommissioning) to ssh ubuntu@busymate.net ... (DigitalOcean VPS, busymate-prod-lon1). The OVH path had been silently picking up every deploy since the rewrite, which is why dash.busymate.net was stuck on build 24 before today.
Supabase Edge-Function-side advisor + the official supabase-swift Examples + open issues were the primary references this morning. Findings vs ours:
Channel-cache-by-topic gotcha is alive in supabase-swift too (RealtimeClientV2.swift:298-311 — cached instances win, ignoring the new options closure). teardown() now calls removeChannel(...) on all three channels before disconnect() so a re-bringUp() rebuilds fresh.
Redundant setAuth(token) before subscribe removed — the accessToken: { ... } closure in RealtimeClientOptions runs on every channel join. Canonical SlackClone pattern.
Redundant onBroadcast(event: "UPDATE") on devices:all removed — postgres_changes on devices filtered by uuid=eq.<self> on the per-device channel already covers it (also canonical).
Presence track() now gated on devicesAll.statusChange.filter({ $0 == .subscribed }). First event fires on the initial subscribe; every silent reconnect after a heartbeat drop re-publishes presence automatically. Replaces the brittle "track right after subscribeWithError" race.
gracefulStopForBackground() cancels the long-lived presenceTrackTask before untracking. Without that cancel, a silent reconnect during the bg suspend would re-fire the status-change observer and re-track — net effect was "device shows online even though backgrounded" (the regression we hit on build 28 and fixed on build 29).
retrackForForeground() simplified to always call bounce(). The previous "cheap retrack on the existing channel" path tried track() over the iOS-frozen-from-bg socket; the SDK still reported .subscribed, so frames silently went to /dev/null until Phoenix's heartbeat-timeout (~60 s) declared the socket gone and reconnected. A full bounce() finishes in 1-2 s and the new bringUp's status-change loop re-tracks on the first .subscribed. Net: bg → fg flips the dashboard back to Online within seconds.
vsn: .v1 pin reinstated (after a controlled-removal test). GitHub research said v2-sender frames should propagate to v1 subscribers because Phoenix selects a serializer per-connection. Empirically they don't on this Realtime project — the dashboard (supabase-js, vsn=1 default) saw presence from the proxy-server peers but never from the v2 iOS sender. Pin restored with the rationale documented inline citing https://supabase.com/docs/guides/realtime/protocol. Audit finding 030 (upstream docs gap) stays open.
SetupGuideView.nameHeader now renders v1.0 (N) as a .caption secondary monospaced-digits line below the editable device name, matching the dashboard.
New migration 20260516000001_devices_self_insert_policy.sql:
sql
create policy devices_self_insert on public.devices for insert to authenticated with check (uuid = public.device_uuid());
Without it, every iOS UPSERT against public.devices (writeDeviceSetup + writeDeviceName) silently 401'd. PostgREST gates UPSERT on the INSERT policy first, regardless of whether ON CONFLICT would trigger UPDATE. Symptom: "renaming from iOS does nothing" — the catch only logged at .debug so the failure was invisible in monitoring. Iphone now does propagate renames to the dashboard.
nginx mcp.busymate.net.conf hardened with resolver 1.1.1.1 8.8.8.8 valid=300s + variable indirection. At 2026-05-16 06:23 UTC a transient DNS failure for xfjplaganjqowkcnznbr.supabase.co killed nginx's config test during a certbot-triggered reload, taking the entire VPS offline. The variable-indirection form defers DNS resolution to request time so a startup-time DNS hiccup can't take everything down.
OVH box (bd-us, p.bds.bot) services stopped + disabled. The proxy-vps-bd-us presence row in the dashboard came from there — its busymate-proxy.service was registering its identity (via ~/.busymate-proxy/identity.json on that host) into Supabase. Both busymate-proxy and busymate-dashboard are now inactive + disabled on the OVH box. Droplet itself untouched.
SECURITY.md + README.md — every p.bds.bot reference replaced with proxy.busymate.net. README's component-status table now reads https://proxy.busymate.net for proxy-server (matches CLAUDE.md's canonical-hostnames section).
Memory files — reference_deno_deploy_env.md + reference_deno_deploy_token.md deleted (Deno Deploy gone post-rewrite). reference_mcp_busymate_logs.md rewritten to point to the mcp.busymate.net Edge Function endpoint + Bearer-via-supabase-secrets rotation flow. reference_stack.md updated to call out the OVH decommission and the new SSH target. MEMORY.md index trimmed.
New feedback memory: feedback_inc_before_deploy.md codifies the user's new rule — every new build / redeploy must FIRST bump build project-wide. Don't redeploy under an already-deployed build number, both so the version badge / BuildWatcher / TestFlight build number are unique identifiers AND so BuildWatcher's auto-reload functions.