feat: mesh status indicator state helpers for chat header
Add resolveMeshStatusState, richer P2P badge labels (connecting/syncing/relay), and tooltip copy so the UI can show live mesh health for guild and DM.
This commit is contained in:
@@ -24,16 +24,28 @@ const { shouldShowMeshBoundedBadge, buildMeshBoundedBadgeContent } = boundedBadg
|
||||
const meshStabilityUiReconnectChromeFieldKeys = [
|
||||
'statsPeers',
|
||||
'hasStats',
|
||||
'p2pBadgeText'
|
||||
'p2pBadgeText',
|
||||
'meshStatusState'
|
||||
]
|
||||
|
||||
const meshStabilityUiReconnectChromeActionKeys = [
|
||||
'syncChatHeader',
|
||||
'syncPendingBanner',
|
||||
'syncServerRail',
|
||||
'updateP2pBadge'
|
||||
'updateP2pBadge',
|
||||
'updateMeshStatus'
|
||||
]
|
||||
|
||||
/** Visual / semantic states for the chat-header mesh status indicator. */
|
||||
const MESH_STATUS_STATES = Object.freeze([
|
||||
'idle',
|
||||
'local',
|
||||
'connecting',
|
||||
'live',
|
||||
'syncing',
|
||||
'relay'
|
||||
])
|
||||
|
||||
function listMeshStabilityUiReconnectChromeFieldKeys () {
|
||||
return meshStabilityUiReconnectChromeFieldKeys.slice()
|
||||
}
|
||||
@@ -42,19 +54,146 @@ function listMeshStabilityUiReconnectChromeActionKeys () {
|
||||
return meshStabilityUiReconnectChromeActionKeys.slice()
|
||||
}
|
||||
|
||||
function formatMeshStabilityP2pBadgeText (stats) {
|
||||
const peers = stats != null ? Math.max(0, Number(stats.peers) || 0) : null
|
||||
return peers != null
|
||||
/**
|
||||
* Resolve mesh status indicator state from peer count + context.
|
||||
* @param {object} [ctx]
|
||||
* @param {number|null} [ctx.peers] display peer count (null = no stats yet)
|
||||
* @param {string} [ctx.mode] view mode: guild | dm | home | …
|
||||
* @param {boolean} [ctx.meshBounded]
|
||||
* @param {boolean} [ctx.pending] guild sync pending
|
||||
* @param {number} [ctx.openGossip]
|
||||
* @param {number} [ctx.dmTopicPeers]
|
||||
* @param {number} [ctx.contactsReachable]
|
||||
* @param {boolean} [ctx.hasStats]
|
||||
*/
|
||||
function resolveMeshStatusState (ctx = {}) {
|
||||
const hasStats = ctx.hasStats != null ? !!ctx.hasStats : ctx.peers != null
|
||||
if (!hasStats && (ctx.peers == null || Number.isNaN(Number(ctx.peers)))) {
|
||||
return 'idle'
|
||||
}
|
||||
const peers = Math.max(0, Number(ctx.peers) || 0)
|
||||
const mode = String(ctx.mode || '')
|
||||
const pending = !!ctx.pending
|
||||
const meshBounded = !!ctx.meshBounded
|
||||
const dmTopicPeers = Math.max(0, Number(ctx.dmTopicPeers) || 0)
|
||||
const contactsReachable = Math.max(0, Number(ctx.contactsReachable) || 0)
|
||||
|
||||
if (peers > 0) {
|
||||
if (pending) return 'syncing'
|
||||
// DM: topic empty but contacts mesh is carrying traffic
|
||||
if (mode === 'dm' && dmTopicPeers === 0 && contactsReachable > 0) return 'relay'
|
||||
return 'live'
|
||||
}
|
||||
if (mode === 'guild' && meshBounded) return 'connecting'
|
||||
if (mode === 'dm' && (meshBounded || Number(ctx.openGossip) > 0)) return 'connecting'
|
||||
if (mode === 'guild' || mode === 'dm') return 'local'
|
||||
return 'local'
|
||||
}
|
||||
|
||||
/**
|
||||
* Short label for the mesh status pill (kept P2P-prefixed for legacy readers).
|
||||
* @param {object|null} stats
|
||||
* @param {object} [ctx] extra context for state (mode, meshBounded, …)
|
||||
*/
|
||||
function formatMeshStabilityP2pBadgeText (stats, ctx = {}) {
|
||||
if (stats == null && !ctx.forceState) return 'P2P · local'
|
||||
const peers =
|
||||
stats != null ? Math.max(0, Number(stats.peers) || 0) : Math.max(0, Number(ctx.peers) || 0)
|
||||
const state =
|
||||
ctx.state ||
|
||||
resolveMeshStatusState({
|
||||
peers: stats != null ? peers : null,
|
||||
hasStats: stats != null,
|
||||
mode: ctx.mode,
|
||||
meshBounded: ctx.meshBounded,
|
||||
pending: ctx.pending,
|
||||
openGossip: stats?.openGossip ?? ctx.openGossip,
|
||||
dmTopicPeers: stats?.dmTopicPeers ?? ctx.dmTopicPeers,
|
||||
contactsReachable: stats?.contactsReachable ?? ctx.contactsReachable
|
||||
})
|
||||
if (state === 'connecting') return 'P2P · connecting…'
|
||||
if (state === 'syncing') {
|
||||
return `P2P · syncing · ${peers} peer${peers === 1 ? '' : 's'}`
|
||||
}
|
||||
if (state === 'relay') {
|
||||
return `P2P · relay · ${peers} peer${peers === 1 ? '' : 's'}`
|
||||
}
|
||||
if (state === 'live') {
|
||||
return `P2P · ${peers} peer${peers === 1 ? '' : 's'}`
|
||||
}
|
||||
if (state === 'idle') return 'P2P · local'
|
||||
return peers > 0
|
||||
? `P2P · ${peers} peer${peers === 1 ? '' : 's'}`
|
||||
: `P2P · local`
|
||||
: 'P2P · local'
|
||||
}
|
||||
|
||||
/**
|
||||
* Richer tooltip for the mesh status indicator.
|
||||
*/
|
||||
function formatMeshStatusTitle (stats, ctx = {}) {
|
||||
const peers = stats != null ? Math.max(0, Number(stats.peers) || 0) : 0
|
||||
const state =
|
||||
ctx.state ||
|
||||
resolveMeshStatusState({
|
||||
peers: stats != null ? peers : null,
|
||||
hasStats: stats != null,
|
||||
mode: ctx.mode,
|
||||
meshBounded: ctx.meshBounded,
|
||||
pending: ctx.pending,
|
||||
openGossip: stats?.openGossip ?? ctx.openGossip,
|
||||
dmTopicPeers: stats?.dmTopicPeers ?? ctx.dmTopicPeers,
|
||||
contactsReachable: stats?.contactsReachable ?? ctx.contactsReachable
|
||||
})
|
||||
const mode = String(ctx.mode || '')
|
||||
const parts = []
|
||||
if (state === 'idle' || state === 'local') {
|
||||
parts.push(mode === 'dm' ? 'Direct message mesh idle' : mode === 'guild' ? 'Guild mesh local only' : 'Peer-to-peer mesh')
|
||||
} else if (state === 'connecting') {
|
||||
parts.push(mode === 'dm' ? 'Connecting to DM mesh…' : 'Connecting to guild mesh…')
|
||||
} else if (state === 'syncing') {
|
||||
parts.push(`Syncing with ${peers} peer${peers === 1 ? '' : 's'}`)
|
||||
} else if (state === 'relay') {
|
||||
parts.push(`DM relay via contacts · ${peers} reachable`)
|
||||
} else {
|
||||
parts.push(`Mesh live · ${peers} peer${peers === 1 ? '' : 's'}`)
|
||||
}
|
||||
const dmTopic = Math.max(0, Number(stats?.dmTopicPeers ?? ctx.dmTopicPeers) || 0)
|
||||
const contacts = Math.max(0, Number(stats?.contactsReachable ?? ctx.contactsReachable) || 0)
|
||||
if (mode === 'dm' && (dmTopic > 0 || contacts > 0)) {
|
||||
parts.push(`topic ${dmTopic} · contacts ${contacts}`)
|
||||
}
|
||||
const openGossip = Math.max(0, Number(stats?.openGossip ?? ctx.openGossip) || 0)
|
||||
if (openGossip > 0 && mode === 'guild') {
|
||||
parts.push(`${openGossip} channel gossip open`)
|
||||
}
|
||||
if (ctx.pending) parts.push('sync pending')
|
||||
if (ctx.meshBounded && peers === 0) parts.push('waiting for peers')
|
||||
return parts.join(' · ')
|
||||
}
|
||||
|
||||
function pickMeshStabilityReconnectChromeFields (view) {
|
||||
const stats = view?.stats
|
||||
return {
|
||||
statsPeers: Number(stats?.peers) || 0,
|
||||
const peers = Number(stats?.peers) || 0
|
||||
const mode = view?.mode || null
|
||||
const rail = view?.guild?.id ? view?.guildSyncHealthRail?.[view.guild.id] : null
|
||||
const ctx = {
|
||||
mode,
|
||||
meshBounded: !!(rail?.meshBounded || view?.guildMeshBounded),
|
||||
pending: !!view?.guildSyncHealth?.pending,
|
||||
openGossip: stats?.openGossip,
|
||||
dmTopicPeers: stats?.dmTopicPeers,
|
||||
contactsReachable: stats?.contactsReachable
|
||||
}
|
||||
const state = resolveMeshStatusState({
|
||||
peers: stats != null ? peers : null,
|
||||
hasStats: stats != null,
|
||||
p2pBadgeText: formatMeshStabilityP2pBadgeText(stats)
|
||||
...ctx
|
||||
})
|
||||
return {
|
||||
statsPeers: peers,
|
||||
hasStats: stats != null,
|
||||
p2pBadgeText: formatMeshStabilityP2pBadgeText(stats, { ...ctx, state }),
|
||||
meshStatusState: state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,11 +207,14 @@ function buildMeshStabilityReconnectChromeSnapshot (view) {
|
||||
module.exports = {
|
||||
meshStabilityUiReconnectChromeFieldKeys,
|
||||
meshStabilityUiReconnectChromeActionKeys,
|
||||
MESH_STATUS_STATES,
|
||||
MESH_PEER_DISPLAY_ZERO_DROP_HOLD_MS,
|
||||
MESH_PEER_CONTACT_GRACE_MS,
|
||||
listMeshStabilityUiReconnectChromeFieldKeys,
|
||||
listMeshStabilityUiReconnectChromeActionKeys,
|
||||
resolveMeshStatusState,
|
||||
formatMeshStabilityP2pBadgeText,
|
||||
formatMeshStatusTitle,
|
||||
createMeshPeerDisplayStabilizer,
|
||||
stepMeshPeerDisplayStabilizer,
|
||||
resolveMeshPeerContactAt,
|
||||
|
||||
Reference in New Issue
Block a user