fix(live-state): eliminate flash on poll by switching to innerHTML swap

outerHTML removes and re-inserts the section on each tick, causing a
blank frame. Keeping the <section> as a stable DOM container and
swapping only innerHTML means avatars and text update in-place without
any teardown/reconstruct cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-12 23:26:54 +02:00
parent 096d18ac64
commit 175e4e653c
No known key found for this signature in database
2 changed files with 59 additions and 64 deletions

View file

@ -1,65 +1,60 @@
<section class="panel live-state" <h2 class="section-title">Live state</h2>
hx-get="/servers/{{ server.id }}/live-state" {% if not snapshot or not snapshot_fresh %}
hx-trigger="every {{ poll_seconds }}s" <p class="muted">No data — server is not currently reporting.</p>
hx-swap="outerHTML"> {% else %}
<h2 class="section-title">Live state</h2> <p class="server-live-summary">
{% if not snapshot or not snapshot_fresh %} {{ snapshot.players }}/{{ snapshot.max_players }}
<p class="muted">No data — server is not currently reporting.</p> {% if snapshot.hibernating %}· idle{% endif %}
{% else %} · {{ snapshot.map }}
<p class="server-live-summary"> <small class="muted">
{{ snapshot.players }}/{{ snapshot.max_players }} polled {{ ((now - snapshot.last_seen_at).total_seconds() | int) }}s ago
{% if snapshot.hibernating %}· idle{% endif %} </small>
· {{ snapshot.map }} </p>
<small class="muted"> {% endif %}
polled {{ ((now - snapshot.last_seen_at).total_seconds() | int) }}s ago
</small>
</p>
{% endif %}
{% if current_players %} {% if current_players %}
<h3 class="section-subtitle">Current players</h3> <h3 class="section-subtitle">Current players</h3>
<ul class="player-grid"> <ul class="player-grid">
{% for session, profile in current_players %} {% for session, profile in current_players %}
<li class="player-card"> <li class="player-card">
<a class="player-link" <a class="player-link"
href="https://steamcommunity.com/profiles/{{ session.steam_id_64 }}" href="https://steamcommunity.com/profiles/{{ session.steam_id_64 }}"
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
{% if profile and profile.avatar_url %} {% if profile and profile.avatar_url %}
<img class="avatar" src="{{ profile.avatar_url }}" alt="" loading="lazy"> <img class="avatar" src="{{ profile.avatar_url }}" alt="" loading="lazy">
{% else %} {% else %}
<span class="avatar placeholder" aria-hidden="true"></span> <span class="avatar placeholder" aria-hidden="true"></span>
{% endif %} {% endif %}
<span class="name">{{ (profile and profile.persona_name) or session.name_at_join }}</span> <span class="name">{{ (profile and profile.persona_name) or session.name_at_join }}</span>
</a> </a>
<span class="meta"> <span class="meta">
joined {{ ((now - session.joined_at).total_seconds() // 60) | int }}m ago joined {{ ((now - session.joined_at).total_seconds() // 60) | int }}m ago
· ping {{ session.min_ping }}-{{ session.max_ping }}ms · ping {{ session.min_ping }}-{{ session.max_ping }}ms
</span> </span>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
{% if recent_players %} {% if recent_players %}
<h3 class="section-subtitle">Recent players</h3> <h3 class="section-subtitle">Recent players</h3>
<ul class="player-grid recent"> <ul class="player-grid recent">
{% for row in recent_players %} {% for row in recent_players %}
<li class="player-card"> <li class="player-card">
<a class="player-link" <a class="player-link"
href="https://steamcommunity.com/profiles/{{ row.steam_id_64 }}" href="https://steamcommunity.com/profiles/{{ row.steam_id_64 }}"
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
{% if row.avatar_url %} {% if row.avatar_url %}
<img class="avatar" src="{{ row.avatar_url }}" alt="" loading="lazy"> <img class="avatar" src="{{ row.avatar_url }}" alt="" loading="lazy">
{% else %} {% else %}
<span class="avatar placeholder" aria-hidden="true"></span> <span class="avatar placeholder" aria-hidden="true"></span>
{% endif %} {% endif %}
<span class="name">{{ row.persona_name or row.name_at_join }}</span> <span class="name">{{ row.persona_name or row.name_at_join }}</span>
</a> </a>
<span class="meta"> <span class="meta">
last seen {{ ((now - row.last_seen).total_seconds() // 60) | int }}m ago last seen {{ ((now - row.last_seen).total_seconds() // 60) | int }}m ago
</span> </span>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
</section>

View file

@ -22,7 +22,7 @@
<section class="panel live-state" <section class="panel live-state"
hx-get="/servers/{{ server.id }}/live-state" hx-get="/servers/{{ server.id }}/live-state"
hx-trigger="load, every 5s" hx-trigger="load, every 5s"
hx-swap="outerHTML"> hx-swap="innerHTML">
</section> </section>
<h2 class="section-title">Files</h2> <h2 class="section-title">Files</h2>