Repository navigation
iPhone: history and ahead on the engine, in the live trace's material, the chart small and growing (WP7) - #44
Conversation
…(WP7) Today and 48 h draw from HistoryPicture: a fixed day with 30-minute slots, 48 h with 60-minute slots, each slot the mean of the box's five-minute means through Steffen, with its band. One stable scale for the chosen series. Holes on their way are hatched with a dashed estimate; holes with no record are a dotted baseline. AS OF marks old data. A tile that lands moves each slot to its new mean. Ahead draws from AheadPicture: plan quarters as ghost bars, forecasts as a dashed line, the price strip under the same slots. Fredrik's note 16: the chart is compact and a tap zooms it to the whole screen. Chips add or take away series, kept per viewer per node; the last one stays and a hidden series changes nothing the others say. The scale re-fits as one spring. Size caps for the largest text move to where each view is placed, so @ScaledMetric sees them; this fixes the price and node heroes too. NodeCharts, ChartMotion and ChartScrub are gone. 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: eed9e01ffa
ℹ️ 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".
| guard !bars.isEmpty || node == .car else { return nil } | ||
| let prices = (window?.slots ?? []).map { ChartPrice(startMs: $0.startMs, endMs: $0.startMs + $0.durationMs, minor: $0.totalMinor) } |
There was a problem hiding this comment.
Preserve prices embedded in plan and forecast slots
When the box supplies PlanSlot.priceMinor or Forecast.priceMinor but does not expose the separate spot-price capability—or that request has not completed—window is nil and this now passes no prices to AheadPicture. The previous implementation used the prices embedded in those slots, so affected battery, grid, solar, and home Ahead charts lose both the price strip and price readouts despite having that data; use the plan/forecast prices as a fallback.
Useful? React with 👍 / 👎.
| for n in series { | ||
| let samples = Self.samples(n, home: home, fromMs: window.fromMs) | ||
| if !samples.isEmpty || n == node { inputs.append((n.rawValue, samples)) } |
There was a problem hiding this comment.
Keep the unsupported-history state visible
When a connected box lacks history.5m, HistoryModel.available is false and no frame or problem is produced, but this unconditional inclusion of the node with an empty sample array still creates a non-nil HistoryPicture. Consequently the chart branch always renders an all-missing graph and the previous “Your box keeps no history.” state disappears entirely; gate picture construction on history availability and show the unsupported message instead.
Useful? React with 👍 / 👎.
| let now = site.minuteMs + 60_000 | ||
| let window = period == .today ? HistoryPicture.day(now, clock: clock) : HistoryPicture.twoDays(now) |
There was a problem hiding this comment.
Derive Today from the current minute, not its end
During every day's 23:59 minute, adding 60 seconds makes now exactly midnight of the following day, so HistoryPicture.day switches the Today window early and the current day's chart becomes empty for up to a minute. Keep the day selection based on site.minuteMs (while using a separate end-of-minute value if needed for the data limit).
Useful? React with 👍 / 👎.
| let series = AXDataSeriesDescriptor(name: node.title, isContinuous: true, dataPoints: buckets.compactMap { b in | ||
| b.mean.map { AXDataPoint(x: b.startMs, y: $0, additionalValues: [], label: Readouts.word(node, $0)) } |
There was a problem hiding this comment.
Describe the selected primary series to VoiceOver
If the user deselects the viewed node's own series, SeriesPick allows another node to become picture.layers.first, but this descriptor still names and interprets those buckets using the original node. For example, solar values on a battery page are announced as Battery with charging/giving labels; derive the series name and Readouts.word node from the first layer's key.
Useful? React with 👍 / 👎.
| .opacity(timebase == .today || timebase == .twoDays ? 1 : 0) | ||
| .allowsHitTesting(timebase == .today || timebase == .twoDays) |
There was a problem hiding this comment.
Hide inactive full-screen series controls from accessibility
In the enlarged Live and Ahead views these chip buttons are only made transparent and non-interactive; unlike the compact view, this copy does not apply accessibilityHidden. VoiceOver therefore still navigates and announces invisible series controls on timebases where they do not apply.
Useful? React with 👍 / 👎.
Fredrik on 0.3.5: the live view feels right, so every other chart should feel and look the same. Live, Today and 48 h, small and enlarged, now draw through one place, TraceInk: the same band, body, 2.5 pt line, and glow only while live (on Today, only the slot still filling). Live uses ChartAxes and NodeChartLook like the rest. SlidingSurface gives history and Ahead Live's motion: a change for the same slots slides over 0.25 s, a new scale or a series coming or going springs over 0.35 s, with the same ease. AheadPicture.blended moves a new plan or forecast slot by slot. Under a finger the hero follows on Today and 48 h, as on Live, and the line under the chart says the energy so far or between, from the same functions. The dot takes its side's colour, the span tag gives the average, and nothing dims behind the finger. History's landmarks are the high, the low, now and each end of a run of data, as on Live. The debug finger reads the newest track; HistoryTour starts on Live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…complications (W1) (#43) * Apple Watch: Now, the plan dial on the Crown, the car, the price and complications (W1) A watch app and its complications, reading and commanding through the iPhone. watchOS gives an ordinary app no WebSocket (TN3135), so the phone hands the watch the records its widgets read after each cache write, sends Now's frames while the watch looks, and runs each command over its own session. FTW Cloud never controls. - FTWWatch: Now (lit only while a live frame is fresh, ink with its time otherwise), the plan dial with the Crown stepping by quarter hour, the car with Charge now, Pause and Back to the plan, the price, and the mode. Each command shows SENT ▸ ACCEPTED ▸ CONFIRMED from the box's answers. - FTWWatchWidgets: the dial as accessoryCircular, plus rectangular, corner and inline, and a RelevanceConfiguration card for the cheapest hours. - Built to stand alone: its own cache, its own key, its own cmdId, and two seams (ReadPath, CommandPath). PhonePath is the only path today. - Phone side: WatchBridge in the app, WatchShare in FTWKit, the wire in FTWShared/Watch. Session.command takes the watch's cmdId. - Each colour set gains a watch variant equal to its dark one, since watchOS compiles no dark appearance; ThemeTests holds them equal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * iPhone: history and ahead on the engine, in the live trace's material, the chart small and growing (WP7) (#44) * iPhone: history and ahead on the engine, the chart small and growing (WP7) Today and 48 h draw from HistoryPicture: a fixed day with 30-minute slots, 48 h with 60-minute slots, each slot the mean of the box's five-minute means through Steffen, with its band. One stable scale for the chosen series. Holes on their way are hatched with a dashed estimate; holes with no record are a dotted baseline. AS OF marks old data. A tile that lands moves each slot to its new mean. Ahead draws from AheadPicture: plan quarters as ghost bars, forecasts as a dashed line, the price strip under the same slots. Fredrik's note 16: the chart is compact and a tap zooms it to the whole screen. Chips add or take away series, kept per viewer per node; the last one stays and a hidden series changes nothing the others say. The scale re-fits as one spring. Size caps for the largest text move to where each view is placed, so @ScaledMetric sees them; this fixes the price and node heroes too. NodeCharts, ChartMotion and ChartScrub are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * iPhone: every power trace in the live trace's material (WP7) Fredrik on 0.3.5: the live view feels right, so every other chart should feel and look the same. Live, Today and 48 h, small and enlarged, now draw through one place, TraceInk: the same band, body, 2.5 pt line, and glow only while live (on Today, only the slot still filling). Live uses ChartAxes and NodeChartLook like the rest. SlidingSurface gives history and Ahead Live's motion: a change for the same slots slides over 0.25 s, a new scale or a series coming or going springs over 0.35 s, with the same ease. AheadPicture.blended moves a new plan or forecast slot by slot. Under a finger the hero follows on Today and 48 h, as on Live, and the line under the chart says the energy so far or between, from the same functions. The dot takes its side's colour, the span tag gives the average, and nothing dims behind the finger. History's landmarks are the high, the low, now and each end of a run of data, as on Live. The debug finger reads the newest track; HistoryTour starts on Live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
WP7 (#44) replaced the node view's Swift Charts with its own surfaces and deleted ChartMotion.swift, so this branch's change to MovingPoints goes with it. WP7's SlidingSurface already starts settled when it appears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WP7 of the iPhone polish release (Phosphor): smooth history and ahead on the engine, in the live trace's material, with Fredrik's note 16. Swift Charts leaves the node view.
Fredrik on 0.3.5, as acceptance
He liked how the live trace feels and wants every other chart to feel and look the same. Today and 48 h, small and enlarged, now share all of this with Live; Ahead shares the finger, the haptics, the slide and the type:
TraceInk: a 2.5 pt line in each direction's colour, a body from nothing at zero to 0.30 (dark) or 0.18 (light) at the scale's edge, a band at 0.14 or 0.10. The live trace draws through it too, and both take their colours fromNodeChartLook.LockOn: the rule, the dot in the colour of its side of zero inside its brackets, and the inverse tag (08:30–09:00 82 W, and07:30–14:30 avg 522 Wfor two fingers). Nothing dims behind the finger, as on Live.82 W,08:30–09:00 · giving power), and the line under the chart says↑ 3.5 kWh ↓ 3.5 kWh so far, each from the same functions as on Live. Two fingers give their mean in the hero and the energy between under the chart.SlidingSurface).ChartAxesfor Live and history: zero as one device pixel, mono magnitudes in the gutter, the direction words.What still differs is listed at the end, to queue.
Fredrik's note 16, as acceptance
"The history charts could be a bit smaller, and then you can enlarge them. And maybe customise them, adding and removing sources by tapping, so it feels more dynamic."
SOLAR · BATTERY · USAGE · GRID, andCARon the car's own view) over Today and 48 h. A tap adds or takes away a series; the last one stays. The scale re-fits by the stable rules and moves as one spring, a new series fades in and an old one out. The choice is kept per viewer, per node, in the phone's defaults.What changes
Today and 48 h (research §4.2 and §4.3),
HistoryPictureandHistoryDrawing:FETCHING…and a dashed straight estimate across it; a hole the box has no record for is a dotted baseline. Slots after now are neither.AS OF 14:05where the newest data ends, when it is more than a slot old.↑ 3.5 kWh ↓ 3.5 kWh so far · Solar 2,1 kW.NOWunder the now rule.Ahead (design §7.6),
AheadPictureandAheadDrawing:AheadPicture.blended, test).Also:
ChartSeriesgains holes on their way and on no record, the dashed estimate, the slot highs and lows, a given scale for several series, andblended(from:progress:)for the slide between tiles.SeriesPickin FTWKit holds the chips' rule, with tests.NodeCharts.swift,ChartMotion.swift,ChartScrub.swift, and the readouts only they used.HistoryPlotstays inHistoryModel.swiftfor now, because WP5 is rewriting that file. Swift Charts stays inPlanCardand the widgets.HistoryTour(skipped unless asked), drives Live, Today, two series, enlarged, 48 h and back.NowFitsTeststaps Live before it looks for the live chart: the node view now opens on the timebase this phone chose last.Evidence
Tests.
swift test: FTWShared 160, FTWKit 297, all green. NowFitsTests 8/8 on an SE (3rd gen), iOS 26.5, on the last commit.HistoryTourpassed on both phones in dark, light and the largest text, six runs.With the Watch PR. #43 builds the watch app and its complications from
FTWCharts, where this PR addsHistoryCharts.swiftandTraceInk.swift. The two merge without conflict, and the merged tree buildsFTWWatchfor the watchOS simulator with both files in both targets.New tests hold: a hole on its way is dashed and one with no record is not; slots after now are neither; series share one scale and a hidden one changes nothing the others say; each series keeps its band; Today is a fixed day, 25 hours when the clocks go back;
AS OFappears only when data is a slot old; 48 h names each midnight; a tile that lands moves each slot between its old and new mean, and another window or scale does not morph; the plan is ghost slots and a forecast an estimated line; the price strip's thirds and eight steps; a chip never takes the last series away.Calm on the home box's own days. Five-minute means of the Sungrow's battery, panels and grid meter on 12 and 13 October 2026 (from
/api/series, read only; fixturesungrow-days.json, 32 KB), Today drawn as here (30-minute means) against 0.3.2 (every five-minute mean through a monotone curve), on 350 × 280 pt:Every day holds the research's thresholds: at most 12 turns per 100 pt and under half of 0.3.2's; a test runs them in CI. The heat pump reports no power to the box, so there is no heat-pump trace of its own; its load is inside the grid meter.
Live and Today side by side, from the demo after five minutes of live trace: Live, Today, a finger on Live, a finger on Today, two fingers on Today. The figure, its word, the tag and the line under the chart change the same way on both.
Pro Max, dark:
SE, light:
Also Pro Max, light and SE, dark.
Screenshots from the demo on an iPhone SE (3rd gen) and a 17 Pro Max, iOS 26.5, taken by
HistoryTour. Each strip runs Live eight seconds after launch, Today small, small with solar added, enlarged with grid added, enlarged on 48 h.Pro Max, dark:
Pro Max, light:
Pro Max, largest text:
SE, dark:
SE, light:
SE, largest text:
Ahead under a finger, the plan slot lit as its whole bar:
The battery's plan and the sun's forecast at rest, both phones, dark and light:
At the largest text the hero numbers, the chart's labels, the chips and the footer now stop at a set size, so the chart keeps its height. The caps sat inside the views before, where
@ScaledMetricnever saw them; they now sit where each view is placed. This also fixes the price hero from WP3 and the node hero from WP6.Recording.
HistoryTouron the 17 Pro Max in dark, 54 s: Live and a finger on it, Today and a finger on it, a tap on SOLAR, a tap on GRID, a finger, the zoom into the whole screen, a finger, 48 h, back to one series, and shut.What still differs, to queue
In this PR, on purpose:
NOW.Elsewhere, for later PRs:
LockOn, haptics and type, but a price is bars, not a trace, and tomorrow's prices draw at once, with no slide or spring.PlanCard,PlanLanes, WP10): still Swift Charts. No finger, no haptics, the system's type on the axes.GridDayChart, the plan widget, WP14): still Swift Charts, with the system's curve and type. A widget takes no finger and does not move, so line, body, band and type are what can match.Departures from the design document
🤖 Generated with Claude Code