Everything that changed in Garage Buddy, newest first.
1.20.1 2026-09-22
Fixed
The scan no longer freezes the screen while it prepares the photo. Rendering and encoding a 12-megapixel camera frame was happening on the main thread, so for 80ms or more nothing could redraw — including the progress spinner that is meant to show the scan is working (#80). Measured stall: 80ms before, under 25ms after, and that is on a fast Mac; a phone was worse. The scan takes the same time; it stops looking stuck while it happens.
1.20.0 2026-09-22
Fixed
Odometer scanning is dramatically faster. Every scan was uploading a photo of every single car in your garage so the AI could match the dashboard by sight. With twelve cars that is 10.26 MB per scan, and a phone's upload speed is a fraction of its download speed however strong the signal looks — which is where the reported ~45 seconds went (#78). Those photos are gone. The app identifies the car from each one's last odometer reading instead, which it was already sending as text and which is right essentially always. Measured on the same photo and a twelve-car garage: 10.26 MB → 0.78 MB, input tokens 59,749 → 4,367, and about 3 seconds of upload on a slow connection instead of 41. The reading itself is unchanged — benchmarking found the photos made no difference to accuracy on two separate corpora.
A scan costs 2.3¢ instead of 30¢. Those reference photos were charged as image tokens — twelve of them at full size came to 59,749 input tokens per scan against 4,367 without, so at Opus 4.8's $5/MTok a scan cost 13× more than it does now. Settings has always estimated "~2–3¢" for an Opus scan; that estimate is accurate for the first time. For a scan a day, $109/year becomes $8.
Added
Odometer scans now report where their time went (#78). A scan taking more than 10 seconds says so and names the stage responsible, and every scan logs a full stage-by-stage breakdown. The original report was impossible to diagnose from a description, and this is so the next one isn't.
Kept scan-review samples now record the scan's duration, its stage breakdown, and which AI model read it, so a slow scan can be told apart from a wrong one.
Changed
A car's reference photo is no longer used for anything. You can still set one and it is still stored, but nothing reads it — see #79.
1.19.1 2026-09-20
Fixed
A mistyped odometer reading no longer skews a car's average MPG. The fuel-economy chart already ignored impossible figures so they wouldn't wreck it — but the average shown on the dashboard didn't, so a bad entry disappeared from the chart while still dragging the headline number. Worse, the chart looking normal made the average look trustworthy. On a four-fill history with one 10× typo the average read 153 MPG instead of 30; the EV equivalent read 1,398 MPGe instead of about 102. Both now ignore the same entries, from the same threshold. This does not yet tell you about a suspect entry — it's still silently excluded from both. That part is #72 and is waiting on a decision about a "filled the tank?" checkbox, which would prevent most of these at entry instead of flagging them afterwards.
1.19.0 2026-09-20
Changed
The per-car fuel-economy chart is now spaced by fill-up, not by date. Fill-ups are unevenly spaced in time — a fortnight of commuting, then six weeks parked — and on a date axis those gaps dominated the picture while consecutive fills were squeezed together and hard to compare. Economy is a property of a tank rather than of a week, so each fill now gets an equal slot, oldest first. The axis labels still show when each fill happened, so nothing about the timeline is lost.
Fixed
Two fill-ups on the same day are both visible now. On the old date axis they landed on the same position and the second was drawn underneath the first — so a second fill on one day simply wasn't there. This was the motivating bug behind the axis change rather than a side effect of it.
1.18.0 2026-09-20
Fixed
A network blip can no longer produce a second, empty spreadsheet. On launch the app checks the workbook it remembers; that check treated any failure — a dropped connection, a Google outage, an expired token — the same as "the spreadsheet was deleted", and then went looking for one. Because Drive's file search is also eventually consistent and can briefly not list a file that exists, the two together could end in the app creating a fresh empty workbook while your real one sat in Drive. That presents as every car and every entry having vanished. Now only a genuine "not found" justifies making one, anything else is reported so you can retry, and an empty search is confirmed a second time before creating.
Entries with no odometer reading no longer show "0 mi". An insurance payment logged without a reading was displaying as made at zero miles; it now says "no reading", shown lighter rather than in a different colour.
A vehicle history document no longer tells the AI a car was bought at zero miles when the purchase odometer was never recorded — it says "not recorded" and asks it not to estimate annual mileage from the ownership period. Both facts were going into a document meant for a buyer.
Internal
The Mac chart-typography test was branching on a compile-time platform check inside an iOS-only test target, so its Mac half never compiled and its expectations had drifted to sizes the code stopped using months ago. The sizes are now a pure function of "is this a Mac", so both platforms are checked from either one.
digitRegion now has tests that run real text recognition over rendered digits and verify the crop lands on the half containing them — closing the gap where ADR-040 claimed a coordinate flip was covered when the test actually pinned its consumer.
ADR-009, ADR-016 and ADR-018 corrected where they described intentions as facts, each dated with the version that made it true. ADR-048 records the rule. Tests 340 → 350.
1.17.1 2026-09-20
Fixed
"Delete kept photos" no longer claims success when it fails. If removal failed, Settings said "Kept so far: 0" with the photos still on the device — and because the button only appears when the count is above zero, it also took away the only way to try again. The count now only resets when the files actually go.
A large photo can no longer slip past the size limit the app applies before sending a scan. An optimisation added in 1.17.0 measured the image in points rather than pixels, so an image with a 2× scale factor could be sent at double the intended resolution. Not reachable from the camera, but the limit is there for a reason.
Kept scan photos decide where to go by asking the store, not by checking which storage mode the app is in. Those can disagree, and when they did the photos would have gone back to being unnamed files — the bug 1.17.0 fixed.
Internal
ADR-047 (a pixel budget is not a point budget); ADR-045 extended with CarStore.preservesFileNames and the failed-delete path. Tests 337 → 340.
second-opinion reworked to make agreement expensive rather than its prose harsher: claim_check now demands a falsifier before a ruling and can answer "could not check", and the reviewer must name its weakest claim and where it would look for a defect it missed. Re-running the identical review took it from 0 findings to 2.
1.17.0 2026-09-20
Fixed
A scan no longer refuses to fill in a reading just because the two readers disagree. The app cross-checks the AI against on-device recognition, and it was leaving the field empty whenever they differed — but the on-device reader is the weaker of the two, so it was vetoing perfectly good readings and making you retype them. Now it fills the field and tells you the other reading, and only withholds when something actually contradicts the number: the AI's own digit list not matching, or a reading with fewer digits than your last one.
The digit-count check now uses the car you picked, not the car the scan guessed. Previously a correct low reading on a new car could be refused because a different car reads 128,456 — and the message quoted a number that car never had.
Saving an entry no longer waits for the diagnostic upload. With "Keep my odometer scans" on, the entry sheet stayed open while two photos uploaded.
Kept scans work properly on-device. In local mode the photos were being written as unnamed files with no folder and no way to find or delete them, while Settings said otherwise. They now go to a real folder with real names, and Settings has a "Delete kept photos" button that actually works.
Kept scans record kilometres correctly. One field was being stored in miles while the rest were in your chosen unit, which made every kept sample wrong for anyone not using miles.
A gas-pump scan no longer inherits the previous odometer photo, which filed the pump's number against the wrong picture.
A scan photo is no longer re-processed four times, which on a 12-megapixel photo was enough memory pressure to matter on an older phone.
The "Keep photos I correct" switch is now "Keep my odometer scans", because it keeps all of them — the correct ones are what show whether a change helped.
Internal
ADR-044 (evidence of error withholds; absence of agreement warns), ADR-045 (a store method is not reusable until both implementations are read), ADR-046 (units are picked once and recorded with the value). ADR-040's test-coverage claim corrected. Tests 333 → 337. Two findings filed as #77 rather than guessed at.
1.16.0 2026-09-20
Added
Settings → Improve Odometer Scanning. Off by default. Turn it on and each odometer photo you scan is kept in a “Scan Review” folder in your own Google Drive (or on the device in local mode), together with what the app read and what you actually saved. That gives the scanning something real to be measured against instead of sample pictures — which is what the last round of fixes was missing. Photos go nowhere else, and you can delete the folder whenever you like; nothing in the app reads it back. It keeps the ones it got right as well as the ones you corrected. That sounds like clutter and isn't: without them, a change can look like an improvement while quietly breaking readings that already worked.
Fixed
A corrected car guess again learns from the whole dashboard photo. In 1.15.0 the new zoom-to-the-digits step meant that when you corrected which car a scan belonged to, the app stored a tight crop of the instrument cluster as that car's reference picture instead of the dashboard shot — leaving later scans trying to recognise a car from its gauges.
Internal
ADR-043. Testing/odometer-bench/import-scan-review.py converts a downloaded Scan Review folder into a bench sample set. Tests 326 → 333.
1.15.0 2026-09-20
Fixed
The photo is now zoomed to the numbers before it's sent. A pump display fills the frame, which is why pump scanning always worked. Your instrument cluster fills maybe a quarter of a dashboard photo, and after the app shrank the picture for upload the odometer digits were only about 20 pixels tall — too small to read reliably. The app now finds the cluster on-device, crops to it at full resolution, and sends that at higher detail. On test photos framed like real ones this went from 0 out of 6 correct to 6 out of 6.
A reading the app isn't sure about is no longer filled in for you. It already ran a second, independent reader to cross-check — and when the two disagreed it said "double-check" and filled in the first one's answer anyway. Now it tells you both numbers and leaves the field for you. It also refuses a reading that has fewer digits than your last one, since an odometer can't lose a digit, and one where the AI's own digit-by-digit transcription doesn't match the number it reported.
Internal
New Testing/odometer-bench/ — scores scanning against photos with known readings, and holds the measurements behind the above. Not in the app target.
Removed a line from the AI prompt warning that photos "may be upside down". Measured over 40 calls on upright and rotated images, it changed nothing in either direction; the app already corrects orientation before sending.
Three architecture decisions recorded: ADR-040 (crop to the digits before encoding), ADR-041 (a prompt may not describe a condition the pipeline has removed), ADR-042 (prefill a scan only when nothing contradicts it).
Tests 315 → 326.
1.14.0 2026-09-17
Fixed
A second charge at the same charger could vanish. Two charging entries for the same car, day, odometer and price but different kWh were treated as one duplicate row, and the second disappeared on every read — then a move to a new store made it permanent. Energy is part of what distinguishes two entries now, as is the "fuel added without being logged" flag.
Dates edited in the spreadsheet could shift to the previous day. Once a date cell is formatted as a date, Sheets stores it as a plain day number — and those were read as UTC, which west of UTC is the day before. A entry dated the 1st came back as the last day of the previous month, moving it between months in the charts and reports. It now lands on the day it names.
Changes queued for one Google account could sync into another. Queued changes deliberately survive a sign-out, so they aren't lost — but they weren't tagged with whose they were. Signing in with a different Google account replayed the first account's changes into the second account's spreadsheet. They're now held for the account that made them: sign back in and they sync, or Disconnect to discard, and the app tells you which account is waiting.
One unreadable queued change no longer discards all of them. Unsynced changes were read all-or-nothing, so a single damaged one silently dropped the entire queue — the only copy of every change waiting to sync. Each is read on its own now, and if any can't be read the app says how many were lost instead of leaving you to discover it.
Ticking off a reminder or a to-do no longer reverts an edit made elsewhere. A one-tap "done" sent the whole row from this device's copy, which can be up to 30 seconds old — so it could undo a title or note changed in the web version. It now writes only what being done actually changes.
A photo or document uploaded while the app refreshed kept its file. A narrow timing window could blank the link between an entry and the file it had just uploaded, which then left the file stranded in your Drive when the entry was deleted.
A deleted document's row goes before its file. The two happen as separate requests, and if the second was interrupted the old order left an entry pointing at a file that no longer existed — unopenable, forever. Reversed, an interruption leaves an unused file in Drive instead, which nothing has to look at.
Internal
Five architecture decisions recorded: ADR-035 (a queued write belongs to one account and one record), ADR-036 (read stored collections element by element), ADR-037 (delete the row before the file), ADR-038 (a dedup key names every meaningful field), ADR-039 (a spreadsheet date cell is a calendar day).
Tests 287 → 307. The Sheets test harness can now answer requests by URL, so a test about the order of requests can't pass or fail for the wrong reason.
1.13.0 2026-09-16
Fixed
A decimal odometer reading was ten times too high. Typing 12345.6 — a trip meter, a km reading, or what a photo scan returns — stored 123456. The reading is what every mileage, MPG and cost-per-mile figure in the app is worked out from, so one entry threw off every chart, and the wrong number looked like real data rather than a mistake. Reminder due-mileage and repeat intervals had the same bug. All three now read the number you typed, including in locales that write 1250,5.
An edit made while a change was still uploading could be thrown away. Renaming an entry that hadn't finished syncing showed as saved, then vanished when the earlier upload completed. The newer version now stays queued and goes out.
Fixing that could have duplicated an upload. A photo or document whose entry stayed queued would have been uploaded a second time, leaving an orphan file in your Drive. The app now remembers the file it already uploaded and reuses it.
A future update could have discarded your unsynced changes. Reminders and to-dos were the two records whose stored form couldn't tolerate a new field being added. Because unsynced changes and the offline cache are both read leniently, one unreadable field would have silently dropped the whole queue of changes waiting to sync — the only copy of them — rather than one field. Both now read tolerantly, like every other record already did.
nan or inf no longer breaks a save. Both parse as real numbers in Swift, and neither can be written to JSON — so one in a cost, gallons or kWh field made the offline cache fail to save, with nothing shown in Google mode. Rejected now both when typed and when read from the spreadsheet, which is the likelier source: the sheet is a document you and the web version both edit.
Internal
Running the test suite no longer writes to (or deletes from) the same on-device storage a development build uses. Tests get an isolated temporary directory.
The column-ownership rule from 1.12.0 — a write only sends the columns it owns, so it can't clobber a value the web app set — is now pinned by tests across every combination, driven through the real Sheets code rather than a stand-in.
Two new architecture decisions recorded: ADR-033 (the unsynced queue is the only copy of a change) and ADR-034 (one parser for every user-typed number).
1.12.0 2026-09-16
Fixed
A save could overwrite or delete the wrong record. Every write found its row by id and then wrote to that row number. If the web app, your Mac, or you editing the sheet by hand removed a row above it in the meantime, everything shifted up and the write landed on a different entry — an edit overwrote its neighbour, a delete removed the wrong one, and the duplicate that was left behind was hidden on the next read. Writes now re-check the row is still the right one immediately before touching it, and abandon the write rather than guess.
A failed request could delete a second row. Deletes are sent as a POST, and any failed request was automatically retried — so if the server applied the delete and then failed, the retry deleted the next row down. No second device needed; one bad connection was enough. Requests that create or remove something are no longer retried.
Moving your data to Google could wipe rows added elsewhere. The move cleared the existing spreadsheet first, including anything the web version had added that this device had never seen — under a dialog promising nothing would be deleted. It now checks first and refuses, telling you what it found.
A Google outage could throw away your offline edits. Edits made offline were dropped after five failed attempts, and a server error counted as an attempt — so a long outage deleted the very changes the queue exists to protect. Server errors and rate limits now keep their place in the queue.
An edit could bring back something you deleted. If you deleted a car or entry on the web and your phone had an unsent edit to it, the edit re-created it. The app now knows the difference between "this is new" and "this was deleted elsewhere".
1.11.0 2026-09-16
Added
Reminders to record an odometer reading (#33). Mileage is worked out between readings, so a gap between them leaves those months blank — and they can't be filled in afterwards. The app now notices when a car's last reading is older than an interval you choose (Settings → Odometer Readings; every 3 months by default), badges the car, lists the overdue ones, and sends a notification that opens straight into that car's entry sheet. Counted from each car's last reading, not a fixed date.
This is deliberately about readings, not activity: the existing "no recent entry" badge was satisfied by a cost-only entry, so a car with monthly insurance and no reading for a year looked fine.
A real first-run screen (#11). It used to open on "Where Should Your Data Live?" and two buttons — asking you to decide before saying what the app does or why a spreadsheet is involved. Now it says what it's for, what each storage option means for you, and that the AI features are optional.
Fixed
Saving a report on the Mac works again (#32). The Mac was getting the iPhone share sheet, which has no "Save to Files" — a menu with no way to save the report at all. Same root cause put the camera button and document scanner back on the Mac, where they can't work.
An edit no longer vanishes for 30 seconds (#41). If a refresh started while an earlier change was still uploading, the change could disappear from the screen until the next refresh.
1.10.1 2026-09-16
Fixed
Moving your data to Google no longer loses your car values. 1.10.0's own fix for cross-client edits accidentally made the migration write every car with a blank "Your value" and no estimate history — into a sheet it had just cleared, with the on-device copy deleted straight after. Unrecoverable. If you ran a move to Google on 1.10.0, check your values.
"Update Estimates" no longer wipes a value you set on the web. The AI refresh was writing the price column as well as the estimates, and it only runs on cars it thinks have no price — which is exactly when another device has just set one.
Plug-in hybrids get their MPG back. 1.10.0 started treating a charge as a break in the petrol chain (and a fill as a break in the charging chain), so a car that does both reported far worse economy than it gets.
A queued offline car edit no longer restores its own stale value over a newer one when it replays.
A document deleted on another device no longer reappears when a queued rename replays.
Renaming a file from the rename sheet no longer strips a dot-suffix that isn't really an extension — the sheet had its own copy of the old rule.
The expenses drill-down now ends where its bar does, like mileage already did.
An AI estimate that fails offline no longer promises to sync later.
On-device mode no longer reports itself "offline" when a read fails; there is nothing to be offline from.
1.10.0 2026-09-16
Fixed
Editing a car on the phone no longer wipes a value you set on the web. Every save wrote the whole row from the phone's copy, so renaming or retiring a car blanked "Your value" if the web had set it since the phone last read — the garage total silently reverting to the AI estimate. A save now writes only what it changes. Same for renaming a document versus marking it as from a previous owner: those two used to undo each other, with no conflict check at all on that sheet.
"Missed a fill-up before this" works on a cost-only entry. Gallons are optional, and an entry without them was dropped from the economy chain before the flag was ever read — so the next fill was measured straight across the unlogged fuel and could read double the real MPG. A fuel stop with no amount now breaks the chain whether or not you tick the box.
A cost dated next month no longer inflates a car's bar. Pre-pay a registration and the per-car bar counted it while the drill-down you get by tapping that bar stopped at this month, so the two didn't add up.
On-device mode offers Try Again when a read fails, instead of an empty garage inviting you to add your first car on top of data that is still there.
A failed first Google sign-in no longer says your access "expired or was revoked". It never existed, and Settings just repeated the same failure; the message now says sign-in failed and why.
"Will sync as soon as you're back online" is only promised where it's true. Reference photos, AI estimates, document scans, opening a file and data moves aren't queued — they need doing again, and now say so.
Renaming a file whose name contains a dot but no real extension ("Invoice no.5") no longer invents one, giving you "Registration.5".
A deleted document no longer flickers back into the list when a refresh lands inside its 8-second Undo window.
1.9.1 2026-09-15
Fixed
Deleting something no longer loses it if you move your data. A file inside its 8-second Undo window was hidden from the app but not yet deleted — and starting a move to Google or to this device in that moment copied everything except that file, then destroyed the original. The record and the file were both gone, with Undo still on screen. Cancelling a pending delete now puts the row back first, and the Undo banner goes away with it.
A corrupt local file no longer erases your garage. In on-device mode, a store file that couldn't be read was quietly replaced with an empty one on the next launch — no error, just an empty garage and nothing to recover from. The file is now left alone and the app tells you, so a backup can still restore it.
A move to Google can't destroy your spreadsheet half-way through. The destination was cleared before anything was uploaded, so a dropped connection during the upload left it permanently empty. Clearing now happens after the files are safely across.
Moving data from a store that failed to open is no longer offered. It would have cleared the destination and deleted the local copy while holding nothing to move.
A revoked Google sign-in no longer discards queued changes. Edits made after access was revoked were treated as ordinary failures and dropped after five retries, despite being the exact case the app promises to hold onto. The sign-in badge now offers to reconnect instead of silently doing nothing, and the message says what to do rather than "You're not signed in to Google."
An error arriving while Undo was on screen used to replace it, removing the only way to stop a delete that still went ahead. It now waits its turn.
A missing file on this device is no longer reported as missing from Drive.
A failed move no longer hides a "reconnect" instruction behind "couldn't refresh".
Changed
The tests guarding error messages now check the behaviour a release build has. They were passing in Debug for a reason unrelated to the code, so they could not have caught the bug they were written for — verified by deleting the code and watching them stay green.
1.9.0 2026-09-14
Added
The first screen now asks where your data should live. The Dashboard and Reports tabs used to say "Not Connected — Connect in Settings" with nothing to tap, and Dashboard is the tab the app opens on. Both now offer the same two buttons the Garage tab always had.
"Try Again" when a load fails. A first read that timed out used to show "Not Connected", which told a signed-in user to go and reconnect — while the banner underneath said it would sync itself. Failed loads are now their own state with a retry that re-opens whichever store you chose.
Undo for deleting a report or document. Delete lives in a long-press menu right under "Open in Drive", and one mis-tap used to destroy a scanned title or a buyer dossier for good. The row disappears at once and the delete only commits after an 8-second Undo window.
Share on saved reports. The share sheet used to appear exactly once, right after generating — so emailing Thursday the dossier you built on Sunday meant regenerating it. Share is now in the row's menu, including in local-only mode.
Charts say when they're hiding a car. A car with no activity in the range, or fewer than two fill-ups, simply vanished — indistinguishable from one whose entries hadn't synced. Each chart now names how many are missing and why.
Errors keep their own wording. An independent review found the new error mapper was swallowing the app's own messages: a blocked migration, a denied Google permission, or a mistyped AI address all came out as "Something went went wrong — try again." The mistyped-address case was the worst, since "try again" could never work. Those messages now survive, with tests so they stay that way.
A revoked Google session can reconnect again. If access was revoked part-way through a load, the app concluded the session was fine and offered a retry that could never succeed — with the reconnect button hidden, so Disconnect was the only way out. It now recognises that case.
Undo covers album photos too, not just documents and reports.
Changed
The dashboard keeps its controls while loading. A cold launch replaced the whole screen with one centred spinner; the metric and range pickers now stay live so you can set up the view you want while the read is in flight, and the text says what it's fetching instead of "Syncing", which is also what the background-refresh badge says.
A visible way out of a single-car chart. Getting back used to mean reading a caption and finding that car in a ten-item legend — and when any bar was an estimate, the caption said something else entirely and there was no instruction at all. There's now an "All cars" button beside the chart title.
"Spent" says "(all time)". It sums every entry ever but sat above the 1M/3M/6M range pills and a "last 6 months" caption, so it read as six-month spend and didn't change when you tapped 1M.
The garage total says how many cars it covers — "Estimated total (8 of 9 cars)" — and cars without an estimate say "No estimate" on their own row, instead of the shortfall being sentence three of a grey footer with no way to tell which car.
Error messages name the next step. A Google failure used to put its raw API text in the banner ("Google API error 403: {300 characters of JSON}"). Rate limits, outages, expired access and missing sheets now each say what to do; the raw text is kept for debug builds.
Report names truncate in the middle. Every name starts with the same date, so cutting the end hid the only part that identified the file.
Fixed
Charts and the "Cars" count disagreed. The tile counted active cars while every chart drew retired ones too — 9 against 10 bars — so retired mileage quietly inflated fleet comparisons.
The floating add button permanently covered the last row of the Garage list; the list now has the bottom clearance the Dashboard already had.
The error banner covered the navigation title it appeared over.
The Undo button's tappable area was the word "Undo" rather than the button drawn around it, well under the minimum target size — on the one control in the app with a countdown attached.
A pending delete could outlive a sign-out or a data move and then try to delete a file in the wrong place.
A rotated phone had no way out of a single-car chart, and a car retired while you were looking at it became unclearable.
An unrelated edit during an undo window could leave the deleted row hidden until the next successful sync.
Retired cars were labelled "No estimate" forever, contradicting the count in the footer above them.
Tapping a document twice while it was still downloading cleared the preview.
Local-only mode showed a healthy "on this device" in Settings even when the store had failed to open, with no way to retry.
1.8.2 2026-09-14
Fixed
Chart text on the Mac is actually larger now. 1.8.1 shipped bigger Mac chart sizes that never applied, because the Mac test was #if targetEnvironment(macCatalyst) — a compile-time condition that is false in the iOS binary, and TestFlight hands the iOS binary to an Apple Silicon Mac ("iPad app on Mac"). The check is now ProcessInfo.isMacCatalystApp, which is true for a Mac Catalyst app and for the iOS app running on macOS, so it fires whichever build is installed. Sizes went up again as well, and the per-car bar chart's value axis was picking up the default size instead of the Mac one.
1.8.1 2026-09-14
Fixed
Chart text is legible on the Mac. Axis labels, the numbers at the end of each bar, legends and the captions under each chart were all rendering about a quarter smaller on macOS than on iPhone — Mac Catalyst draws the iPad interface at 0.77 scale, which body text absorbs and an 11-point axis label does not, particularly across a window three times the width of a phone. Chart type now has explicitly larger sizes on Mac, chosen to land at the same apparent size as on iPhone. The iPhone and iPad are untouched, and so are the PDF reports — they share those chart views, and a report should look the same whichever device made it.
1.8.0 2026-09-14
Changed
The expenses graph ranks your cars, like mileage does. It was a stacked monthly chart, so working out which car actually costs the most meant comparing the heights of coloured segments by eye. Expenses is now one bar per car totalled over whatever range you've picked — the ranking is the chart — and tapping a car's bar shows that car's spending month by month. The fleet-wide monthly view it replaces is still there per car, one tap in.
Added
The app keeps itself up to date while you're looking at it. It now re-reads your data every 30 seconds whenever it's the app in front, so a Mac window left open beside you picks up what you logged on the phone without being touched. Foregrounding still triggers a read as well, and nothing polls in the background or in on-device mode, where there's nothing to poll.
Fixed
A car you'd tapped into can always be untapped. On a chart tab with no data in the selected range — or when the car had been deleted on another device, or simply had too few fill-ups to chart — every bar dimmed with no way to clear the selection. Every tab that dims for a selection now offers a way back. (The Total chart never dims, because it isn't per-car.)
1.7.1 2026-09-14
Changed
Report filenames all start with the date now. The four report types each built their own name and had drifted into three different shapes — the garage report, the car report and the vehicle-history packet put the date at the end, while the fleet report put it at the front. Every one is now YYYY-MM-DD — what it is — which car, so a Reports folder sorts chronologically wherever you look at it. Reports you already generated keep the names they were saved with.
1.7.0 2026-09-14
Changed
Chart ranges are 1M / 3M / 6M / 1Y / All, and every one is a tap. The range used to be a single pill showing where you already were, which you tapped to cycle forward — fine with four ranges, tedious with five. All five now sit under the chart tabs with the current one filled in, so any window is one tap away and you can see which is active without touching anything. 1M and 3M are new; 2Y is gone, since All covers it. Swiping up and down over the chart still steps through them.
Fixed
A second device now notices what the first one did. Reports generated on the iPhone didn't appear on the Mac until Garage Buddy was quit and relaunched. Two things caused it: the Reports tab had no pull-to-refresh (the Dashboard and Garage have had one all along), and coming back to the app only re-read your data if the last sync had actually failed — an app sitting there connected never asked for anything new. Returning to the app now re-reads if what's on screen is more than a minute old, and the Reports tab can be pulled down like the others.
1.6.0 2026-09-14
Added
A Reports tab, and reports that are kept. Reporting used to be four buttons hidden in the toolbars of three different screens, and what they produced existed only in the share sheet — dismiss it and the report was gone. There's now a fourth item in the dock where all four are generated: the fleet report, the whole-garage PDF, and — with a car picker — a single car's report and its vehicle-history packet. The car page and the garage keep their own screens uncluttered; their export buttons have moved here.
Every report is filed and listed. Generating one saves it to a Reports folder in your Google Drive, alongside the per-car folders, and it appears under "Saved reports" in the tab — tap to read it again, or long-press to open it in Drive, rename it, or delete it. Reports save the same way attachments do, so generating one works offline and uploads when you're back; on-device mode keeps them on the device instead. The fleet report, which was previously only ever a screenful of text, is now saved as a PDF like the rest — writing it files it automatically, and "Save the Numbers as a Report" keeps the figures on their own whether or not you have an AI key.
Fixed
Reading a car document is sharper and cheaper. "Extract Details" on a title, registration or insurance card rendered the PDF at about 72dpi — legible for a heading, marginal for the VIN underneath it — and sent every page of the file, however long. It now renders at roughly 150dpi and stops after the first few pages, which is where a document says what it is. The same rendering the receipt scan has used since 1.5.0.
Changed
The mileage graph now compares cars, and a new Total graph shows the fleet. The Mileage tab was a stacked monthly chart that answered two questions at once and neither cleanly: which cars you actually drive meant estimating the size of coloured segments by eye, and how much you were driving overall was hidden by every bar being split up. Mileage is now one bar per car, summed over whatever range you've swiped to — so the ranking is the chart — and tapping a car's bar still shows that car month by month, now matching the range you picked. A new Total tab next to it shows every car's miles combined, one bar a month.
The fleet report says what the money was actually spent on. It treated a car's spending as one number, so a set of tires filed under Maintenance came back as "$900 in maintenance" — which reads as a car that keeps needing work rather than one purchase. The report now breaks the period's spending down by category, with the number of entries behind each total, and a category that is really a single item says so. The written summary is told the same thing and is told not to describe a one-off as ongoing servicing.
1.5.1 2026-09-07
Changed
The MPG chart makes room for a full garage. With eight cars on it, the bars, the car names, the numbers, and the gridlines were all crushed into one 260-point band and the colour legend wrapped onto a second line repeating names the chart already showed. The chart now grows with the garage — each car gets a proper row and the dashboard scrolls — the legend is gone from this view, and the horizontal gridlines that ran between rows went with it. Tap a bar now, rather than a legend entry, to drill into that car's fill-ups; the per-fill view keeps its legend for switching cars and going back. The number on the longest bar no longer runs off the edge.
1.5.0 2026-09-05
Added
What your garage is worth. The Garage screen now leads with an estimated total, how it has moved since the last estimate, and a per-car figure on each row; a car's own page shows its value, the change, and how it compares to what you paid. Values come from the AI you've already configured — one small text-only question per car, from its year, make, model and mileage — so this appears only if you've set up an AI key, and every figure is labelled as an estimate with the date behind it. They're cached and topped up at most once a launch, and only when a car has never been valued or its estimate has passed 30 days, so a ten-car garage costs roughly ten small calls a month; "Update Estimates" re-runs the lot on demand. A generic estimate is worst exactly where cars are most interesting, so Your value in a car's Edit screen overrides it everywhere, including the garage total — set it for modified, rare, or project cars and that car is never asked about again.
Documents from before you owned the car. A car's paperwork often arrives with it — the previous owner's service invoices, an inspection report, the bill of sale. Long-press any document on a car's page and mark it "From a Previous Owner": it moves into its own Previous Owner section, wears a badge, and is described as inherited history everywhere it appears. It counts as a record, never as your mileage — these are car documents, which carry no odometer reading, so nothing about them can reach your mileage history, averages, projections, or service intervals. They travel with the car: listed and marked in the car report, and — unlike your title, registration, and insurance, which stay private — included in the vehicle-history packet you hand a buyer, because a previous owner's service record is exactly what that packet is for.
Fixed
Two fill-ups on the same day drew one bar at their combined MPG. The per-fill economy chart bins its bars by day, and same-day bars were stacked — the default — so two 30 MPG fills rendered as a single 60 MPG bar and dragged the y-axis up with them. Each bar is now drawn from zero, one per fill, as the caption under the chart always claimed. Only the chart was affected: the stored entries, the car's average MPG and the moving-average line were right the whole time.
A PDF receipt now gets read, like a photographed one. Adding a receipt with "Choose PDF" attached the file and stopped there — no scan, no filled-in fields — while the same receipt photographed or scanned with the camera came back with its vendor, date, total, and a summary already in place. The PDF path now runs the same extraction: its pages are rendered and read, and the entry is pre-filled for you to check. Needs an AI key, like every other scan; without one the file is simply attached as before.
PDF pages are rendered at a readable size before being read. They were rasterised at their natural point size — about 72dpi, which is legible for a heading and marginal for the line items under it. Receipts and documents are now rendered at roughly twice that before going to the AI, which also helps the "Extract Details" action on a document you added earlier.
Removed
The fuel-price graph is gone. The "Fuel $" tab charted what you paid per gallon at each fill. It mostly drew the pump price at the stations you happen to use — a picture of the fuel market, not of your car — so it took up a tab without telling you anything you could act on. Mileage, expenses, and fuel economy are unchanged, and nothing about your fills was deleted: the cost and gallons on every entry are still there and still feed economy and cost-per-mile. A more useful money-per-distance view can take the slot later.
1.4.1 2026-08-29
Fixed
A seeded run could resync the real account over the sample fleet.-seedSampleData populates a demo garage and start() returns before any auth or network — but scene activation calls resumeSyncIfNeeded() straight afterwards, which reconnected and pulled the live Google Sheet back over the seeded data, usually a second or two after launch. Anyone capturing screenshots got real cars while believing the demo fleet was on screen. That is how twelve real cars, their photos and real spend reached the 1.3.0 Mac App Store listing. A seeded run now refuses to sync. DEBUG only.
Changed
Mac App Store screenshots now come from the sample fleet. Replaces the three shots on the macOS listing, which were captured from a real garage: cars by name, year, model and exact odometer, the owner's own photos of them (driveways, a house, a street), total spend, and a dated activity log. Screenshots on a READY_FOR_SALE version can only be replaced by submitting a new version, so this release exists to carry the clean ones.
New tools/capture-mac-screenshots.sh. Builds, launches the seeded app three times and captures each tab at exactly 2560x1600, so the store assets are reproducible instead of hand-grabbed. Backed by a DEBUG-only -screenshotFrame x,y,w,h launch argument that pins the window to an exact rect — Catalyst renders at 0.77 scale, so the numbers in the script are pre-calibrated to land on 1280x800 points.
1.4.0 2026-08-23
Added
"Missed a fill-up before this" on gas and charging entries. When a tank gets filled without being logged — someone else fills it, a jerry can, a receipt that never made it home — the next fill you log looks like it covered two tanks' worth of miles on one tank of fuel, and its MPG comes out roughly double. Turn the switch on when logging that next fill: its MPG is left out of the per-fill chart and the car's average, and the fill after it measures from there, so one unlogged stop no longer bends the trend line. The entry is otherwise a normal entry — its odometer, cost, and gallons still count everywhere else — and it's marked "after a missed fill" in history lists so the gap in the chart has a visible reason.
1.3.0 2026-08-07
Removed
The Mac no longer offers camera capture. "Take Photo" and "Scan Document" are gone from the Mac build. Every capture flow here means walking up to a car to photograph an odometer, a pump, or a receipt, and a Mac's camera is fixed to the screen facing the user — the buttons had no plausible use even where they worked. On the Mac they did nothing at all: a sandboxed app is denied the camera silently unless it claims the camera entitlement, so no permission prompt ever appeared. That is the App Review guideline 2.1 rejection for 1.1.0. Macs add photos and documents with "Choose Photo" and "Choose PDF", or scan on the iPhone and let it sync. iPhone and iPad are unchanged.
Fixed
Choosing a photo from the library left you with no obvious way out. On the Mac, the photo picker offers a checkmark to confirm but no Cancel, so changing your mind meant guessing that the Escape key would work. The Mac picker now has a Cancel button. iPhone and iPad already had one.
Library picking no longer uses a deprecated picker. It ran through UIImagePickerController, which Apple deprecated for library access in favour of the modern photo picker and which behaves poorly on the Mac, and it was presented as a full-screen cover — which fills the whole Mac window and, unlike on iPhone, can't be swiped away. Every library pick now uses the modern picker in an ordinary sheet.
A refused camera now explains itself. On iPhone and iPad, "Take Photo" asks for permission before opening the camera and, if it's refused, says where to turn it back on instead of leaving a button that does nothing. The odometer scan launched from the widget or a shortcut falls back to the photo-source dialog, so it offers the library rather than dead-ending.
1.2.3 2026-08-05
Fixed
Documents are named for when they were issued, not when they expire. 1.2.2 led a registration's filename with its expiration, filing a registration issued this year under next year. The document scan now reads the issue/effective date as well — it previously extracted only the expiration, which is why the name reached for the wrong one — and that is what the filename leads with: "2026-03-14 — Registration.pdf". A document showing only an expiration gets no date rather than a misleading one. The expiration is still read and still drives the renewal reminder; it just doesn't name the file.
1.2.2 2026-08-05
Changed
Document names lead with the document's own date, in YYYY-MM-DD. A receipt is now "2026-07-16 — Les Schwab.pdf", so documents sort chronologically in the app, in Drive, and in Files. The date is read off the document, never today's; a document with no date of its own gets no date rather than a filing date, which would say when you scanned it instead of what it is.
1.2.1 2026-08-05
Fixed
"Suggest a Name" reported readable receipts as unreadable. The document read only knows vehicle paperwork — title, registration, insurance — so a receipt came back classified as "other", and the app turned "I have no name for this kind of document" into "Couldn't read that document", sending you looking for a problem that wasn't there. Receipts are now read as receipts and named for the vendor and date ("Les Schwab — 2026-07-16.pdf"), and the two failures are told apart: one message for a file that wouldn't open, a different one for a document that was read but couldn't be named.
1.2.0 2026-08-05
Added
Scanned documents name themselves. With an AI key configured, a document you photograph or scan is read before it's saved and filed under a name worth reading — "Registration — expires 2027-03.pdf", "Insurance — State Farm.jpg" — instead of "Document 2026-08-05.pdf". The name comes from the structured fields the app already extracts, not from asking a model to invent a filename. Characters a filename can't hold (slashes, colons, newlines and the rest) are replaced, names are length-capped on a word boundary, and anything unusable falls back to the old dated name. No key, no signal, or an unrecognised document all fall back silently — the document still saves. Files you import keep the name you already gave them.
Rename a document. Long-press any car document → Rename. The extension is preserved for you, so a file can't stop opening as a PDF because the name was cleared. With AI configured, "Suggest a Name" reads the document and fills the field for you to accept or edit — it never applies a name behind your back, and it says so rather than replacing a good name with a generic one when it can't tell what the document is. Renaming also renames the file in your Google Drive, so the two don't drift apart.
Full vehicle history — the packet you hand a buyer. A car page now has two export buttons. The existing share button still produces the quick report; the new one builds a complete history document: a cover page, an AI-written history of the car (how long you've had it, how it's been used, what's been repaired and when), charts for mileage, spending, where the money went, and fuel economy per fill, then the service and repair record with your notes in full, the complete log, a page or two of photo thumbnails, and the scanned receipts themselves.
It deliberately withholds what a buyer shouldn't get. No purchase price, sale price, or profit anywhere in it. No title, registration, or insurance documents — those carry VIN provenance, policy numbers, and your address — and no receipts filed under Insurance or Registration. Those entries still appear as lines in the log; only their scanned images are held back. Nothing is removed from the app or the quick report.
It degrades instead of failing. No AI key or no signal means the history page is simply left out, and any attachment that can't be downloaded is counted in the document ("2 attachments couldn't be downloaded") rather than quietly missing. Photos beyond the two-page cap are reported the same way.
Receipts are downscaled before embedding, so a packet with dozens of them stays a sane file size. Multi-page PDF receipts are included in full, each page captioned "page 2 of 3" — a shop invoice often runs its line items and totals onto later pages.
The progress panel says what it's actually doing instead of one unchanging line: which step is running, what it's working on right now (the model being asked, or the file being fetched and how far through), and a per-step seconds counter so a slow AI call is visibly alive rather than apparently hung. Past eight seconds it also notes that the writing step usually takes 10–30 seconds.
1.1.0 2026-08-01
Added
Garage Buddy on the Mac. The app now runs natively on macOS (Mac Catalyst). Sign in with the same Google account and it reads and writes the same spreadsheet as your iPhone — edit on either, and the change shows up on the other. On-device mode stays per-device, since there's no shared storage to sync.
iPad support. Required for the Mac build, and the layout adapts.
Fixed
PDF reports couldn't be saved on the Mac. Exporting a report opened the macOS Share menu, which offers share destinations only — AirDrop, Mail, Messages — with no way to save the file. "Save to Files" is an iOS-only share action; on macOS saving belongs in a save panel. The Mac build now opens a proper save panel, and iPhone/iPad keep the share sheet (where Save to Files already appears alongside AirDrop and Mail). This also clears the duplicate "Copy" entry the Mac menu was showing.
Buttons were nearly invisible in dark mode. The accent colour had no dark-appearance variant, so blue-on-dark text sat at a 1.21:1 contrast ratio against a 4.5:1 accessibility minimum — worst on Settings, where "Connect Google Sheets" and "Keep Everything on This Device" were barely legible. Now 5.11:1. This affected iPhone dark mode too, not just the Mac.
The app called itself "CarLog" on the Mac — in the menu bar, in Finder, and in the macOS "…wants to use accounts.google.com to sign in" prompt. It now says Garage Buddy everywhere.
1.0.0 2026-07-24
Changed
First public release. No functional change from 0.38.4 — this version exists so the build matches the 1.0 record in App Store Connect. Every feature below shipped and was field-tested through the 0.x TestFlight series: local-first or Google Sheets storage with two-way migration, AI/on-device odometer scanning, per-fill MPG and MPGe, reminders and service intervals, photo albums and document scans, PDF reports, and home-screen widgets.
Fuel economy no longer reads in the thousands of MPG. An entry with no odometer reading was being used as the starting point for the tank-method average, so the car's whole odometer counted as one tank's worth of miles — a car doing a real 30 MPG could show as 2,515 MPG on the fleet economy chart, in the PDF report, and in the AI fleet summary. The same applied to EV efficiency (MPGe). This completes the 0.38.2 fix, which covered the mileage charts but missed the economy calculations.
No more reminders for cars you've sold. Date reminders on a retired car kept their scheduled notifications, so a repeating one (registration, insurance) would nudge you every year about a car you no longer own. Retiring or un-retiring a car now updates its notifications immediately.
A bad AI Base URL no longer crashes the app. Typing or pasting an invalid address into the custom AI endpoint (a stray space, a trailing newline) crashed on the next scan — and because the value was saved, it crashed again on every retry. It now reports a clear error instead.
The AI provider picker stops erasing itself. When entering a custom model id, choosing a provider reset the model back to a suggested one, which made the Provider section — and the Base URL field with it — disappear mid-selection. Choosing a provider now keeps your custom model and just moves the endpoint.
0.38.3 2026-07-20
Fixed
A dropped connection during a data move can no longer lose your photos. While moving data between Google and this device, any problem reading a file was treated as "you must have deleted this one" — so the move quietly unlinked it and carried on. A Wi-Fi drop, a timeout, or Google rate-limiting a burst of downloads partway through could therefore detach every remaining photo and document, report the move as successful, and (moving back the other way) overwrite the originals. Only a file the source genuinely no longer has is skipped now; anything else stops the move, which was always safe — nothing is deleted and you can simply retry.
0.38.2 2026-07-20
Fixed
Car prices no longer collapse to a few dollars. Typing a purchase or sale price went wrong the moment it reached four digits: the field re-formatted itself mid-typing (2500 → 2,500), and the next digit made it read the separator as a decimal point — so a $25,000 car saved as $2.50. Prices are now parsed once when you tap Save, matching how the rest of the app handles money entry.
Entries without an odometer reading no longer distort everything. A blank odometer (an insurance or registration entry, or an imported row with an empty cell) was being read as a genuine reading of zero, which:
charted the car's entire odometer as miles driven, smeared back across every month before the first real reading;
reported that whole figure as "driven with you" on the car and in the PDF report;
raised a false "possible odometer typo" warning on clean data, and put the orange check-this-entry triangle next to correct entries in the report. A reading of zero now consistently means "no reading" everywhere, as it already did on the activity heatmap.
Miles land in the right months. An entry that recorded no new miles (a blank or rolled-back odometer) reset the measuring point, pushing the surrounding interval's miles into later months on the chart.
Finishing an overdue reminder actually clears it. Completing a repeating reminder rolled it forward by exactly one interval — so an oil change 12,000 miles overdue, or a registration two years past, came back still overdue and needed tapping again for every interval you'd missed. It now rolls forward to the next date or mileage genuinely ahead of you (keeping any custom offset), and overdue annual reminders can be notified again.
Report typo-flagging catches both sides. When several entries share the most recent day, all of them are now checked against a later-dated higher reading instead of only the first.
Changed
The unit-test bundle can be code signed again (it had no generated Info.plist, so xcodebuild test failed to build). Tests are part of the release pipeline, so this had to work before anything could ship on it.
0.38.1 2026-07-20
Fixed
Data moves are safer (local-first review). A batch of fixes to the Google↔on-device migrations:
Adding or editing anything during a move is now held with a "finishing the data move" note and the + button hides, instead of the change landing in the store about to be swapped out and being lost.
Moving back to Google no longer resurrects deleted cars/entries. The move now clears the destination sheet before rewriting it, so it mirrors what's on your device exactly (previously it merged into the reused spreadsheet, so anything deleted while on-device reappeared).
A single file the app can't read (a Drive file you deleted, a missing local blob) is skipped and reported instead of aborting the whole move.
Tapping "Move Data to Google Sheets" twice can't start two moves; a failed move cleans up the files it uploaded so a retry doesn't pile up duplicates in Drive.
Charts tell same-named cars apart. Two cars with no nickname and the same year/make/model (or two blank "Untitled Car"s) no longer share a color, collapse into one legend entry, or get isolated together — charts now key on each car's identity, not its display name.
Smoother photo album. Adding several library photos at once, and opening a photo full-screen, no longer briefly freezes the screen (image encoding moved off the main thread); album memory use is capped.
0.38.0 2026-07-18
Added
Local-first mode — no Google account needed. On first run the app asks where your data should live: a Google Sheet in your own Drive (as before), or entirely on this iPhone. Local mode stores everything on-device (included in your device backup), works with zero network, and only the optional AI features ever leave the phone. You can switch later and take your data with you, in both directions, from Settings → Data: "Move Data to This Device" copies every entry, photo, and document out of Google (leaving the spreadsheet behind as a frozen snapshot), and "Move Data to Google Sheets" moves a local garage back into Google Sheets + Drive. Both moves show progress, survive being retried, and never delete the source until the copy has fully succeeded.
0.37.1 2026-07-18
Fixed
Copying the PDF report finally pastes. The share sheet's Copy now puts a named PDF file on the clipboard — the same thing the Files app's Copy produces — so pasting into Messages, Mail, or Files attaches the report. (The previous fix copied typed PDF bytes, which most apps ignore on paste; that's why Copy still appeared to do nothing.)
0.37.0 2026-07-18
Added
Add several album photos at once. "Add Photos" on a car's page now opens a multi-select library picker — pick any number and they all join the album in selection order (the first becomes the cover on an empty album). Taking a photo with the camera remains one at a time.
Paste photos into the album. Copy a photo anywhere (e.g. from a text message) and "Add Photos" offers Paste Photo whenever the clipboard holds an image. iOS asks its usual allow-paste question on first use.
Fixed
Chart legend wraps. The tappable car legend under the charts scrolled horizontally, which clipped cars off the end and dragged the chart when swiped. It now wraps onto as many lines as needed, every car visible.
Garage rows no longer wrap. With the to-do counter and badges in the row there wasn't width for longer names, which wrapped onto extra lines. The name and odometer use a slightly smaller font and names stay on one line (truncating if very long).
Car page toolbar overlap. The car's PDF-export button overlapped the Edit button; it now sits on the left edge of the navigation bar, in the same spot as the whole-garage export button.
Dashboard summary cards. Long amounts (e.g. $19,930) in the Spent card no longer wrap — the value shrinks to fit on one line. The Cars card now opens the Garage tab when tapped, and the Due soon card jumps to the reminders list further down the dashboard.
Fuel $ chart crash. The fuel-price chart colored each dot by the car name stored on the transaction, while its color scale was pinned to current car names — so any fill logged before a car was renamed (or a hand-edited name) crashed the chart the moment it appeared, every time. Dots now resolve the car by its stable id and always use the live name; fills whose car no longer exists are dropped. (Reproduced against the chart framework and covered by regression tests; the other charts already derived names from live cars and were never affected.)
0.36.0 2026-07-18
Added
Car photo albums. Each car has a Photos section on its page: add whole-car shots from the camera or library, tap to view full-screen (pinch-zoom), long-press to delete or pick the cover — the photo that headlines the car's page and appears as its thumbnail in the garage list (first photo added becomes the cover automatically). Pictures are stored in a "Photo Album" folder inside the car's own Google Drive folder, ride the same offline pipeline as receipts (photos added with no signal appear immediately and upload when a connection returns), and render from an on-device cache that decodes thumbnails at thumbnail size — no Drive round-trip, no scroll stutter. The album is separate from the AI "reference photo" (still a dashboard shot for scan recognition) and from Documents (paperwork).
Per-car to-do list. A lightweight checklist on each car's page for maintenance things you notice and don't want to forget — type an item, tap to check it off, swipe to delete. No due dates or notifications (that's what Reminders are for). The garage list shows a badge with each car's open-item count. Stored in a new Todos tab (created automatically) and fully offline-capable like every other write.
Fixed
Offline cache survives updates. Adding a new field to the on-device cache (the todos list) would have silently discarded every existing cache on first launch after updating — blanking the app for anyone who updated while offline. The cache now decodes old files, treating missing sections as empty (with a regression test).
Sheet booleans (is_done) are now parsed by one shared reader for reminders and to-dos, so the accepted truthy spellings can't drift apart.
Removed
Driving-activity heatmap dropped from the PDF report — the GitHub-style day squares spread miles evenly between odometer readings, which didn't reflect how entries are actually logged.
0.35.0 2026-07-17
Added
Concurrent-edit protection. Rows now carry an updated_at stamp (column added automatically). A change queued offline no longer clobbers an edit made later from another device or the sheet — the newer edit wins and the stale queued write is skipped. Rows without a stamp (hand edits, older data) always accept writes, preserving hand-edit tolerance.
Attachments work offline. Receipts and documents captured with no signal are staged on-device, appear (and preview) immediately, and upload to Drive automatically when a connection returns — completing the offline story. Deleting a still-queued attachment cancels the upload instead of round-tripping it.
Fleet report. A new report button on the Dashboard opens a this-month / this-year fleet summary: total miles and spend, a utilization ranking (workhorses at the top, idle cars flagged), and an AI-written narrative covering standouts, upcoming maintenance, and anomalies. The year view doubles as a year-in-review.
Scans learn from corrections. When a photo scan guesses the wrong car (or can't guess) and you pick the right one before saving, that photo automatically becomes the car's reference photo if it didn't have one — so the next scan recognizes that dashboard.
AI document reading. Long-press any car document (new or previously imported, photo or PDF) → "Extract Details" has the AI read it: VIN, license plate, policy number, carrier, and expiration date, presented in a review sheet where you choose what to apply — set the car's VIN/plate, and create a renewal reminder from the expiration date. Nothing is applied without review.
EV support. New "Charging" entry category with a kWh field (a kwh column is added to existing sheets automatically). EVs get a per-charge efficiency chart (mi/kWh with moving average) on their car page, and they join the fleet economy chart as MPGe (33.7 kWh ≈ 1 gal), marked with an "e" — the Model 3 and the F-150 finally share a scale.
Single-car report. A share button on the car screen exports that car's full history as its own PDF — the "hand it to the buyer or mechanic" document.
Report: documents + driving heatmap. Each car's report section now ends with its car-level documents (title, registration, …) and includes a compact GitHub-style driving-activity strip (one square per day for the last 26 weeks; miles spread between odometer readings).
PDF report navigation. The garage overview rows on the report's first page are now clickable — tapping a car jumps to its section. Cars are ordered deliberately (active first, then most recently driven) instead of whatever order the sheet had.
Smarter anomaly alerts. Two new data-quality warnings join the rollback/stale badges: an entry costing several times the car's norm for that category ("check for a typo"), and a fill-up whose fuel economy is well below the car's recent average ("service may be due"). Shown on the car page and as garage badges.
Fuel price trends. A new "Fuel $" dashboard chart shows what you actually paid per gallon (or liter) at every fill — one dot per fill-up, colored by car, with a 5-fill rolling average line. Swipe/range and the tap-to-isolate legend work like the other charts.
Service templates. New reminders offer one-tap presets (oil every 5k, tire rotation every 7.5k, registration yearly, insurance every 6 months, …) that fill the form with the repeat interval and the next due point anchored to owner's-manual mileage multiples. Everything stays editable before saving.
Retire = sell in one step. "Retire (Sold) Car" now opens a small sheet asking for the sale price (optional, with live profit preview) and sale date, instead of retiring and then editing the car to record the price.
Report entries sort by mileage (highest first), and any entry whose calendar day contradicts the mileage order gets an orange "date out of order" flag — likely an entry error. Several same-day entries with shuffled miles are normal and never flagged. Both sides of a date/mileage disagreement are flagged, since either could be the typo.
Fixed (from code review)
Locale-safe number entry. Costs, gallons, and car prices are parsed with a locale-tolerant parser: "1,250.50", "1.250,50", and "1250,50" all read correctly. Previously a US-style thousands separator silently dropped the cost, and comma-decimal input could inflate a price 100×.
Revoked Google access recovers gracefully. If you remove Garage Buddy's access in your Google Account, the app now signs out locally, keeps your cached data and queued changes, shows one clear banner, and Settings' "Try Again" runs the full sign-in. Previously it looped error banners while still looking connected.
Car document PDF import reports read failures instead of silently doing nothing.
AI scan replies parse more robustly — JSON extraction now survives models that wrap the answer in prose containing braces.
Odometer photos decode once, not twice, for the upright + rotated OCR passes (faster scans, less memory).
Settings footer no longer references the old "Car Log Data" sheet name.
0.34.1 2026-07-16
Fixed
"Copy" in the report share sheet now copies the PDF itself (pasteable into Mail as an attachment) instead of a useless file path. Sharing directly to Mail also gets a sensible subject line.
0.34.0 2026-07-16
Added
Car documents. A car's Documents section now has an "Add Document" button for files that belong to the car itself — title, registration, insurance card — with the same scan/photo/PDF options as entry attachments. They upload to the car's Drive folder and no longer require inventing an entry to hang them on. (The Attachments tab gains a car_id column, added to existing sheets automatically.)
0.33.3 2026-07-12
Fixed
The Dashboard's chart tab now follows the fuel-economy unit setting — "MPG" becomes "km/L", "L/100 km", or "gal/100 mi" — and "Mileage" reads "Distance" for metric users.
0.33.2 2026-07-12
Fixed
No more red "Cancelled" banner. Interrupted requests (backgrounding the app mid-sync, abandoning a pull-to-refresh, dismissing the Google sign-in sheet) are cancellations, not failures — they're now silenced everywhere errors surface, and a cancelled sync retries automatically on the next foreground/reconnect.
0.33.1 2026-07-12
Changed
The spreadsheet is now named "Garage Buddy" (was "Car Log Data"). The app renames an existing sheet in place on next connect — the Drive id is unchanged, so nothing about your data or links breaks. Fresh installs also find the sheet under either name.
0.33.0 2026-07-12
Added
Units settings (#10): Settings → Units picks distance (miles/km), volume (gallons/liters), and fuel economy (MPG, km/L, L/100 km, gal/100 mi). Applies across entry forms (type in your unit — the sheet keeps storing miles/gallons unchanged), charts, car details, reminders, widgets, the PDF report, and scan plausibility checks. Defaults are mi/gal/MPG, so nothing changes until you switch.
All app data now lives in a "Garage Buddy" Drive folder (#15): new spreadsheets are created there, and attachments/reference photos go into per-car folders directly beneath it. Existing files keep working wherever they are (Drive tracks them by id); a short manual migration moves old files into the new folder.
0.32.1 2026-07-12
Changed
The exported PDF is named with the date ("Garage Buddy Report 2026-07-12.pdf"), so saved reports don't overwrite each other and sort chronologically.
0.32.0 2026-07-12
Changed
PDF report (#14): notes now render as grey text underneath their entry; the main table columns got the freed width and never wrap.
Dashboard (#16, #17, #18): the reminders card only shows IMMINENT reminders (overdue, or due within 30 days / 500 miles) and sits below the graph; the "Entries" tile is now "Due soon" (imminent-reminder count); Recent Activity shows the last 5 entries. The Reminders widget follows the same imminent-only rule.
Garage (#19): a car's impending-reminder bell can no longer be hidden by a data-quality warning — reminder and odometer/stale badges now show independently side by side.
Fixed
Demo/seeded mode showed "Not Connected" since 0.29.0 (it never set the data-loaded flag the views now gate on). DEBUG-only.
0.31.0 2026-07-12
Added
Unit tests (#9): a CarLogTests target with 48 tests across six suites covering the app's pure logic — mileage distribution (incl. the 0=unknown rule), per-fill MPG + moving average, tank-method MPG, service-interval anchoring (the 0.27.1 bug is now a permanent regression test), car matching, lenient sheet-date parsing, transaction dedup, OCR number extraction, the offline journal/cache round-trips, offline-error classification, ISO dates, and AI JSON extraction. The test suite runs in Xcode Cloud on every push — a failing test now blocks the TestFlight deploy.
Changed
Service-interval due-point math moved to Analytics (nextServiceDue); a few internal helpers exposed for testing. No behavior changes.
0.30.2 2026-07-12
Fixed
Attachments with slashes in their names open again (#12). Files named before the ISO-date switch (0.14.3) — e.g. "Photo 7/4/2026.jpg" — failed to preview with a "file doesn't exist" error, because the slashes read as folders in the local temp path. The name is now sanitized for the filesystem only; display names and Drive files are unchanged.
0.30.1 2026-07-12
Added
API keys are verified when you save them — a tiny live call runs against the provider and reports immediately: verified, rejected (bad paste/revoked), over quota (with an explanation of free-tier resets vs billing), or model-access problems. A "Verify Key" button re-checks a saved key any time. No more discovering key problems as cryptic scan failures.
0.30.0 2026-07-12
Fixed
API keys no longer "disappear." Two hardening fixes: key state is now read live from the Keychain (a launch-time cache could go stale when iOS prewarmed the app before first unlock, making every key look lost at once), and keys are stored with AfterFirstUnlock protection so prewarmed reads succeed. If your keys were showing as missing, they may reappear after this update; if the app was reinstalled, they need one re-paste (iOS wipes an app's Keychain on uninstall).
Changed
AI settings reorganized. The section is now just "AI" (it powers scanning AND text features like service intervals, not only photos):
The model list is the page's focus; the Provider picker only appears when a custom model id is selected.
The API Key section names the provider it edits and lists which providers have saved keys.
The test bench moved to its own "Test Models" screen.
0.29.2 2026-07-12
Added
Sync status chip on the Dashboard. An orange "Offline" capsule (with the queued-change count, e.g. "Offline · 2") appears in the nav bar when disconnected — tap it to retry — and a brief "Syncing" chip shows while queued changes drain after reconnecting. Nothing shows when everything is normal.
0.29.1 2026-07-12
Fixed
Offline launch actually shows your data now. GoogleSignIn's silent sign-in restore needs the network to refresh an expired token, so it fails in airplane mode — and 0.29.0 gated the offline cache behind that check, showing "Not Connected" instead of your garage. The cache now loads regardless (its presence proves ownership — signing out deletes it); auth is retried automatically when connectivity returns, then queued changes sync. Offline sessions also keep the cache updated and show the account name in Settings.
0.29.0 2026-07-12
Added
Offline support (ADR-016). The app now works without a connection:
Instant launch / offline viewing — the last-loaded data is cached on device and shown immediately at launch (even in airplane mode); the live Google sync refreshes in the background.
Offline logging — every change (entries, reminders, cars, deletes) goes into a persistent write-ahead journal and syncs automatically when connectivity returns (on reconnect, foreground, or launch). Queued changes survive app restarts and are always visible in the app.
Offline status — Settings shows an "Offline" state with the number of changes waiting to sync and a Try Again button; the rest of the app stays fully usable, including the quick-add button.
Attachments and reference photos still need a connection (they upload to Drive) and say so if it fails.
0.28.3 2026-07-12
Changed
Errors now show as a banner instead of an alert — a red banner slides in at the top, auto-dismisses after 5s (tap to dismiss sooner), and renders in its own overlay window so it appears above sheets too. This closes the last silent-failure hole: an alert can't present while a sheet is open, so a failed save or attachment upload during entry showed nothing.
0.28.2 2026-07-12
Fixed
MPG tab no longer goes blank on short ranges. The tank method needs two fills inside the window, so the 6-month default could empty the chart for seldom-driven cars — it now falls back to all-time averages (and says so), and an isolated car with no fills in range shows its full history instead of bouncing back to the fleet view.
Gemini 3.1 Pro scans no longer risk empty responses: the output-token cap was raised so reasoning models can think before emitting the JSON (unused headroom costs nothing).
A custom OpenAI-compatible base URL now survives switching providers — it's stored separately and restored when you come back.
AI calls retry on rate limits/transient errors (1s/2s backoff) — Test All's burst of requests was prone to spurious 429 failures.
Signing out clears cached reference photos, so a different account's scans can't match against the previous account's dashboards.
0.28.1 2026-07-12
Changed
One model list across all providers. The scanning list now shows every provider's models together (Claude, Gemini, GPT — plus On-device and a custom id); picking a model automatically selects its provider and uses that provider's saved key. Models whose provider has no key yet show an orange key badge. The Provider picker remains only for custom model ids and the generic endpoint's base URL.
0.28.0 2026-07-12
Added
Per-provider API keys. Each provider (Anthropic, Gemini, OpenAI-compatible) keeps its own key in the Keychain — switching providers never loses a key. Your existing key migrates automatically to the provider it was saved under.
Cross-provider Test All. The scan test bench now sweeps every model of every provider you've saved a key for (plus on-device) in one tap — e.g. Opus, Sonnet, Haiku, Gemini Pro, Gemini Flash, and on-device, all on the same photo.
0.27.2 2026-07-12
Changed
Faster scans. Reference photos are only uploaded when the car is still unknown (a scan with the car already picked skips them entirely); the main scan image is capped at 1600px (~40%% fewer upload bytes and vision tokens, digits still crisp); receipt pages are downsampled to 1800px for the upload (the stored PDF keeps full quality); the on-device OCR's two orientation passes now run in parallel. JPEG quality is unchanged — compression artifacts hurt digit reading.
0.27.1 2026-07-11
Fixed
AI service intervals now anchor to absolute odometer multiples (the way the owner's-manual table works: 30k/60k/90k…), not to current mileage + interval — which wrongly assumed every service had just been performed and set everything one full interval late. A 10k oil interval on a car at 80,282 mi now comes due at 90,000 (was 90,282); 60k plugs at 120,000 (was 140,282). The review sheet shows each item's computed due point, and the footer explains how to adjust when you know a service was done recently.
0.27.0 2026-07-11
Added
AI-suggested service intervals. On a car's page (Reminders section, when an AI key is set): "Suggest Service Intervals (AI)" asks your model for the manufacturer's routine schedule — oil, rotation, filters, fluids, plugs — and presents it for review with checkboxes. Selected items become repeating reminders: mileage intervals anchor to the current odometer, time intervals to today. Items whose name matches an existing reminder default to off, and every created reminder is noted "AI-suggested — verify against the owner's manual." EVs get EV-appropriate schedules (no oil changes).
0.26.2 2026-07-11
Fixed
On-device OCR no longer reads upside-down odometers backwards (80282 → 28208). Dashboard photos taken pointing down often carry a wrong gravity-based orientation from iOS, and rotationally-symmetric seven-segment digits (0/2/8) still "read" flipped. The on-device reader now OCRs the photo both ways and lets mileage plausibility pick the orientation (the rotated reading must win by a clear margin). The AI prompt also warns the model the photo may be inverted.
0.26.1 2026-07-11
Added
Test All in the scan test bench: one tap runs every model of the current provider plus on-device OCR sequentially on the same photo, with progress shown per engine. (Cross-provider sweeps would need a second API key — each provider's models share the one saved key.)
0.26.0 2026-07-11
Added
Scan test bench in Settings → AI. Choose a test photo once, then run the complete odometer pipeline — reference photos, seven-segment prompt, on-device cross-check — against the currently selected model. Results accumulate (model, reading, the digits it saw, car match, confidence, cross-check agreement, elapsed time), so you can switch models and re-run on the same photo to compare. Nothing is added to your log.
0.25.0 2026-07-11
Added
Google (Gemini) provider (#8). Settings → AI now offers Gemini via Google's OpenAI-compatible endpoint — Gemini 3.1 Pro (class-leading vision) and Gemini 3.5 Flash (fast/cheap). Paste a Gemini API key and pick a model.
On-device cross-check for AI odometer scans. Every AI scan also runs the free on-device OCR in parallel: if both read the same number the scan message says so; if they disagree it shows both readings and asks you to double-check — catching the single-flipped-digit errors segmented displays cause.
Changed
Scan prompts rebuilt for seven-segment displays (#8): the model is told the display type and its classic confusions (0/8, 5/6, 6/8), to transcribe digit by digit, to exclude boxed/decimal trailing tenths digits and trip meters, and to sanity-check digit count against the car's last known miles. Gas-pump scans get the same treatment plus explicit decimal-place guidance.
Requests now follow the documented best layout for vision models: images come before the instructions and every image is individually labeled (odometer photo, each reference photo, each receipt page).
0.24.1 2026-07-11
Changed
Dashboard charts now default to the 6-month range (was All). Swipe up on the chart or tap the range chip for longer windows.
0.24.0 2026-07-11
Added
Swipe the Dashboard chart (#6): left/right switches between Mileage / Expenses / MPG; up/down widens/narrows the time range (6M · 1Y · 2Y · All). A tappable range chip next to the subtitle shows the active window and cycles it as an alternative to swiping. The range also scopes the MPG views (fleet averages and per-fill charts).
Changed
All dates now display in ISO-8601 (#7): entry rows, car details, reminders (in-app and widget), notifications, and the PDF report show YYYY-MM-DD. Attachment filenames already did.
0.23.0 2026-07-11
Added
Per-fill-up MPG charts (#5). A car's fuel economy now shows every individual fill-up as a bar (miles since the previous fill ÷ gallons added) with a 5-fill moving-average trend line:
Dashboard → MPG: tap a car in the legend to drill from the fleet averages into its per-fill history; tap again to go back.
Car detail: a new "Fuel Economy" section shows the same chart.
Implausible readings (missed fills, typos over 150 MPG) are dropped from the chart rather than distorting it.
0.22.2 2026-07-08
Fixed
PDF report page breaks are clean. Sections were rendered as one tall image and sliced every page-height, cutting rows mid-text. The report is now paginated block-by-block (headers, charts, individual table rows), so a row that doesn't fit moves whole to the next page. Tables use fixed-width columns so rows stay aligned across pages, and table headers travel with their first row. Verified against a 21-page report with 47-row tables.
0.22.1 2026-07-08
Fixed
The Scan Odometer widget opens the camera again on a cold launch. The entry sheet appears before the Google sync delivers the cars; the auto-scan ran once, found no cars, and gave up silently. It now fires as soon as the fleet loads (and shows "Loading your garage…" instead of "Add a Car First" while syncing). Warm launches were unaffected — which is why the widget seemed to work until an app update forced a cold start.
Notes
Adds DEBUG-only launch args (-seedSlow, -quickActionScan) used to reproduce and verify the race in the simulator.
0.22.0 2026-07-08
Added
Fleet-wide Reminders on the Dashboard. Every open reminder across active cars, most urgent first — tap to open the car, long-press to mark done. The in-app counterpart of the Reminders widget.
Reference photos are now wired up. Add a dashboard/interior photo when editing a car (the reference_photo_id field finally has UI); AI odometer scans attach the fleet's reference photos so the model identifies the car visually, not just by mileage. Photos are cached locally for fast scans.
Fixed
Picking GPT-4o mini no longer silently reverts to GPT-4o on next launch (the legacy weak-model upgrade now runs exactly once).
Disconnecting clears attachments, reminders, and the home-screen widgets — no more previous-account data lingering after sign-out.
"Driven with you" and the PDF report now honour the "0 = unknown starting mileage" rule, matching the charts (a used car with unknown start no longer shows its whole odometer as driven).
Deleting an entry now also deletes its attachments (sheet rows + Drive files); cancelling a new entry cleans up attachments already uploaded.
Errors now surface on every tab, not just Settings — a failed save can no longer look like a success.
Reminders due today now fire their 9am notification (they were skipped).
Receipt OCR no longer overwrites a date you picked yourself.
Google API calls retry with backoff on rate limits/server errors.
Changed
Internal streamlining: one shared miles-driven/odometer implementation, one currency-code helper, vehicleDescription on Car, dead code removed.
0.21.1 2026-07-08
Changed
Compacted the small Fleet Snapshot widget — each stat is now a single line with a short label and auto-shrinking text, so all three fit without clipping. Medium keeps the larger stacked layout beside the sparkline.
0.21.0 2026-07-08
Added
Two data home-screen widgets (small + medium):
Upcoming Reminders — your next maintenance/renewal reminders across active cars, most urgent first, colour-coded by status.
Fleet Snapshot — active-car count, miles and spend this month, plus a six-month miles-per-month sparkline.
The app publishes a shared snapshot to an App Group container after every reload and data change, and refreshes the widgets automatically.
Notes
Requires the group.com.shuffman.carlog App Group on both the app and widget App IDs (one-time provisioning).
0.20.1 2026-07-08
Changed
Simplified AI settings. The separate on-device/AI engine toggle is gone — "On-device" is now just a Free option in the same list as the AI models. Pick a row and it sets everything; the Provider and API Key fields only appear when an AI model is selected.
0.20.0 2026-07-08
Added
AI model picker. Settings → AI now offers a menu of vision models with a rough per-scan cost, instead of typing a model identifier. For Anthropic: Opus 4.8 (~2–3¢, most accurate), Sonnet 5 (~1¢), Haiku 4.5 (~0.3¢). A "Custom model…" option still accepts any identifier.
Fixed
The Scan widget now actually deploys. 0.19.0's Cloud build failed because the widget extension's CFBundleVersion (1) didn't match the app's — the extension now tracks the app's MARKETING_VERSION/CURRENT_PROJECT_VERSION, so it passes App Store export and reaches TestFlight.
0.19.0 2026-07-08
Added
First Home Screen widget: "Scan Odometer" (small) — tap it to jump straight into mileage entry, via a garagebuddy://scan deep link. This is Slice 1 of the widget work: it stands up the widget extension target and proves the build/sign/ deploy path with minimal risk (no shared data yet).
Notes
Adds a WidgetKit app-extension (com.shuffman.carlog.widgets) embedded in the app, and a garagebuddy:// URL scheme. Data-backed widgets (reminders, fleet snapshot, miles sparkline) need a shared App Group and come in Slice 2.
0.18.0 2026-07-08
Fixed
Much better odometer/gas-pump/receipt OCR. Two root causes addressed:
Model: the default AI model was Haiku 4.5 — the weakest vision tier. Default is now Claude Opus 4.8 (high-resolution vision, far stronger OCR). Anyone still on the old Haiku default is auto-upgraded; you can still choose a cheaper model in Settings → AI.
Image handling: scan photos are now sent upright (baking in camera orientation so the model never gets a sideways image) and at higher JPEG quality, instead of a heavily-compressed frame.
Notes
Opus costs more per scan than Haiku, but a scan is one image + a tiny response (well under a cent). For best results, frame the odometer so it fills the shot.
0.17.0 2026-07-07
Added
New "Odometer Check" category for plain mileage readings — and it's now the default for new entries (the most common action).
Changed
Existing "Other" entries can be reclassified to Odometer Check in one tap: Settings → Data shows "Reclassify N ‘Other’ as Odometer Check" whenever any "Other" entries exist. (Opt-in, so it never silently changes data; the row disappears once none remain.)
0.16.0 2026-07-07
Changed
App name is now "Garage Buddy" (was briefly "Garage Manager"). Updated the Home Screen name and all in-app product references. App Store Connect listing is "Garage Buddy: Car Tracker" (the bare names were taken).
0.15.0 2026-07-07
Changed
Renamed the app to "Garage Manager." The Home Screen name (CFBundleDisplayName) and in-app product references (PDF report title, Settings privacy note, Siri/Shortcut descriptions, permission messages) now say Garage Manager. Siri phrases follow the display name automatically.
Notes
Bundle id, internal module name, and the Google Sheet/Drive names (Car Log Data, Car Log Attachments) are unchanged, so existing data and the TestFlight app lineage are preserved. The App Store Connect app name is managed separately in App Store Connect.
0.14.3 2026-07-05
Changed
Scanned/attached files are now named with an ISO-8601 date (Receipt 2026-06-21.pdf), using the date the work was performed — for receipts the OCR now runs before the file is named/uploaded, so the receipt's own date is used rather than today's.
0.14.2 2026-07-05
Changed
When several cars share the same canonical year/make/model, their attachment folders are disambiguated with a .1/.2/.3 suffix (stable by creation order), e.g. 2001 Porsche Carrera.1. A car with a unique name stays unsuffixed.
0.14.1 2026-07-05
Changed
Per-car attachment folders now use the canonical year/make/model name (e.g. 2001 Porsche Carrera) rather than the car's custom display name.
0.14.0 2026-07-05
Changed
Attachments are now organized per car in Drive. New uploads go into a subfolder named for the car, inside "Car Log Attachments" (e.g. Car Log Attachments/996 Carrera Cab/). Files are still located by id in the sheet, so this is purely for tidier browsing in Drive.
Notes
Existing attachments already uploaded stay in the top-level folder; only new uploads are foldered. (A one-time migration of existing files can be added if wanted.)
0.13.0 2026-07-05
Added
Long-press an attachment for a menu: Delete, and (on a transaction) Extract Details (OCR) to re-read an already-saved attachment — including PDFs added earlier — and pre-fill the entry. Attachments on the car's Documents list can be deleted the same way.
Changed
Attachment delete moved from a swipe action to the long-press menu.
0.12.1 2026-07-05
Fixed
Scanned PDFs were corrupt / wouldn't open. The PDF context was finalized in a defer that ran after the bytes were already read, so the file was missing its trailer. It's now finalized before the data is returned.
0.12.0 2026-07-05
Added
Scan multi-page receipts. "Scan Document" in a transaction's attachments uses the system document scanner to capture several pages, combined into a single PDF attachment (instead of loose images).
AI receipt reading. If an AI key is configured, a scanned receipt is read to pre-fill the entry's total, date, and category, with a brief summary written into the notes. Best-effort — you verify before saving; the PDF is attached either way.
Changed
AIVisionService now supports multi-image prompts (used for multi-page receipts) via a new scanReceipt.
0.11.0 2026-07-04
Added
Reminders per car. Each car has a Reminders section: add date-based renewals (registration, insurance, inspection) or mileage-based service (oil every N miles). Reminders can repeat, and "Done" rolls a repeating one forward to its next due date/mileage.
Local notifications for date reminders (morning-of). Permission is only requested once you actually have a date reminder to schedule. Mileage reminders are tracked in-app against the current odometer (mileage can't be time-scheduled).
Garage badges now include reminders: a red bell for an overdue reminder, orange for due-soon — prioritised alongside the existing odometer/stale flags.
New spreadsheet tab Reminders, auto-created on connect for existing spreadsheets.
0.10.0 2026-07-04
Added
Photo & PDF attachments on transactions. Add receipts, titles, and service records to an entry (take a photo, choose from library, or pick a PDF). Files upload to a "Car Log Attachments" folder in your Drive; tap any attachment to preview it (QuickLook).
Documents on the car screen. A car aggregates all attachments across its transactions into one Documents section.
New spreadsheet tab Attachments (attachment_id, txn_id, kind, file_id, name, created_at), created automatically on connect for existing spreadsheets.
Notes
Uses the existing drive.file scope (no new sign-in consent). Deleting an attachment also removes the underlying Drive file.
0.9.0 2026-07-04
Added
Cost per mile & spend breakdown on each car — operating cost per mile (excludes purchase price), total spent, and spend by category.
Data-quality guardrails. The Garage flags cars that need attention: an odometer rollback (a later reading lower than an earlier one) and a stale car (active but not logged in 30+ days), shown as a badge in the list and a banner on the car's screen.
Search & filter a car's history by text and by category.
Notes
Cost-per-mile and the guardrails honour the "0 = unknown starting mileage" rule when measuring miles driven.
0.8.1 2026-07-04
Added
Each car's page in the PDF report now includes its miles-per-month chart.
Removed
The redundant "Log Entry for This Car" button on a car's screen — the floating + button already defaults to the car you're viewing.
0.8.0 2026-07-04
Added
VIN, purchase price, and sale price on each car. Purchase price is entered in the car's Ownership section; VIN in the Vehicle section; sale price appears when a car is retired (sold). The car's detail screen and the PDF report show these, plus profit (sale − purchase) for sold cars.
New spreadsheet columns vin, purchase_price, sale_price. Existing spreadsheets are migrated automatically on connect: missing columns are appended without disturbing existing columns, order, or data.
Changed
Header handling is now non-destructive (ensureColumns appends only missing headers), replacing the previous full-row rewrite — safe to run on reordered or hand-edited sheets.
0.7.0 2026-07-04
Added
Current-month estimate on the mileage chart. Since miles only appear once a reading brackets them, the newest month always looked empty. The chart now projects each active car's likely miles since its last reading (at its average measured daily rate) so the current period isn't blank. These bars are rendered faded and labelled "estimates projected from recent use" so they're clearly not measured data. Applies to the dashboard and per-car charts; the PDF report shows measured data only.
Changed
A starting mileage of 0 is treated as "unknown." Previously a car with no entered starting mileage had its entire odometer counted as miles driven and smeared back from its acquired date (e.g. a used car showing tens of thousands of fabricated miles). Now mileage for such a car is anchored from its first real reading instead.
0.6.0 2026-07-04
Added
PDF export of the whole garage. An Export button on the Garage screen produces a multi-page report: a title page with the fleet overview and the miles-per-month chart, then one page per car listing its transactions. Long histories paginate automatically.
Per-car mileage chart. Each car's Garage detail now shows a miles-per-month chart for just that vehicle.
Quick-add remembers the car you're viewing. Opening the quick-add (+) while looking at a car in the Garage pre-selects that car, and treats it as your explicit choice so a photo scan won't reassign it.
Fixed
Duplicate transactions from a double-tap. Saving a new entry now reuses one stable id and ignores repeat taps while the save is in flight, so a fast double-tap updates one record instead of writing two rows.
Defensive de-duplication on load. Rows identical in every meaningful field (car, date, odometer, category, amounts, notes) are collapsed to one when reading the sheet, cleaning up any duplicates already written. Genuinely distinct entries (e.g. two fills the same day) are preserved.
0.5.0 2026-07-04
Added
Tappable chart legend. The mileage and expenses charts now have a custom interactive legend — tap a car to isolate it in the chart, tap again to clear the filter and show all cars. Car colors stay stable, and the legend scrolls horizontally to fit many cars.
Fixed
Chart bars no longer collapse into a single month. Transaction dates that Google Sheets stored as date-time cells came back space-separated (e.g. 2026-01-15 8:00:00, 1/15/2026 8:00:00) and weren't recognized, so each fell back to its row's creation time and piled onto one recent bar. The lenient date reader now understands these space-separated date-time formats, so each entry keeps its real date. (Fixes #1.)
0.4.1 2026-06-28
Fixed
Hand-edited dates no longer jump to "today." Reading a date from the sheet was strict RFC3339 only, so any other format silently fell back to the current date — quietly relocating a transaction (and its mileage/expense bars) onto the current month. Date cells are now parsed leniently: RFC3339 (with or without fractional seconds), plain calendar dates (2026-06-28, 2026/06/28, 6/28/2026), and Google Sheets' own serial date numbers are all accepted. A transaction whose date still can't be read falls back to when its row was created rather than today. (ARCHITECTURE.md ADR-009.)
0.4.0 2026-06-28
Changed
Spreadsheet columns are now matched by header name, not position. You can reorder the columns in the Cars/Transactions tabs, and any extra columns you add are ignored on read and preserved when the app updates a row. The contract is now "don't rename/delete the known headers" instead of "don't reorder." (ARCHITECTURE.md ADR-009.)
0.3.1 2026-06-28
Fixed
On-device odometer OCR accuracy — the biggest fix: camera photos were handed to Vision without their rotation, so portrait shots were read sideways. Vision now receives the correct image orientation.
Thousands separators no longer fragment a reading (e.g. 128,456 was split into 128 and 456); grouped digits are kept intact.
Changed
When several numbers are equally plausible, the larger (more prominent) digits on screen now win — usually the odometer rather than the clock or trip meter.
0.3.0 2026-06-28
Added
Landscape chart mode — rotate the phone on the Dashboard for a focused, full-width chart with room for many months.
Fixed
Chart x-axis crowding — month labels now thin out as the range grows (every 2nd/3rd/6th month) instead of overlapping, and show the year when the range spans more than one calendar year.
0.2.0 2026-06-28
Added
Fast entry: Home Screen Quick Actions (long-press the app icon) plus App Shortcuts surfaced in Siri, Spotlight, the Shortcuts app, and the Action Button — "Scan Odometer" (opens straight to the camera) and "Log Entry".
On-device odometer OCR using Apple's Vision framework — free, private, offline, and works with no API key. A Settings toggle chooses on-device vs AI.
Closest-mileage car guessing: a scanned reading is matched to the car whose last mileage is nearest (and the right number is picked from the photo).
Changed
Mileage is distributed over time: miles between two readings spread across the intervening months (proportional to time), and the first reading spreads from the car's acquired date — so usage is never lumped into a single month.
Charts: abbreviated month labels (e.g. "Jun") and a rolling ~6-month x-axis window so sparse data still reads clearly.
0.1.0 2026-06-28
Added
Car management (the Garage): add/edit, retire/reactivate, starting mileage for used cars; scales to 10+ cars.