Repository navigation
Conversation
price.get's answer and FTW Cloud's prices tile carry vatPermille, the VAT rate the box taxed totalMinor at, in permille (250 is 25 %). The rate comes from the price service's Applier after the default, so a box with no vat_percent sends 250. Always sent, so 0 means no VAT; an older box leaves it out. Permille because every ratio on the wire is an integer in permille and the price answer keeps floats off the wire. The tile's hash covers the rate. The prices golden vector changed, so the four vector files that hold that envelope are regenerated; ftw-cloud needs no change. 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. |
This was referenced Nov 1, 2026
frahlg
added a commit
to srcfl/ftw-app
that referenced
this pull request
Nov 1, 2026
* feat(ios): the price stands on zero Erik, 1 November 2026: Now's strip made 450 against 550 öre look five times dearer. It put the window's cheapest quarter at the floor and the dearest at the top. Every price bar now reads one PriceScale: zero at the foot and a round top just over the dearest (1, 1.5, 2, 2.5, 3, 4, 5, 6 or 8 times a power of ten). Now's strip draws the bars from zero, marks half the top and the top with figures in a gutter on the right, and carries the price now on as a dashed line. A price below zero hangs under the zero line. The dial's ticks, the lock-screen glyphs, the plan widget and the watch's bars take the same scale in eight steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ios): Now chooses a pass and shows its figures Fredrik picked proposal 5 for Now on 1 November 2026. Each run of the battery and each car window on the strip is a pass (PlanPass, FTWShared, tested). A tap chooses one: it stands in full colour over a faint column of its colour, the bars under it take that colour, and three figures under the strip say when it runs, what it does to the level and the price it meets. The strip opens on the battery's run under way or next. A run on spare sun says it buys nothing. With no pass, the sentence stays; under a finger, the quarter's line still takes its place. The colours are Phosphor's, not the proposal's: gold gives power, cyan stores, violet for the car. Nothing glows, since a plan is not live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ios): the plan as a clock, with Now's strip under it Erik, 1 November 2026: on the 24-hour dial 18 is not straight down as on a clock, so you lose the feel for when things happen and have to read the figures at the edge. Fredrik said yes to round 3 of the design canvas the same day. - The dial is a clock: one turn is the next twelve hours, 12 at the top and 18 straight down, with the turn's whole hours round the rim. Its ticks are as long as the battery's planned power and coloured by what it does, gold giving power as on the strip, cyan storing. The price leaves the dial. The phone, its widget and the watch draw the same face. - Under the dial, and as the lanes, the plan shows Now's strip: the passes, the price from zero and the chosen pass's figures. A tap on a pass lights its ticks on the dial. The lanes run the whole plan with the battery's level in a row of its own. - The old lanes, the story under the dial and the rows under the lanes go; the figures say what they said. CLAUDE.md's dial rules follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ios): choose which price the app shows Fredrik, 1 November 2026: he wants the price without grid fees, and to know whether it is with or without VAT. Home gets "Price shown" with two switches: with the grid fee and tax, and with VAT. Now's strip, the plan and the price page draw the chosen price (PriceShown, FTWShared, tested) and name it: "SPOT INCL. VAT", "ELPRIS INKL. MOMS". The box sends spotMinor and totalMinor, and with srcfl/ftw#1949 the VAT rate it taxed at, as vatPermille (250 is 25 %), on price.get and on FTW Cloud's price tile. The phone reads both and keeps the rate with the prices. A box that does not send it gets the price the phone has: the total keeps its VAT, the spot price goes without, and the label says so. The widgets and the watch still show the total with VAT. The demo box sends 250. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Pair: srcfl/ftw-webapp#116 (protocol doc,
Pricestype, simulated box). Merge the two together.What
price.get's answer (Price) and FTW Cloud'spricestile now carryvatPermille: the VAT rate the box taxedtotalMinorat, in permille (250 is 25 %). The iPhone app needs it to show spot with VAT.Applier, after the default. A box with novat_percenttaxes at 25 % and sends 250, not 0.prices.Service.VATPercent()reads the rate under the lockSetAppliertakes, so a config reload cannot tear it.Why permille, not a float percent
Every ratio on the wire is an integer in permille (
socPermille,targetPermille,chargeLimitPermille, the registry'sratiokind). No wire field carries a float percent, and the price answer keeps floats off the wire on purpose. Every VAT rate in our markets has at most one decimal in percent (Finland 25.5 %, Ireland 13.5 %, Switzerland 8.1 %), so permille holds each one exactly. The box rounds once, like the money.FTW Cloud
No ftw-cloud change. The service ignores keys it does not know and forwards the box's signed bytes, so the new key reaches the phone as is.
The field changes the
pricesgolden vector, so the four files that hold that envelope are regenerated:cloud-vectors,cloud-app-vectors,cloud-once-vectorsandcloud-quic-vectors. Only bytes that depend on the prices envelope moved. I ran ftw-cloud's full suite (origin/main 931e64b) against this branch'sgo/protocolthrough a localreplace: all packages pass, the vector replays included. ftw-app keeps its own copies of the vectors; they stay valid until someone copies the new ones.Registry
No change. Price message keys (
spotMinor,totalMinor) are not inregistry.yaml, and this one follows them.Known limit
The box taxes a slot when it stores it and rewrites today and tomorrow every hour. After a VAT change, older days, and today's slots for up to an hour, carry the old rate. VAT seldom changes; a rate per slot would need a schema change.
Tests
Run on this machine:
go test -count=1 ./internal/appproto/... ./internal/prices/... ./internal/cloudlink/... ./cmd/ftw/...fromgo/: passgo test -count=1 ./...fromgo/protocol/: passmake verify: passgo test ./...withreplace github.com/srcfl/ftw/go/protocol => <this branch>/go/protocol: passNew or changed tests:
TestPriceCarriesTheAppliedVATRate: 25 sends 250, 25.5 sends 255, and 0 sends 0 with the key present. It fails ifpriceFromdrops the rate.TestVATPercentIsTheRateTheTotalsAreTaxedAt: the default 25, a total taxed at that rate, and a reload's rate.TestAppPricesSendsTheServicesVATRate: the adapter sends the service's rate, default included.TestPriceColumnsRepeatLongSlotsAndLeaveGapsNull: the tile sends 255 for 25.5.Open PR #1051 also edits
go/internal/prices/prices.go, inFromConfig. This PR adds a method afterSetApplierand leaves those lines alone.🤖 Generated with Claude Code