Fix boot active-server selection and titlebar peer chip
Release rolling / release (push) Successful in 8m49s

Stop overwriting the header on every parallel restore connect; wait for
the last-used peer before falling back; avoid persisting temporary
active selection so the remembered server stays correct across restarts.
This commit is contained in:
Raven Scott
2026-07-16 19:20:02 -04:00
parent 3a2534c11c
commit 461a5498f9
5 changed files with 404 additions and 86 deletions
+37 -5
View File
@@ -315,20 +315,52 @@ export class ConnectionManager extends EventEmitter {
}
/**
* Mark a connected peer as the UI/RPC active server.
* @param {string} id
* @param {{ persist?: boolean }} [opts]
* persist (default true): write last-active to peers.json / localStorage.
* Use persist:false for temporary boot fallback so a faster sibling peer
* does not overwrite the user's real last-used server.
*/
setActive(id) {
setActive(id, opts = {}) {
const conn = this.connections.get(id)
if (!conn) return
this.active = conn
try {
setLastActivePeerId(id)
} catch {
// ignore persist failures
const persist = opts.persist !== false
if (persist) {
try {
setLastActivePeerId(id)
} catch {
// ignore persist failures
}
}
this.emit('active', conn)
}
/**
* Human label for header / fleet: alias when known, else short peer id.
* @param {string|null|undefined|{ id?: string, alias?: string|null }} idOrConn
* @returns {string}
*/
resolvePeerLabel(idOrConn) {
if (idOrConn == null) return 'peer'
const id =
typeof idOrConn === 'string'
? idOrConn
: String(idOrConn.id || idOrConn.publicKeyHex || '').slice(0, 12)
if (!id) return 'peer'
const live = this.connections.get(id)
const recon = this._reconnect.get(id)
const alias = String(
(typeof idOrConn === 'object' && idOrConn.alias) ||
live?.alias ||
recon?.alias ||
''
).trim()
if (alias) return alias
return `Peer ${String(id).slice(0, 6)}`
}
/**
* Close a live connection.
*
+55
View File
@@ -0,0 +1,55 @@
/**
* Active-server selection helpers (boot restore / peer switch).
* Pure functions — safe for unit tests without DOM or DHT.
*/
/**
* Loose id match: equal, or either is a prefix of the other (12-char vs longer).
* @param {string} a
* @param {string} b
*/
export function topicIdsMatch(a, b) {
if (!a || !b) return false
const x = String(a)
const y = String(b)
return x === y || x.startsWith(y) || y.startsWith(x)
}
/**
* Prefer last-known active peer if connected; otherwise first connected in list order.
* @param {string|null|undefined} lastActiveId
* @param {string[]} orderedKeys
* @param {Record<string, { peer?: { connected?: boolean, id?: string }|null }>} table
* @returns {string|null}
*/
export function pickPreferredActivePeer(lastActiveId, orderedKeys, table = {}) {
const keys = Array.isArray(orderedKeys) ? orderedKeys : []
if (lastActiveId) {
const match = keys.find(
(k) => topicIdsMatch(k, lastActiveId) && table[k]?.peer?.connected
)
if (match) return match
for (const k of keys) {
const peer = table[k]?.peer
if (peer?.connected && topicIdsMatch(peer.id, lastActiveId)) return k
}
}
const firstLive = keys.find((k) => table[k]?.peer?.connected)
return firstLive || null
}
/**
* Sort peer ids so last-active is first (stable for the rest).
* @param {string[]} keys
* @param {string|null|undefined} lastActiveId
* @returns {string[]}
*/
export function orderPeersForBoot(keys, lastActiveId) {
const list = [...(keys || [])]
if (!lastActiveId) return list
return list.sort((a, b) => {
if (topicIdsMatch(a, lastActiveId)) return -1
if (topicIdsMatch(b, lastActiveId)) return 1
return 0
})
}