A/B test: from-scratch minimal small widget (proven medium intent)
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.
This commit is contained in:
@@ -4,7 +4,10 @@ import SwiftUI
|
|||||||
@main
|
@main
|
||||||
struct FuelBoardWidgetsBundle: WidgetBundle {
|
struct FuelBoardWidgetsBundle: WidgetBundle {
|
||||||
var body: some Widget {
|
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()
|
FuelPriceSmallWidget()
|
||||||
FuelPriceWidget()
|
FuelPriceWidget()
|
||||||
FuelBoardLiveActivity()
|
FuelBoardLiveActivity()
|
||||||
|
|||||||
@@ -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<FuelBoardWidgetConfigurationIntent>(
|
||||||
|
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 {
|
struct FuelPriceEntry: TimelineEntry {
|
||||||
let date: Date
|
let date: Date
|
||||||
let stations: [FuelStation] // already filtered + sorted per config
|
let stations: [FuelStation] // already filtered + sorted per config
|
||||||
@@ -99,6 +128,12 @@ struct FuelPriceEntry: TimelineEntry {
|
|||||||
struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & WidgetConfigValues>: AppIntentTimelineProvider {
|
struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & WidgetConfigValues>: AppIntentTimelineProvider {
|
||||||
typealias Intent = Configuration
|
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 {
|
func placeholder(in context: Context) -> FuelPriceEntry {
|
||||||
let unit = FuelStore.loadDistanceUnit()
|
let unit = FuelStore.loadDistanceUnit()
|
||||||
let sample = SampleFuelProvider.sampleStations
|
let sample = SampleFuelProvider.sampleStations
|
||||||
@@ -128,7 +163,7 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
|
|||||||
|
|
||||||
private func makeEntry(configuration: Configuration) async -> FuelPriceEntry {
|
private func makeEntry(configuration: Configuration) async -> FuelPriceEntry {
|
||||||
let entry = await makeEntryCore(configuration: configuration)
|
let entry = await makeEntryCore(configuration: configuration)
|
||||||
Self.writeDiagBeacon(entry: entry)
|
writeDiagBeacon(entry: entry)
|
||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,8 +172,8 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
|
|||||||
/// access log records that a widget timeline actually ran in the
|
/// access log records that a widget timeline actually ran in the
|
||||||
/// extension and what it produced. Deliberately outside the timeline
|
/// extension and what it produced. Deliberately outside the timeline
|
||||||
/// result — can never affect rendering.
|
/// result — can never affect rendering.
|
||||||
private static func writeDiagBeacon(entry: FuelPriceEntry) {
|
private func writeDiagBeacon(entry: FuelPriceEntry) {
|
||||||
let intentType = String(describing: Configuration.self)
|
let intentType = beaconTag.isEmpty ? String(describing: Configuration.self) : beaconTag
|
||||||
let first = entry.stations.first
|
let first = entry.stations.first
|
||||||
let json = """
|
let json = """
|
||||||
{"intent":"\(intentType)","source":"\(entry.locationSource)",\
|
{"intent":"\(intentType)","source":"\(entry.locationSource)",\
|
||||||
|
|||||||
Reference in New Issue
Block a user