feat(events): SSID enrichment for Wi-Fi roam + RF stir lines#86
Merged
Conversation
User report (2026-05-18): the Events panel renders Wi-Fi roam + RF stir events with raw BSSIDs only. The AP-name half is already handled when aps.yaml has the BSSID mapped, but the SSID never surfaces — for a user with multiple SSIDs on the same physical link (home/guest, office corp/IoT, cafe open/5G) the BSSIDs alone don't say which network was affected. Proposal: carry the associated SSID at the moment the event fires (the poller and environment monitor already have it on the Connection they observed) and render it on the event line. Renderer rules: - RoamEvent: single 'SSID: <name>' when previous_ssid == new_ssid (the common band-switch case), 'SSID: <prev> -> <new>' when they differ, omitted entirely when both are None or both are ''. - RFStirEvent: append '. SSID <name>' after the location body when event.ssid is non-empty; omit when None/empty. JSONL serialisation gains 'previous_ssid' / 'new_ssid' / 'ssid' keys (additive, skipped when None — old log entries diff-stable). Capabilities touched: events (schema), tui-shell (new rendering requirement). Drafted as separate from fix-bonjour-list-empties-after-ttl (PR #85) since the user's two asks are independent. Proposal validates strict.
The Events panel was rendering Wi-Fi events as raw BSSIDs only;
for a user with multiple SSIDs on the same physical link the
BSSIDs alone don't say which network was affected. AP-name was
already handled by aps.yaml lookup; SSID never surfaced.
Implementation:
- RoamEvent gains previous_ssid / new_ssid (default None,
appended at the end of the frozen dataclass for backwards
compat).
- RFStirEvent gains ssid (same shape).
- WiFiPoller tracks _last_ssid alongside _last_bssid /
_last_channel and fills both sides of the RoamEvent.
- EnvironmentMonitor.ingest takes optional ssid=, remembers
the latest non-None per BSSID, and attaches it to emitted
RFStirEvents. Both TUI and CLI ingest call sites updated
to pass ssid.
- _format_roam_event appends a single 'SSID: <name>' segment
when previous_ssid == new_ssid, 'SSID: <prev> -> <new>' when
different, omitted when both None or both '' (hidden).
- _format_rf_stir_event appends '. SSID <name>' when
event.ssid is non-empty.
- JSONL writer emits the new keys (previous_ssid, new_ssid,
ssid) when set; skips when None so old log entries stay
diff-stable.
- i18n catalog gains 'SSID: {ssid}', 'SSID: {prev} -> {new}',
'SSID {ssid}' entries (EN + ZH).
OpenSpec change: wifi-event-ssid-and-name-enrichment.
605 pytest (was 591, +14 new tests across event_log,
environment, poller, tui_helpers), regression clean, 20/20
specs validate, change validates strict.
This was referenced May 18, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the approved
wifi-event-ssid-and-name-enrichmentproposal: Wi-Fi event lines (roam + RF stir) now surface the affected SSID. AP-name lookup againstaps.yamlis unchanged — populating the inventory is still the user's job, but at minimum the SSID is now visible without any config.Before / after
Before (user's screenshot from 2026-05-18):
After (same environment, with
aps.yamlstill empty):After (same environment, with the BSSIDs mapped in
aps.yaml):What landed
RoamEventgainsprevious_ssid/new_ssid;RFStirEventgainsssid. All defaultNone, appended at the end of each frozen dataclass for backwards-compat.WiFiPoller): tracks_last_ssidalongside_last_bssid/_last_channel; fills both sides of the emittedRoamEvent.EnvironmentMonitor):ingest(...)takesssid=(default None), remembers the latest non-None value per BSSID in its state map, and attaches it to emittedRFStirEvents. Both TUI and CLI ingest sites updated to passssid._format_roam_event/_format_rf_stir_event):SSID: <name>when both sides equal;SSID: <prev> → <new>when different; segment omitted when both areNoneor both are""(hidden).· SSID <name>segment appended whenevent.ssidis non-empty; omitted otherwise.previous_ssid/new_ssid/ssidwhen set; skips whenNoneso old log entries stay diff-stable.SSID: {ssid},SSID: {prev} -> {new},SSID {ssid}added to EN + ZH catalogs.OpenSpec change
openspec/changes/wifi-event-ssid-and-name-enrichment/— modifiesevents(schema + JSONL keys) and adds anADDEDrequirement totui-shellcovering the rendering rules. Drafted as a separate change fromfix-bonjour-list-empties-after-ttl(#85) since the two asks are independent.Test plan
uv run pytest— 605 passed (was 591 → +14 new tests acrosstest_event_log,test_environment,test_poller,test_tui_helpers)uv run python scripts/tui_snapshot.py --mode regression— 12 scenarios passopenspec validate --specs --strict— 20/20 passopenspec validate wifi-event-ssid-and-name-enrichment --strict— pass🤖 Generated with Claude Code