Widget diagnostics: per-intent beacons (keychain slots + relay query)

The single-slot beacon couldn't distinguish small vs medium (last writer
wins). Beacon keychain service now embeds the intent type name so small
and medium never overwrite each other; the relay GET now carries the
intent type + entry state as query params so the access log shows exactly
which widget kind ran makeEntry, when, and with what. Diagnostics screen
shows SMALL and MEDIUM beacons side by side — a missing SMALL beacon
after adding the small widget proves its timeline never reaches the
provider (system-level config resolution failure).
This commit is contained in:
FuelBoard Contributor
2026-08-13 20:13:24 +01:00
parent 335a9751dc
commit 3cf1d5c9af
3 changed files with 50 additions and 23 deletions
+20 -8
View File
@@ -45,13 +45,15 @@ struct SettingsView: View {
@State private var debugMode: Bool = FuelStore.loadDebugMode()
@State private var versionTapCount = 0
@State private var lastVersionTap = Date.distantPast
/// Widget diagnostics: the extension's last makeEntry beacon (keychain)
/// + the installed-widget inventory from WidgetCenter.
@State private var widgetDiagBeacon: String?
/// Widget diagnostics: the extension's last makeEntry beacon PER intent
/// type (keychain) + the installed-widget inventory from WidgetCenter.
@State private var smallWidgetDiag: String?
@State private var mediumWidgetDiag: String?
@State private var installedWidgets: String = ""
private func refreshWidgetDiag() {
widgetDiagBeacon = FuelStore.loadWidgetDiag()
smallWidgetDiag = FuelStore.loadWidgetDiag(intentType: "FuelBoardSmallWidgetConfigurationIntent")
mediumWidgetDiag = FuelStore.loadWidgetDiag(intentType: "FuelBoardWidgetConfigurationIntent")
WidgetCenter.shared.getCurrentConfigurations { result in
let text: String
switch result {
@@ -139,13 +141,23 @@ struct SettingsView: View {
} label: {
Label("Refresh widget diagnostics", systemImage: "arrow.clockwise")
}
if let widgetDiagBeacon {
Text(widgetDiagBeacon)
if let smallWidgetDiag {
Text("SMALL: \(smallWidgetDiag)")
.font(.caption2)
.foregroundStyle(.secondary)
.textSelection(.enabled)
} else {
Text("No widget beacon yet — add a widget on the home screen, then refresh.")
Text("SMALL: no beacon yet")
.font(.caption2)
.foregroundStyle(.secondary)
}
if let mediumWidgetDiag {
Text("MEDIUM: \(mediumWidgetDiag)")
.font(.caption2)
.foregroundStyle(.secondary)
.textSelection(.enabled)
} else {
Text("MEDIUM: no beacon yet")
.font(.caption2)
.foregroundStyle(.secondary)
}
@@ -155,7 +167,7 @@ struct SettingsView: View {
} header: {
Text("Widget Diagnostics")
} footer: {
Text("The beacon is written by the widget extension at the end of every timeline entry (keychain, so it survives free-SideStore installs). Installed widgets come from WidgetCenter.")
Text("Each beacon is written by the widget extension at the end of every timeline entry for that intent type (keychain, so it survives free-SideStore installs). A missing SMALL beacon after adding the small widget means its timeline never reaches the provider. Installed widgets come from WidgetCenter.")
}
.onAppear { refreshWidgetDiag() }
}