Updates
version: "1.3.0" date: "2026-09-15" title: "Standalone Native Android Application Launch" summary: "ExpenseReports is now available as a fully standalone native Android application with local Room SQLite persistence, on-device ML Kit document scanning, offline OCR auto-fill, native PDF report rendering, ERX archive packaging, and system share sheet integration." tags: [android, native, mobile, ocr, scanner, offline, erx, privacy]
Highlights
- Standalone Native Android Client: Built with Kotlin and Jetpack Compose (
apps/android), featuring a local-first Room SQLite database and zero server persistence. - On-Device Document Scanner & OCR: Integrated Google ML Kit Document Scanner with automatic fallback to native photo and PDF pickers. Receipt text recognition extracts merchant name, date, and expense total entirely on-device without cloud upload.
- Offline City & Trip Detection: Bundled offline US cities dataset with Haversine distance matching (150 km) and automatic 3-day active trip window detection.
- Executive PDF & ERX Export: Native
android.graphics.pdfexecutive summary rendering with embedded receipt image/PDF pages, plus structured.erxZIP package generation conforming to the ERX 1.0 specification. - System Share Sheet Integration: Native
FileProvidersharing of PDF reports and ERX packages to any destination app, and inboundACTION_SENDintent filters to accept receipts shared from other apps. - Google Account Backup & Recovery: Bidirectional cross-platform
.erbackuparchive compatibility with web PWA. Includes hidden Google DriveappDataFoldertransport, pre-upload validation, publication verification, 3-snapshot retention rotation, and atomic transaction restore.
Key Capabilities
- Local Storage & Trash Store: Enforces 25 MB max receipt size, image and PDF format validation, cryptographic SHA-256 digests, and soft-delete trash recovery with permanent purge.
- Substantiation Tracking: Live
Substantiation Missingwarning status badges on expenses until receipt evidence is attached. - Canonical Schema Conformance: 100% compliant with
/contracts/schemas/expense.schema.jsonandreport.schema.json.
Platform Availability
- The Progressive Web App (
apps/web) remains fully supported with identical business logic, canonical schemas, and zero behavior changes. - Android APK builds are available via the
apps/androidGradle project and automated GitHub Actions CI pipeline.
version: "1.2.0" date: "2026-05-29" title: "Restore image file sharing into the PWA" summary: "ExpenseReports.app now accepts native mobile share-target uploads so receipt images can be sent directly into the installed app flow." tags: [pwa, sharing, mobile, offline]
Highlights
- Restored native mobile image sharing into the installed ExpenseReports.app PWA.
- Added service-worker share-target intake for multipart
POSTuploads. - Shared files now route directly into the existing receipt preview and save flow.
Improvements
- Updated share-target manifest metadata to support file-based handoff.
- Added startup ingestion for pending shared files cached by the service worker.
- Added end-to-end coverage for share-target intake and preview behavior.
Notes
- Share-target import is designed for installed/offline-capable app usage.
- Existing manual camera/file receipt intake continues to work unchanged.
Historical notice: Version 1.1.0 introduced Gmail receipt import. That integration was later retired to reduce Google-account access and simplify the product's privacy and authorization surface. Gmail import is no longer an available feature. Existing expense records and evidence that users previously imported remain local user data.
What shipped in this historical release
- Gmail receipt search and import
- Schema.org JSON-LD extraction from selected messages
- Proof-of-expense snapshots
- Links back to source messages
For current receipt intake, use supported local image/PDF upload, capture, or native share-target flows.
Highlights
- Google Drive backup is now available so your receipts and reports can sync to your own account.
Read More
Updated: 2026-08-22
ExpenseReports.app provides deliberate, versioned backup snapshots in your Google Drive application-data area (appDataFolder).
What the backup contains
A verified .erbackup snapshot includes supported app records, report metadata, receipt images, receipt PDFs, attachments, and recovery metadata—not merely the report currently open.
What Google can access
The app asks only for Google's Drive application-data permission (https://www.googleapis.com/auth/drive.appdata). That permission does not let ExpenseReports.app browse, view, or modify your ordinary visible Drive files. Snapshots are stored in a hidden application data folder accessible only to this application.
Google, as the storage provider, stores the application data under Google's terms and privacy policies.
Account-authorized recovery
Restoring data on another device or after clearing your browser requires connecting to the same Google account. You do not need to manage or remember a separate application recovery key.
Backup is separate from sharing
Backup snapshots in the hidden appDataFolder are internal recovery files and cannot be browsed as normal Drive files. When you want a readable report in Drive, choose Google Drive in your device's native share sheet. The operating system or Drive app uploads the locally generated PDF without granting ExpenseReports.app broader Drive access.
slug: pwa-share-target-image-intake title: "Send receipts straight from your phone: PWA share-target image intake is back" date: "2026-05-29" summary: "How ExpenseReports.app restored native image sharing into the installed PWA with service-worker POST intake and startup handoff into receipt preview." tags: [blog, pwa, sharing, mobile]
Mobile receipt capture should be as simple as: take a photo, tap Share, and send it into your expense app.
This update restores exactly that flow in ExpenseReports.app. You can now share receipt images from your phone directly into the installed PWA, and the app opens with that receipt ready for review.
What changed
We rebuilt share-target intake around the service worker so the app can reliably accept native file shares from OS share sheets.
The flow now works like this:
- The OS sends a multipart
POSTshare-target request. - The service worker validates and caches the incoming file payload.
- The app is reopened with a pending share reference.
- Startup logic consumes that cached payload and routes it into receipt preview.
This keeps the handoff resilient for installed/offline-capable contexts where direct page-level handling is less reliable.
Why this matters
For people documenting expenses on mobile, every extra step adds friction. With this restore, you can move from camera roll to expense entry in one handoff instead of manually reopening the app and selecting files again.
It also strengthens reliability for PWA-native behavior by letting the service worker own intake and handoff responsibilities.
Quality and coverage
Alongside the implementation, we added Playwright coverage for the share-target POST path and preview handoff so this flow is protected against regression.
What’s next
We’ll continue improving PWA-native intake paths so sharing, previewing, and saving receipts stays fast and predictable across mobile platforms.
When you attach a receipt, Expense Reports can automatically choose the best existing report for you.
This is powered by a threshold-based matching algorithm that balances two things:
- Distance: Is this expense near a report's location?
- Recency: Is that report still active enough to reuse?
How report auto-selection works
1) Infer likely cities near your current position
If location is enabled, we find nearby cities from your coordinates and rank them by distance.
2) Evaluate reusable reports with thresholds
For each candidate city, we search for reports that match either an exact city/state/country or geographic proximity within the configured radius (currently 150 km). We then filter candidates by activity recency (currently three days), using the latest expense/report timestamp.
3) Pick the best match
From valid candidates, we select the most recently active report. If no report passes both thresholds, the app defaults to a new report and prefills location when available.
Why this is valuable
- Fewer taps: Most expenses land in the right report automatically.
- Cleaner grouping: Nearby expenses stay together even when city names differ slightly.
- Better continuity: Recent active reports are reused; older reports are not.
- Consistent behavior: The same recommendation logic applies across supported receipt-capture flows.
The recommendation remains editable by the user.
Why Location Matters for Expense Reports
When you capture a receipt, you're recording a moment and a place. Expense Reports automatically detects your location—with your permission—to pre-fill the city where your expense occurred. This saves you typing and ensures consistent, reliable reporting.
But getting it right matters. We use a well-established algorithm and a robust dataset to identify not just any city, but the three nearest cities to your actual location, ranked by distance.
The User's Choice: Opt-In Location
Location access is opt-in only. When you first open the app, we ask:
"Use my location for nearby cities"
If you say yes, the browser's native Geolocation API reads your approximate coordinates (accurate to ~100 meters in most cases). If you say no or don't answer, you can still type any city manually. Either way, your location never leaves your device—we don't store it.
This approach respects your privacy while unlocking real convenience.
Haversine Distance: The Algorithm
Once we have your coordinates, we use the haversine formula—a method from spherical trigonometry—to calculate the great-circle distance between your position and every known US city.
The haversine formula is ideal because it:
- Accounts for Earth's curvature (not euclidean distance)
- Is numerically stable at all distances
- Runs instantly on mobile hardware
- Is well-tested in navigation and GIS systems
The math:
a = sin²(Δφ/2) + cos(φ1) × cos(φ2) × sin²(Δλ/2)
c = 2 × atan2(√a, √(1−a))
distance = R × c
Where φ is latitude, λ is longitude, and R is Earth's radius (6,371 km).
For your location, we compute distance to all 15,000+ US cities and sort by nearest. The result: three cities appear as suggestions, ordered closest to farthest.
Census Data: Quality & Coverage
The city dataset comes from the US Census Bureau's 2025 Gazetteer:
- 15,000+ cities included
- Population filter: ≥ 5,000 residents (reduces noise, focuses on real towns)
- Coordinates: Precise to ~100 meters (Census-measured)
- Updated annually by the Census Bureau
- Includes all 50 states plus Puerto Rico
We download this data once and store it client-side. It never changes during your session, ensuring fast, predictable queries.
Browser Technologies at Work
Three web APIs power this feature:
1. Geolocation API
navigator.geolocation.getCurrentPosition(
position => {
const { latitude, longitude } = position.coords;
},
error => console.warn('Permission denied')
);
Built into all modern browsers; prompts the user for permission.
2. IndexedDB
The 15,000-city dataset is stored in IndexedDB, the browser's native database. This means:
- Cities load instantly (no server round-trip)
- Works offline
- Survives app restarts
- ~2 MB of storage (negligible)
3. Lazy Loading
The city dataset is only loaded when needed—either when you enable location or manually search for a city. This keeps the app's first load fast.
The User Experience
Here's what happens when you add a receipt:
- You grant location permission (first time only)
- Browser reads your coordinates silently in the background
- Within 300ms, the app computes distances to all 15,000 cities
- Three nearest cities appear as buttons below the form
- One click selects a city; a new report is created with that location
- If no recent report matches that city, the app auto-creates a new one for you
No typing. No menu hunting. One tap.
And if you're offline, or deny location, the manual search still works—start typing "San Fran" and the app finds it instantly from the cached dataset.
Performance & Efficiency
On a typical phone:
- Distance calculation: ~5ms for 15,000 cities
- Sorting: ~2ms
- Total latency: < 20ms
- No network calls (all client-side)
The haversine algorithm is so efficient that even calculating distance to all cities is faster than a typical network request.
Privacy, First
- Your location is read by your device only
- Never sent to our servers
- Deleted at end of session
- Opt-in and revocable at any time
- Reports to other services (cloud sync) use the city name, never coordinates
What's Next
As we expand to other countries, we'll integrate similar government datasets (Statistics Canada, UK ONS, etc.) and apply the same haversine logic. The algorithm scales globally.
For now, Expense Reports finds your three nearest US cities, every time, in under 20 milliseconds—powered by math, browser APIs, and respect for your privacy.
Have feedback on location matching? Send us a note.