Repository navigation
iPhone: the plan dial as one turn, shared with the watch, and its widget (WP11) - #53
Conversation
…get (WP11) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c80a7cd381
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let sun = self.plan.forecast.first.map { "\(self.plan.forecast.count)@\($0.startMs)" } ?? "-" | ||
| return "\(plan?.rev ?? 0)|\(plan?.slots.first?.startMs ?? 0)|\(plan?.slots.count ?? 0)|\(plan?.stale ?? false)|\(w.joined(separator: ";"))|\(p)|\(sun)" |
There was a problem hiding this comment.
Persist forecast changes when they arrive
When the plan and forecast requests start together, PlanModel.load() can invoke plan.received and check() before loadForecast() assigns forecastStamp; the forecast assignment has no callback to PlanShare, so adding it to this key does not cause another shared-record write. A fresh installation can therefore persist daylight: nil, leaving the new widget and watch dial without the solar arc until an unrelated plan or site event triggers check(); invoke sharing when the forecast is accepted and include the sunlight values in the change key.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| let unit = PriceUnits.unit(currency) | ||
| func figure(_ minor: Double) -> String { Words.decimal(PriceStrip.text(abs(minor), currency)) } |
There was a problem hiding this comment.
Preserve the sign of negative dial prices
For a negative tariff, this helper converts the value to its magnitude before it is used by both prices and cheapestText, so −5 is shown as 5 ÖRE/KWH and the cheapest annotation also describes a positive price. price(_:) and figure(_:) retain the sign, meaning the phone dial disagrees with the watch and widget precisely when the customer is being paid to consume; retain the sign here unless the magnitude is accompanied by an explicit below-zero label.
Useful? React with 👍 / 👎.
BlockBars keeps W1.1's highlight and takes PlanDial.levels from #53. DialStrip, the rectangle's strip, now reads PlanDial. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #53 the watch's plan page draws the shared face at the app size, and on a dial about 100 pt across the goal ("80 %") landed on the hand's time and price in the middle. The goal now shows only on a dial at least 200 pt across, which the phone's always is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…raphic complications (#49) * Apple Watch W1.1: Now alive on one screen, pages without scrolling, and graphic complications Fredrik's notes on W1 (14 Oct 2026): the watch widgets let text run outside and should hold little text; the app should have the phone's live view and pages instead of scrolling. - Now: the status line, one node's figure large, the phone's live trace (LiveSeries drawn by LiveTraceSurface in TraceInk), glowing only while live, and the other nodes small. A tap on a small one traces it. The mode sits behind a toolbar button. - Every page fits one screen at the standard sizes; at text sizes too large for the watch it scrolls rather than cut a reading off. The car's commands are one row of glyph buttons. - Complications: no sentences. Rectangular is a glyph, one time, the price now and the plan unrolled as a strip; corner is the price with the day's range as a gauge; inline is the price; the cheap card is a glyph, its hours, one figure and the hours ahead with the window standing out. Every text has one line and may shrink. - LiveTraceSurface takes `paused`, so a trace that is not live holds still. WatchNow.Reading gives its signed watts for the trace, and each node the phone's scale floor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Watch W1.1: the car page fits one screen, the dial's line says what comes next, Now waits for live in the tour Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Watch W1.1: the car page fits one screen, and the strip is DialStrip The receipt now takes the place of what the car does, its goal and next window, until the page is left; a settled receipt then goes. The page no longer keeps a room of its own for the receipt, so it fits 46 mm without scrolling and the swipe turns the page. The commands are one row of glyph buttons, 30 pt high. The watch's strip view is now DialStrip, so it does not shadow the PlanStrip value that the phone's plan lane adds to FTWShared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Watch W1.1: Now and the car fit 41 mm Now's trace takes the height that is left, at least 24 pt, so the page fits 41 mm without scrolling and the swipe turns the page. On 46 mm the trace is as tall as before. The car's meter goes under its level when both do not fit on one line, as with "20 % antaget", rather than cutting the word. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Watch W1.1: the car's goal stays off a watch-sized dial Since #53 the watch's plan page draws the shared face at the app size, and on a dial about 100 pt across the goal ("80 %") landed on the hand's time and price in the middle. The goal now shows only on a dial at least 200 pt across, which the phone's always is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Work package WP11 of the iPhone polish release ("Phosphor"). The plan view opens on the plan dial: one turn for the next 24 hours, drawn by the same face as the Apple Watch dial from #43. The dial gets its own widget, for StandBy and the lock screen. This replaces #27, which drew the dial before the new palette and the chart engine existed.
The same recording as MP4.
Fredrik's decisions, as built
GET /api/mpc/plan, slots at 10 W or more). No forecast, no arc: never astronomy.LAGRAR · 3,0 KW · BILEN).FTWPlanStyle).One face for phone, widget and watch
WatchDialbecamePlanDial(FTWShared/Charts/PlanDial.swift), with the sun, the seam, the rule for a finger and the words a finger reads, made once per plan and minute.DialFacemoved fromFTWWatchSharedtoFTWCharts/PlanDialFace.swiftand split in two:PlanDialFace(every still mark,Equatable) andPlanDialHand(the hand and the lock-on).PlanDialViewputs them together for the watch app, the watch's complication and the phone's widget. The watch gains the sun's arc and the seam's gap; its Crown, words and complications are unchanged.PlanEntrycarriesdaylight, so the sun travels to the widget and the watch with the plan. An older record without it still reads.The finger
A drag round the ring (a ring-shaped hit area, so a drag from the middle scrolls the page) costs one lookup per event (
PlanDial.index(x:y:from:)) and writes nothing a view reads until the quarter changes. Then the hand jumps to it, brackets close on its tick, the middle shows that quarter,.selectionticks with the 40 ms floor, and a light impact marks now, the cheapest two hours' edges and the car's goal. The finger cannot wind past the seam: from the end it stays on the last quarter. On lift the hand springs back to now along the day, withTheme.spring. VoiceOver steps it an hour at a time.From #27 this keeps the scrub path (one lookup per event, haptics beside the marks, signposts "dial drag" and "dial quarter"), the widget kind
energy.ftw.plan-dial,PlanEntry.daylight, the style switch and its UI test. It drops the two turns, the battery ring, the glass disc and the legend.Widget
energy.ftw.plan-dial, systemSmall (also StandBy) and accessoryCircular. Small: the dial, the price now in the middle andPLANERAD 21:36under it, always, on a dark ground. Circle: the dial and the price now. Stores and the car are.widgetAccentable()for tinted and clear screens. The timeline steps a quarter at a time for 12 hours; an hour after the box made the plan the dial goes grey.Departures from the design document
CHEAPESTline,PLANERAD 16:51 · PLANERAS OM VAR 15:E MIN, then the event rows as plain rows on the ground. The three new sentences and dropping the rows wait for Fredrik's answer to the plan design pass.Evidence
swift test: FTWShared 191 and FTWKit 341 tests green. NewPlanDialTests(the seam, a finger finds its quarter, the seam stops a finger, the sun from the forecast and none without it, daylight from the forecast's slots, the middle's words, the quarter running now reads a plan the box made inside it, the planned line, an old record still reads, Swedish for every word).WatchTestspass unchanged against the renamed type.PlanDialTestson the iPhone 17 Pro Max, iOS 26.5: 3 of 3 (VoiceOver reads the dial at now; a finger round the ring keeps the page still; the lanes are one tap away and the choice is kept across launches).NowFitsTestson the iPhone SE (3rd generation): 10 of 11 in the full run, under a load average near 500 from other simulators on the laptop.testNowHoldsItsShapeWhileReadingsArriveonce saw the sun's node 2.5 pt lower 6 s on; it passed in two runs on its own. This PR does not change Now.-FTWDialScrub YES -FTWFrameLog YES), a finger once round the ring and back in 12 s: 191 quarter changes (signpost "dial quarter"), 60 frames a second and 0 ms of late frames in every second of the turn.Not done here: a recording from Fredrik's iPhone against the home box, StandBy and haptics on a device, and an Animation Hitches run. The simulator refuses Animation Hitches and the SwiftUI instrument.
Pictures
iPhone 17 Pro Max: dark, light and the largest text size (accessibility XXXL):
iPhone SE (3rd generation): dark, light and the largest text size:
The lanes, one tap away:
The widget on the home screen, dark and light. It keeps its dark ground in both:
The lock screen's circle, placed by
LockScreenTour(TEST_RUNNER_FTW_LOCK=1 TEST_RUNNER_FTW_LOCK_PAGE=2), and enlarged:StandBy by day and at night, rendered on the Mac from the widget's own face:
Closes #27.
🤖 Generated with Claude Code