Fix overlapping sync status indicator

Render the connection state with one reactive SVG circle so connected and disconnected visuals cannot overlap after reconnects. Add regression coverage that keeps the indicator to a single shape.

Alessandro committed Jul 16, 2026 at 22:02 UTC 77349bd9a5a10084d253ae73c5d084ac5e22eec9
2 files changed +8 -13
tests/test_a0_connector_launcher_gateway.py
+1
@@ -73,6 +73,7 @@ def test_launcher_gateway_indicator_joins_sync_status_without_visual_noise() ->
73 )
74
75 assert '<x-extension id="sync-status-end"></x-extension>' in sync_status
76 + assert sync_status.count("<circle") == 1
77 assert ":title=" not in sync_status
78 assert 'x-for="(gateway, index)' in source
79 assert "launcher-gateway-trigger-label" not in source
webui/components/sync/sync-status.html
+7 -13
@@ -52,19 +52,13 @@
52 x-create="$store.sync.init()"
53 >
54 <svg viewBox="0 0 30 30" width="20" height="20" aria-label="sync status">
55 - <!-- HEALTHY (filled circle) -->
56 - <circle x-show="$store.sync.mode === 'HEALTHY'" cx="15" cy="15" r="8" fill="#00c340" />
57 -
58 - <!-- DEGRADED (filled circle) -->
59 - <circle x-show="$store.sync.mode === 'DEGRADED'" cx="15" cy="15" r="8" fill="#ff6b00" />
60 -
61 - <!-- HANDSHAKE_PENDING (pulsing outline) -->
62 - <circle x-show="$store.sync.mode === 'HANDSHAKE_PENDING'" class="pending-ring" cx="15" cy="15" r="12"
63 - fill="none" stroke="#f0a000" stroke-width="3" />
64 -
65 - <!-- DISCONNECTED (outline circle) -->
66 - <circle x-show="$store.sync.mode === 'DISCONNECTED'" cx="15" cy="15" r="12"
67 - fill="none" stroke="#e40138" stroke-width="3" />
55 + <circle
56 + cx="15" cy="15" r="12" fill="none" stroke="#e40138" stroke-width="3"
57 + :class="{ 'pending-ring': $store.sync.mode === 'HANDSHAKE_PENDING' }"
58 + :r="['HEALTHY', 'DEGRADED'].includes($store.sync.mode) ? 8 : 12"
59 + :fill="$store.sync.mode === 'HEALTHY' ? '#00c340' : $store.sync.mode === 'DEGRADED' ? '#ff6b00' : 'none'"
60 + :stroke="$store.sync.mode === 'HANDSHAKE_PENDING' ? '#f0a000' : $store.sync.mode === 'DISCONNECTED' ? '#e40138' : 'none'"
61 + />
62 </svg>
63 <x-extension id="sync-status-end"></x-extension>
64 </div>