Repository navigation
Conversation
The car view's rules, in FTWKit so they are tested:
- Where the level comes from: from the car, from the car but older than
five minutes ("last reported HH:MM"), set from this phone, FTW's
estimate, or FTW's assumption of 20 % at plug-in.
- A receipt for every command: sent, received (cmd.ack), done
(cmd.result), or why not. Session.command gains onAck for this.
- The ready time on the household clock, worked out the way Core does,
so 07:00 stays 07:00 across 25 October and a box that kept the UTC
minute shows the 06:00 it really uses. Goals go out as both
time_of_day_min and time_of_day_min_utc.
- Whether the car makes its goal, from the plan's own shortfall.
- "Is FTW in control?" from Core's control feedback, never green from
readings that are not live.
- English and Swedish words for all of it.
LoadpointsModel drops boost, solar-only and battery size from the daily
path. Charge now holds the charger's full current with no time limit and
no level gate. The demo box plugs in and out, has a car that reports its
level, a plan with a shortfall, and a charger that takes a moment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Now keeps four things: "Is FTW in control?" in one calm line, the power flow, the car as one row that opens the car view, and the price card. The headline sentence, today's totals, savings and the fuse go. The line is green only when Core's control feedback says so over a live stream; a cache is grey and dated. The car view, in FTW/Car: - How full the car is, and who says so. Plugged in and unread, FTW assumes 20 % and says so with a badge. 20/40/60/80 and ±5 send loadpoint.soc.set at once; quick taps fold into one send. - The goal, on the household clock, with the next deadline, whether the plan makes it, and when it charges. "Use 80 % by 07:00 on weekdays" when there is none; an editor for the rest. - Charge now, Pause and Back to the plan, each with a receipt: sent, received, done, or why not. - In the demo, a card to plug the car in and out. Every word is in English and Swedish. The tab bar with one tab gives way to a navigation stack. The bands above every screen stop growing past xxxLarge text, and the flow diagram past xLarge. The demo's loopback could deliver a frame before the one sent ahead of it: each frame had its own timer, and live timers with the same delay need not fire in order. An api.chunk before its api.head read as "your box didn't answer". Each timer now delivers the oldest frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing asks the box on a timer any more. The chargers are read when a screen that shows them appears, after a command, when the stream says the car started or stopped drawing, and when the stream comes back. The plan's shortfall is read when the car view opens and when the car's plan changes. "Is FTW in control?" now comes from lane 0 alone (the dispatch block and each source's state), so Now no longer polls /api/status. A cold start paints the car from the App Group cache the app writes for its widgets, dated and marked as not live, before the box answers. While nothing at all is known the screens show their shape, not a spinner. LaunchClock logs process start to first frame and first content. A tapped level shows as the chosen chip and the receipt; the number only moves once the box confirms it. Goals send time_of_day_min and today's time_of_day_min_utc, built from nothing read; tests cover a Core with srcfl/ftw#1586 and one without. The car view is a system List with system buttons, haptics on commands and outcomes, symbol effects and numeric transitions. The Now car row zooms into it. Now is split into small views that redraw only what a frame changed. Theme colours move to an asset catalog, so no UIKit is left outside the camera. CLAUDE.md gains "Feel and speed". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 12, 2026
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 (base
claude/ios-m1-foundation). This is M1 step 3: the car view, and Now cut down to what people look at every day. The price widgets come in a separate PR from the same base, and the two touch different files.What changed
Car view (
appleApp/FTW/Car/)VehicleMaxAge, orvehicle_stale), "You set 40 % at 14:02", FTW's estimate, or FTW's assumption. When a car is plugged in and unread, a badge says "FTW assumes" next to the 20 %, and the line asks for the real level.loadpoint.soc.setat once. Taps made while one is on its way fold into the next send. A tap shows at once as the chosen button and the receipt; the number moves only when the box confirms it. The box refuses a level for a car that is not plugged in, so the view shows the buttons only then.loadpoint.holdat the charger's full current withhold_s: 0and no level gate.cmd.ack), Done (cmd.result), or why not, in one sentence. A success fades after 20 s; a refusal stays until the next tap.Session.commandgainsonAckfor this.The ready time (srcfl/ftw#1582, srcfl/ftw#1586)
schedule.time_of_day_min(minutes after midnight on the box's clock) when the box sends it. Otherwise it readstime_of_day_min_utcon today's date the way Core'sclockFromUTCdoes. So 07:00 stays 07:00 across 25 October, and a box that kept the UTC minute shows the 06:00 it really uses.time_of_day_minand today'stime_of_day_min_utc. A Core with #1586 takes the first; an older one reads the second. It never sends back a read schedule with only the UTC field changed. No registry name is involved.Will the car be ready?
loadpoint_shortfall_wh(GET /api/mpc/plan), read when the car view opens and when the car's plan changes. "On track: 80 % by tomorrow 07:00." or "Will not make it: about 62 % by tomorrow 07:00, not 80 %." From an assumed level it adds "Counted from FTW's guess of 20 %." It says nothing while Charge now holds the car or the plan is being remade.Now
/api/statusfor it is gone.-FTWTab caropens the car view for screenshots.Feel and speed (Fredrik's bar, now in
CLAUDE.md).redacted), never a spinner.Equatable, and the price chart's clock ticks by the minute.Listwith system buttons; Liquid Glass comes with the navigation bar and the sheet. Haptics on every command and outcome, symbol effects, numeric transitions on every figure, one spring for all motion, and a zoom from the Now row to the car view.UIColorto an asset catalog (FTW/UI/Theme.xcassets, same values as the design tokens).Words
Package.swiftis outside this branch, so its Swedish lives in a table inFormat/EV.swift(AppWords). It can move to a strings file later. Pairing is still English.Demo box
-FTWDemoCaropens one of eight situations (assumed,unplugged,estimated,car,carOld,late,noGoal,charging).api.chunkthat arrived before itsapi.headread as "your box didn't answer". Each timer now delivers the oldest frame. A test fires the timers backwards.Cut from
LoadpointsModel: boost, solar only and battery size. The plan puts them under "More", later.Checks
swift testinappleApp/FTWKit(Xcode 27.0, Swift 6.4)xcodebuildFTW scheme,generic/platform=iOS Simulator, own derived dataLaunchClock(process start to first frame / to first content), five cold launches of the demo, Debug build, iPhone 17 simulator, iOS 26.5, on a shared laptopNew tests (
CarTests.swift): the level's origin and age, including the five-minute boundary and the box's stale flag; ±5; the receipt at every stage, including a refusal before and after the ack; the ready time across 25 October 2026 and the hours that happen twice or never; the goal from a box that sendstime_of_day_minand from one that does not; a save that sends both fields and keeps 07:00 on either Core, on both sides of 25 October; the household zone; readiness from the shortfall; "Is FTW in control?" from the stream and never green from a cache; no charger read without a reason (none in a minute of quiet, one per appearance, one after a command, one when the car starts drawing) and no shortfall read in three quiet minutes; a cold start painted from the cache; a tap that never shows as the level before the box answers; and the whole flow against the demo box.Screenshots (Swedish unless named): Now in light, dark and the largest text size; the car view in light, dark and the largest text size, including the level buttons there; a plugged-in car whose level FTW assumes; a car level last reported 40 minutes ago; a goal the night is too short for; unplugged; no goal; Charge now running; the receipt after the box took a level but before it answered, with the old number still shown. They are with the session that built this, not in the repo.
Open before merge
Signed-off-by. CONTRIBUTING asks for it, and an agent should not sign in Fredrik's name.soc_retentionkeeps.Next steps, left out on purpose
The core is here: the level with its source and age, the goal, Charge now, Pause and Back to the plan with a visible outcome, and the calm line on Now. These wait, because daily use does not need them:
connect()from the scene's first.taskinstead ofFTWApp.init, so the Keychain reads cannot come before the first frame (AppModel,FTWApp; outside this branch).Platform/NetworkWatch.swiftandPlatform/QRScanner.swift, andUIApplicationinPlatform/Passkeys.swift(outside this branch)./api/loadpoints, so the app need not read the whole plan (Core).AppWordstable to a strings file oncePackage.swiftcan give FTWKit resources. Swedish for pairing.cmd.result, and a sign-out screen (both from iPhone M1 foundation: Swift app cut to Pair, Now and widgets #8).appleApp/scripts/demo-screenshots.sh(-FTWTab car,-FTWDemoCarare ready).🤖 Generated with Claude Code