Repository navigation
iPhone and watch: the car first, from the box's car list (car step 4) - #85
Conversation
On a box that lists der.car the app reads GET /api/cars after each hello and draws the car before the charger: its name and level with source and age, its limit, goal and plan, and the charger as one line while the car is on it. A car with the cable out still shows its last level. Two cars and no answer ask "Which car?" in the car view, on the watch and on the notification, one tap through session.car.set. Settings list the cars: rename, battery size, level sources, add and remove. A box without der.car keeps today's charger view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lugged car The choice's receipt stays under the charger line once the view turns into the named car. The list is read once after hello, not again on the first read of the chargers. "Is the car ready?" gives an unplugged car's last level. Level sources leave the chargers out. CarsTour taps Zoe on the demo and photographs each case. 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: 6c98b2aa08
ℹ️ 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".
| actions: set.cars.map { car in | ||
| UNNotificationAction(identifier: PushActions.carAction(car.id), title: car.name, options: [.authenticationRequired], icon: UNNotificationActionIcon(systemImageName: "car.side")) | ||
| } + [UNNotificationAction(identifier: PushActions.carAction(nil), title: PushActions.noneTitle(language), options: [.authenticationRequired])], |
There was a problem hiding this comment.
Limit notification categories to four actions
When a car.which event offers four cars, this constructs five custom actions because “None of these” is appended after the four car actions. iOS notification categories support at most four custom actions, so the final action can be omitted and the user cannot identify the vehicle as a visitor from that notification. Reserve one of the four slots for “None of these,” or decline to create a category when there are too many car choices.
Useful? React with 👍 / 👎.
- The watch keeps the car's own source (vehicle, manual, assumed) in CarEntry.Owned, so an estimate from the car never reads "set by hand". - The watch keeps the chargers until the list is read, and a plugged-in charger whose car the box cannot name, so a visitor still gets Charge now. - A visitor never shows another car's level on Now or in the car's node, and the node lists its charger. - "Which car?" buttons survive a launch: the categories start from the App Group row. - A car FTW Cloud's table lacks asks for hello again, without a sub, so the stream runs on; the table keeps the box's order. - Settings send only what changed, with a battery from 1 to 300 kWh in 0.1 kWh steps. - A stale or cached list says how old it is; a goal to the car's limit stays one; a retried add keeps its id; no Int() on a NaN from the wire. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Why
Fredrik, 17 October 2026: "jag ser ju inte min bil någonstans". His box (Core v0.147) knows the car.
GET /api/carslists "Car 1", a Tesla at 72 % fromtesla-ha, an hour old, with a goal of 80 % by 07:00 and 76 kWh. The app drew its car row from the charger, so with the cable out it showed nothing. This is step 4 of the car design (FTW-stack/design/vehicles/car-entity-2026-10-15.md, sections 3, 4, 8 and 9; srcfl/ftwdocs/cars.md).What changes
A box that lists
der.carkeeps cars apart from chargers. A box without it keeps today's charger view, unchanged.session.car.set. The receipt (SENT ▸ ACCEPTED ▸ CONFIRMED) stays under the charger line once the view turns into the named car. The car node's row says "Which car?" while a charger asks.car.whichgets the same buttons. The app registers one category for each set of the owner's cars, so the buttons carry the names from the phone's own table. The extension picks the set the event offers, from the event's opaque car ids and the table the box gave over Noise (hello_ok.cloud.cars). A button acts only for a verifiedcar.whichthat offered that car, on the plug-in the event named, behind unlock.car.update), add and remove. Only the fields that changed go to the box. The phone mints a new car's id and keeps it when it tries again after an answer it did not get.car.goal.set,car.soc.set), for a car on a charger or away. A goal "to the car's own limit" stays one when it is saved again.WatchCommand.Op.chooseCar, run on the phone throughCarsModel).CarEntry.owned,CarEntry.asking, optional fields, so older records still read). The watch and Siri's "Is the car ready?" read it. The Live Activity shows Charge now's state and no level, so nothing there read the charger's level before; it is unchanged.How it reads without polling
The list is read after each hello, when the home comes back on screen and the last read is over a minute old, and after each car command. It is also read when a read of the chargers or a pushed plan shows a car that moved: plugged in, named, asked about, or its level changed. FTW Cloud's car class moves each car's level as the box sends it, mapped through
hello_ok.cloud.cars. Acmd.resultcarries the car or the charger's car, so the screen changes at once before the next read. No timer.FTW Cloud names a car by an opaque id from
hello_ok.cloud.cars. When the list has a car that table lacks (one added here or on the box page), the app sends hello again without asub. The box answers withhello_okalone, and the stream runs on. The notification's buttons start from the App Group row at launch and keep the box's order.Names
Every wire name comes from the registry:
CarField,ChargerCarField,CarSocSourceandCarIdentifiedByare held tocar_fields,charger_car_fields,car_soc_sourcesandcar_identified_byby a test. HTTP names are their snake_case, as Core spells them.Contract.scopeCarsWritenamesftw.cars.write.Demo
The demo box is now a Core with cars, with one car on the charger (
-FTWDemoCars one).-FTWDemoCars away | two | asking | oldopens each case.oldis a box from before cars. Unit tests build the box without cars unless they ask, so the existing tests run against an old box as before.Tests
swift testinappleApp/FTWKit: 266 FTWShared tests and 517 FTWKit tests pass, among them the newCarWireTests,CarsFlowTestsandCarShareTests.der.car, no/api/carsread, the charger view as before.car.goal.setandcar.soc.setfor a car on the charger and one away.sub, a goal to the car's limit, and a retried add.xcodebuildbuilds the app, its widgets and the watch app for the simulator.FTWUITests/CarsTour:testOneTapNamesTheCartaps Zoe on the demo and waits for the box's answer.testShots(with `TEST_RUNNER_FTW_| One car, cable out (Fredrik's case) | The car node, cable out | One car, plugged in ||---|---|---|
|
Swedish, two cars and no answer:

=1`) took the screenshots below.Screenshots (iPhone 17 Pro simulator, demo box)
Swedish, two cars and no answer:
Not in this PR
new_id, and does not copy the owner's list between boxes. Both matter only with more than one box.GET /api/carsandGET /api/loadpointsread-only and used the car list as a test fixture.🤖 Generated with Claude Code