From 553730681f898220b6d7c17f54496d727d238174 Mon Sep 17 00:00:00 2001 From: FuelBoard Contributor Date: Thu, 13 Aug 2026 20:27:03 +0100 Subject: [PATCH] A/B test: from-scratch minimal small widget (proven medium intent) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Temporary FuelBoardTestSmallWidget kind: a small-face widget driven by the PROVEN medium intent (fuel/sort/distance only — no favourite params, no dependency, no custom entity query), registered first in the gallery. Isolates small SIZE vs small INTENT: if it populates while FuelPriceSmallWidget skeletons, the small intent's extra machinery is the culprit; if it also skeletons, the failure is small-family/system-level. Provider gains a beaconTag so the test widget's beacons are distinguishable in the relay log and keychain slots. Removed once the root cause is confirmed. --- FuelBoardWidgets/FuelBoardWidgetsBundle.swift | 5 ++- FuelBoardWidgets/FuelPriceWidget.swift | 41 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/FuelBoardWidgets/FuelBoardWidgetsBundle.swift b/FuelBoardWidgets/FuelBoardWidgetsBundle.swift index e38f95e..de82e43 100644 --- a/FuelBoardWidgets/FuelBoardWidgetsBundle.swift +++ b/FuelBoardWidgets/FuelBoardWidgetsBundle.swift @@ -4,7 +4,10 @@ import SwiftUI @main struct FuelBoardWidgetsBundle: WidgetBundle { var body: some Widget { - // Gallery order follows registration order: small first, then medium. + // Gallery order follows registration order. The A/B test widget is + // first so it's easy to find; remove it once the small-widget root + // cause is confirmed. + FuelBoardTestSmallWidget() FuelPriceSmallWidget() FuelPriceWidget() FuelBoardLiveActivity() diff --git a/FuelBoardWidgets/FuelPriceWidget.swift b/FuelBoardWidgets/FuelPriceWidget.swift index 45088e0..6bfb4df 100644 --- a/FuelBoardWidgets/FuelPriceWidget.swift +++ b/FuelBoardWidgets/FuelPriceWidget.swift @@ -85,6 +85,35 @@ struct FuelPriceSmallWidget: Widget { } } +// A/B TEST widget (temporary): a from-scratch SMALL widget driven by the +// PROVEN medium intent — no favourite params, no dependency, no custom +// query. If this populates while FuelPriceSmallWidget skeletons, the small +// SIZE is fine and the small intent's extra machinery is the culprit. If it +// ALSO skeletons, the failure is at the small-family/system level. Removed +// once the root cause is confirmed. +struct FuelBoardTestSmallWidget: Widget { + let kind = "FuelBoardTestSmallWidget" + + var body: some WidgetConfiguration { + AppIntentConfiguration( + kind: kind, + intent: FuelBoardWidgetConfigurationIntent.self, + provider: FuelPriceTimelineProvider( + beaconTag: "FuelBoardTestSmallWidget" + ) + ) { entry in + FuelPriceWidgetView(entry: entry) + .containerBackground(for: .widget) { + Color(.systemBackground) + } + } + .configurationDisplayName("FuelBoard Small Test") + .description("A/B test: minimal small widget using the proven medium intent.") + .supportedFamilies([.systemSmall]) + .disfavoredLocations([.carPlay], for: [.systemSmall]) + } +} + struct FuelPriceEntry: TimelineEntry { let date: Date let stations: [FuelStation] // already filtered + sorted per config @@ -99,6 +128,12 @@ struct FuelPriceEntry: TimelineEntry { struct FuelPriceTimelineProvider: AppIntentTimelineProvider { typealias Intent = Configuration + /// Diagnostics beacon tag: empty → derive from the intent type name. A + /// widget kind that shares an intent (the A/B test small widget reuses the + /// medium intent) passes an explicit tag so its beacons stay + /// distinguishable in the relay log and keychain slots. + var beaconTag: String = "" + func placeholder(in context: Context) -> FuelPriceEntry { let unit = FuelStore.loadDistanceUnit() let sample = SampleFuelProvider.sampleStations @@ -128,7 +163,7 @@ struct FuelPriceTimelineProvider FuelPriceEntry { let entry = await makeEntryCore(configuration: configuration) - Self.writeDiagBeacon(entry: entry) + writeDiagBeacon(entry: entry) return entry } @@ -137,8 +172,8 @@ struct FuelPriceTimelineProvider