fix: deduplicate online user count by username
Same user with multiple tabs/connections (different peer IDs) was counted multiple times. Now deduplicates by username, keeping the most recently seen entry per user for badge count and panel display. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
55771eb26e
commit
7fec0cb699
|
|
@ -485,6 +485,19 @@ export class RStackCollabOverlay extends HTMLElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Deduplicate peers by username, keeping the most recently seen entry per user. */
|
||||||
|
#uniquePeers(): PeerState[] {
|
||||||
|
const byName = new Map<string, PeerState>();
|
||||||
|
for (const peer of this.#peers.values()) {
|
||||||
|
const key = peer.username.toLowerCase();
|
||||||
|
const existing = byName.get(key);
|
||||||
|
if (!existing || peer.lastSeen > existing.lastSeen) {
|
||||||
|
byName.set(key, peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(byName.values());
|
||||||
|
}
|
||||||
|
|
||||||
// ── Color assignment ──
|
// ── Color assignment ──
|
||||||
|
|
||||||
#colorForPeer(peerId: string): string {
|
#colorForPeer(peerId: string): string {
|
||||||
|
|
@ -536,7 +549,8 @@ export class RStackCollabOverlay extends HTMLElement {
|
||||||
const countEl = this.#shadow.getElementById('panel-count');
|
const countEl = this.#shadow.getElementById('panel-count');
|
||||||
if (!list) return;
|
if (!list) return;
|
||||||
|
|
||||||
const onlineCount = this.#peers.size + 1;
|
const uniquePeers = this.#uniquePeers();
|
||||||
|
const onlineCount = uniquePeers.length + 1;
|
||||||
if (countEl) countEl.textContent = this.#connState === 'connected' ? `${onlineCount} online` : '\u2014';
|
if (countEl) countEl.textContent = this.#connState === 'connected' ? `${onlineCount} online` : '\u2014';
|
||||||
|
|
||||||
const fragments: string[] = [];
|
const fragments: string[] = [];
|
||||||
|
|
@ -566,9 +580,9 @@ export class RStackCollabOverlay extends HTMLElement {
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Remote peer rows
|
// Remote peer rows (deduplicated by username)
|
||||||
const isCanvas = this.#moduleId === 'rspace';
|
const isCanvas = this.#moduleId === 'rspace';
|
||||||
for (const [pid, peer] of this.#peers) {
|
for (const peer of uniquePeers) {
|
||||||
const ctxParts: string[] = [];
|
const ctxParts: string[] = [];
|
||||||
if (peer.module) ctxParts.push(peer.module);
|
if (peer.module) ctxParts.push(peer.module);
|
||||||
if (peer.context) ctxParts.push(peer.context);
|
if (peer.context) ctxParts.push(peer.context);
|
||||||
|
|
@ -580,15 +594,15 @@ export class RStackCollabOverlay extends HTMLElement {
|
||||||
<span class="name">${this.#escHtml(peer.username)}</span>
|
<span class="name">${this.#escHtml(peer.username)}</span>
|
||||||
${ctxStr ? `<span class="peer-context">${this.#escHtml(ctxStr)}</span>` : ''}
|
${ctxStr ? `<span class="peer-context">${this.#escHtml(ctxStr)}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
${isCanvas ? `<button class="actions-btn" data-pid="${this.#escHtml(pid)}">></button>` : ''}
|
${isCanvas ? `<button class="actions-btn" data-pid="${this.#escHtml(peer.peerId)}">></button>` : ''}
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
// Expanded actions for canvas
|
// Expanded actions for canvas
|
||||||
if (isCanvas && this.#openActionsId === pid) {
|
if (isCanvas && this.#openActionsId === peer.peerId) {
|
||||||
fragments.push(`
|
fragments.push(`
|
||||||
<div class="people-actions">
|
<div class="people-actions">
|
||||||
<button data-action="navigate" data-pid="${this.#escHtml(pid)}">Navigate to</button>
|
<button data-action="navigate" data-pid="${this.#escHtml(peer.peerId)}">Navigate to</button>
|
||||||
<button data-action="ping" data-pid="${this.#escHtml(pid)}">Ping to join you</button>
|
<button data-action="ping" data-pid="${this.#escHtml(peer.peerId)}">Ping to join you</button>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
@ -707,9 +721,10 @@ export class RStackCollabOverlay extends HTMLElement {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = this.#peers.size + 1; // +1 for self
|
const uniquePeers = this.#uniquePeers();
|
||||||
|
const count = uniquePeers.length + 1; // +1 for self
|
||||||
|
|
||||||
const dots = Array.from(this.#peers.values())
|
const dots = uniquePeers
|
||||||
.slice(0, 5) // show max 5 dots
|
.slice(0, 5) // show max 5 dots
|
||||||
.map(p => `<span class="dot" style="background:${p.color}"></span>`)
|
.map(p => `<span class="dot" style="background:${p.color}"></span>`)
|
||||||
.join('');
|
.join('');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue