Repository navigation
Conversation
… end Four families read the App Group cache and nothing else: small (also the StandBy widget), medium with a twelve-hour curve, and the lock screen's rectangle and inline line. Prices show on the lock screen. - PriceGlance (FTWShared) builds the timeline: one entry now and one at every price boundary the box sent, at the box's resolution. Where the prices stop, the widget turns grey and says "No prices after Tue 24:00 · open FTW". It never stretches or repeats old prices. - The total says what it holds. The wire carries spot and total but not the settings between them, so a line through the two tells a grid fee from VAT. - Freshness stays two fields: every entry is carrier .cache with the srcState the app saw when it fetched. - SiteModel logs each write with os.Logger and gains refreshSharedPrices. The app asks for a BGAppRefreshTask just after 13:05, then every half hour until the box has tomorrow. No silent push, no server. - Swedish and English live in FTWShared; a test checks that every widget sentence has Swedish. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bar mark with an x interval and a single y lies on its side, so the curve showed short dashes at each price. Rectangles from zero to the price fix that. Hour labels fall every third hour, clear of the right edge, on the 24-hour clock the rest of the widget uses. The average line takes the muted tone instead of the system tint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the lock screen the inline line shares its width with the date, so
it now shows the price and the start of the cheapest stretch ("273 öre
· billigast 02:45"), and "Inga priser · öppna FTW" when there is none.
The rectangle's lower lines shrink a little before they would be cut.
Colours resolve through a SwiftUI ShapeStyle instead of UIColor, and the
background task reads the scene phase instead of UIApplication.
The timeline sorts the slots and reads what the total holds once, not
once per entry: 121 entries took 37-40 ms in a Debug build in the
simulator, now 14-17 ms, file read included. An OSSignposter interval
wraps it for Instruments, and the log line carries the time.
The price figure changes with a numeric content transition.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e rendering mode - The App Group write runs off the main actor (a @Concurrent function), in the order it was asked for. A sign-out asked for meanwhile wins: writes and clears share a lock and a per-directory clear count, so a late write never puts the home back. - The same prices again are neither written nor reloaded, so the widget timeline is rebuilt only for new prices. The background task waits for the write to land before iOS can suspend it. - The curve follows widgetRenderingMode: tinted, clear and StandBy's one-colour mode keep the current and cheap slots strong and fade the rest. The cheapest line and the symbol take the accent. - A bolt symbol heads every family, as in Apple's own widgets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
Included in #14, which lands the whole iPhone M1 app as one PR. The branch stays. |
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.
Stacked on #8 (
claude/ios-m1-foundation). This is step 2 of the iPhone M1: the price widgets. The car view and Now run in a parallel branch with separate files.What changed
Four families, all from one
PriceWidget(energy.ftw.prices):systemSmall: price now, what it includes, cheapest next, when it was fetched. This is also the StandBy widget.systemMedium: the same, plus the next twelve hours as bars at the box's resolution, green below the average ahead and red above, the current slot solid.accessoryRectangular: price, cheapest next, what it includes.accessoryInline: "273 öre · billigast 02:45". It shares its width with the date, so it carries the start of the cheapest stretch only, and "Inga priser · öppna FTW" when there is no price.Prices show on the lock screen as they are; they are not secret.
Timeline (
FTWShared/PriceGlance.swift). One entry now and one at every price boundary the box sent. Quarter-hour prices give an entry every quarter hour, and a box that mixes resolutions is followed slot by slot. Where the prices stop, the last entry turns the widget grey with "Inga priser efter sön 24:00 · öppna FTW". It never stretches the last price or starts a day over. A hole in the middle says "Inget pris förrän 12:00 · öppna FTW" and picks the prices up after it. An end at midnight reads 24:00 of the day it ends, and a time on another day names that day, since a widget can sit unread for days. The policy is.never: the app reloads every timeline after it writes.Cheapest next. The cheapest two hours in a row ahead, as on the app's price card: "Billigast 02–04", "Billigast 02:15–04:15" at quarter hours, "Billigast nu till 04" inside the block. "i morgon" is added only when the clock alone would read as today (at 14:00, "15–17" looks like this afternoon; at 20:00, "02–04" can only be tonight).
What the price includes. The wire carries
spotMinorandtotalMinorbut not the settings between them. Core charges(spot + grid fee) × (1 + VAT)(prices.goApplier), so a straight line through the slots' two figures tells a grid fee from VAT: "inkl. nätavgift och moms", "inkl. moms", "inkl. nätavgift", "endast spotpris", or "inkl. påslag" when the market barely moved and the two cannot be told apart. Tests run Core's formula with öre and with cents.Freshness. Every entry carries
carrier(always.cachewith a record,.nonewithout) and thesrcStatethe app saw when it fetched. Nothing in a widget says live; each shows "Hämtat 17:44".Keeping the cache fresh.
SiteModelwrites after everyprice.getand callsWidgetCenter.reloadAllTimelines()(from iPhone M1 foundation: Swift app cut to Pair, Now and widgets #8). It now logs each write withos.Logger(energy.ftw.app, categorywidgets).SiteModel.refreshSharedPrices()waits up to 20 s for the stream, asks for today and tomorrow as the price card does, and writes them.BGAppRefreshTask(energy.ftw.app.prices) when it goes to the background: just after 13:05, then every half hour until 20:00 while the box lacks tomorrow, since Core asks the market once an hour. iOS decides when or whether it runs. No silent push, no server.FTW-Info.plistgainsBGTaskSchedulerPermittedIdentifiersandUIBackgroundModes: fetch.Words. All widget text comes from FTWShared, in English and Swedish. A test checks that every widget sentence has Swedish with the same placeholders. Weekday names follow the language the words resolve to.
Native and fast.
.never.price.getin the app and theBGAppRefreshTaskafter 13:05.@concurrentfunction), in the order it was asked for. Writes and sign-out share a lock and a per-directory clear count, so a write still on its way after a sign-out is dropped instead of putting the home back. A test covers that race.widgetRenderingMode: in full colour the curve is green and red. Tinted, clear (Liquid Glass) and StandBy at night draw in one colour; there the current and cheap slots stay strong and the rest fade. The price, the cheapest line and the symbol are.widgetAccentable().bolt.fillsymbol heads every family, including the lock screen's lines, as in Apple's own widgets. System fonts throughout..contentTransition(.numericText())between entries.ShapeStyle, and the background task reads the scene phase.invalidatableContent()is not used: it marks content an interaction will change, and these widgets have no buttons or toggles.OSSignposterinterval wraps the timeline build, and its log line carries the time.Files
Only the files this branch owns:
appleApp/FTWWidgets/**,FTWShared/**,Tests/FTWSharedTests/**,State/SiteModel.swift,FTW/FTWApp.swift(the background task),FTW-Info.plist(background keys). One new test file,Tests/FTWKitTests/WidgetPricesTests.swift, covers the background refresh against the simulated box; it touches no existing file.CacheReadgains a public init.Checks
swift testinappleApp/FTWKit(Xcode 27)xcodebuildFTW scheme,generic/platform=iOS SimulatorThe simulator got its prices from a fixture written to the App Group container in the shape
sharePriceswrites, since the demo writes nothing and there is no box in the simulator. The home-screen widgets were placed through SpringBoard's layout file, the lock-screen ones by hand in the simulator.Open
Signed-off-by. CONTRIBUTING asks for it, and an agent should not sign in Fredrik's name.systemSmallis the StandBy family, and it uses no colour that depends on the background it loses there. Check on a device.BGTaskScheduler.submitfails in the simulator, and the refresh needs a paired box. The fetch and write are tested against the simulated box. On a device: pair, background the app after 13:05, and checklog stream --predicate 'subsystem == "energy.ftw.app"'.connectHelpis set until the next foreground connect clears it.PriceChartuses the sameBarMark(xStart:xEnd:y:), which draws bars on their side, and it says "incl. fees and VAT" whatever the box includes.PriceGlance.includedandPriceGlance.textcan replace that line.🤖 Generated with Claude Code