Intelli-Expense · Design specification · iOS 26 · July 2026

The exact UI, screen by screen

High-fidelity mockups for every screen in the PRD, grounded in Apple's Liquid Glass adoption guidance and the offline Apple API docset — with two options where the design decision is genuinely open, and a recommendation for each.

First principles

What makes this world-class — before a single pixel

Flighty's Apple Design Award philosophy is the bar: "We want it to work so well that it feels almost boringly obvious." World-class iOS design in 2026 is not decoration — it is six disciplines applied without exception. Every screen below is judged against these.

1 · Borrow a proven visual language

Flighty borrowed 50 years of airport departure-board conventions. Intelli-Expense borrows the two artifacts its users already trust: the paper receipt (the scan is always visible, always primary) and the finance ledger (tabular numerals, per-currency subtotals, date-grouped rows). Nothing needs explaining because it looks like what it replaces.

Source: Apple "Behind the Design: Flighty" (developer.apple.com/news)
2 · One hero number per screen

Expense apps drown users in numbers. Each screen gets exactly one large, light-weight money figure (the group total, the extracted total); everything else steps down through weight and color, not size. Calm hierarchy is what separates Flighty-class apps from dashboards.

Source: swiftui-design-principles skill — weight-based hierarchy, ≤5 type sizes
3 · Content first, chrome as glass

iOS 26's Liquid Glass exists to "bring focus to the underlying content." So: no custom bar backgrounds, floating system tab bar that minimizes on scroll, scroll-edge effects instead of opaque headers, and glass reserved for the few controls that deserve it — never decorative.

Source: docset /documentation/technologyoverviews/adopting-liquid-glass
4 · Uncertainty is a choice, never an error

The extraction pipeline's honesty is the product's soul. When the model is torn, the UI offers two tappable readings with a calm "which is correct?" — no red, no alarm, no modal. The user is the confirmer, not the debugger. This turns the scariest part of AI UX (being wrong) into the most delightful (being asked).

PRD §5.4, §6.3 — ambiguity UX as a first-class requirement
5 · The core loop is sacred: <10 seconds, 0–2 taps of correction

Capture → review → save is the entire app; everything else is furniture. The capture affordance is reachable in one thumb-tap from anywhere, processing shows the receipt (not a spinner), and Save is one prominent action. Speed is a design feature: p50 < 5s is in the PRD as a UX number, not a perf number.

PRD §5.4, §9 — invest UX in the heart of the app
6 · Every state is designed, including the bad ones

Empty states teach the next action. The Apple Intelligence gate explains and self-dismisses. Model-downloading degrades to deterministic extraction with a one-line note. Nothing dead-ends, nothing shows a raw error, and no failure ever loses a captured image.

PRD §5.0, §5.5, §6.4 — designed failure states are must-haves
Foundation

Design system — few tokens, used everywhere

Restraint is the system: SF Pro only, semantic system colors plus one brand accent, a base-4 spacing grid, and five fixed expense-type identities (SF Symbol + accent color) reused identically in review, lists, totals and CSV — exactly as the PRD's §4.5 demands.

Ledger GreenBrand accent · Save, selection, active tab. Calm, money-adjacent, not default-blue.
Foodfork.knife · systemOrange
Hotelbed.double.fill · systemIndigo
Flightairplane · systemBlue
Taxi / Transportcar.fill · systemTeal
Othertag.fill · systemGray
TokenRuleWhy (grounded)
TypeSF Pro. Five roles only: LargeTitle 34 bold · hero money ~31 light with tabular numerals · body 17/15 · footnote 13 · caption 11 semibold uppercase. Dynamic Type styles, never fixed sizes in code.Weight-based hierarchy; money in light weight reads elegant, not shouty. Tabular numerals so amounts align in every list.
ColorSemantic system colors everywhere (systemGroupedBackground, secondaryLabel, separator). One brand accent. Type colors only ever mean expense type.Free dark mode + accessibility contrast; Liquid Glass adapts semantic colors automatically. Color that always means the same thing becomes navigation.
Spacing4/8 grid: 16pt content margins, 12–16pt card padding, 24pt between sections.iOS 26 grouped lists gained taller rows and bigger radii — standard components inherit the new metrics for free.
ShapeSystem-provided radii; custom cards use concentric corners. Never hand-tuned per view."The shape of the hardware informs the curvature of controls" — concentric shapes are an explicit iOS 26 guideline.
GlassSystem bars/sheets only, plus exactly two custom uses: the capture accessory and the floating Save bar.Docset: "Avoid overusing Liquid Glass effects… limit to the most important functional elements."
Haptics.success on save, .selection on chip picks, .warning only for duplicate notice.PRD §5.5; confirmation you can feel closes the loop faster than any toast.
Foundation · Dark mode

Dark mode — the same system, re-lit

Dark mode costs nothing where the system owns color: semantic backgrounds, labels, separators and Liquid Glass adapt automatically. Only the custom tokens need explicit dark variants — each defined once as an asset-catalog color set with Any + Dark appearances. Code references the named color; it never branches on colorScheme.

TokenLightDarkRule
Ledger Green#1E7A55#34C08ABrightens the way Apple's system tints do — deep pigment on white, luminous on black. Still the only brand accent: Save, selection, active tab, links.
Ledger Green Soft#E7F2EDrgba(52,192,138,.16)Selected-chip and icon-tile fill becomes a translucent green wash so it sits on any dark surface.
Food#FF9500#FF9F0AThe four vivid type colors follow Apple's documented system-color dark variants (systemOrange, systemIndigo, systemBlue; taxi's custom teal brightens in step with systemTeal). Identity is unchanged — same symbol, same meaning, slightly more luminous.
Hotel#5856D6#5E5CE6
Flight#007AFF#0A84FF
Taxi / Transport#2AA8BD#3FC2D9
Other#8E8E93#8E8E93systemGray is identical in both appearances — no variant needed.
Attention edge#FF9500#FF9F0AThe 2.5pt warm edge tint on uncertain fields tracks the Food/systemOrange pair.
Duplicate banner#FFF8EC / #8A4D05#2B2013 / #F0B35ESame warm, non-alarming voice: dim amber surface, luminous amber text. Still never red.
Everything elsesemantic system colorssystemGroupedBackground → true black canvas, secondarySystemGroupedBackground#1C1C1E cards, labels/separators/fills adapt for free. Hardcoding any of these is a defect.
9:41
Berlin — June 2026
Jun 12–18 · 14 receipts
€1,284.50
+ US$62.00
Zürich — March 2026
Mar 3–6 · 8 receipts
CHF 942.00
Unfiled
Unfiled receipts
2 receipts · not in any group
€31.90
Scan ReceiptBerlin — June 2026
Groups
Receipts
Settings
Groups home, dark. True-black canvas, #1C1C1E cards, brightened Ledger Green on the capture bar and active tab. Same layout, zero code branches.
9:41
REWE CITY
SUMME€84,50
Tap to zoom
Vendor
REWE City
Date
June 14, 2026
Total amount
€84.50bottom line “SUMME”
€8.45line above
Edit
Two possible totals found — which is correct?
Currency
EUR — €
Payment · required
Card
Cash
Group
Berlin — June 2026
Save Receipt
Discard
Review & Confirm, dark. The paper stays lit — it is content, not chrome. Choice chips, checkmarks and the warm attention edge all read at the brightened token values.

The rules

  • Dark mode is a token problem, not a layout problem. Nothing moves, resizes or reflows. If a screen needs different layout in dark mode, the light design was wrong.
  • Semantic first: backgrounds, labels, separators, fills and all system glass come from semantic colors and adapt automatically. The custom palette is exactly nine values, paired in the table above — every one an asset-catalog color set (Any + Dark), referenced by name in code. colorScheme branches and raw hex are both defects.
  • The receipt image is never dimmed or inverted. The paper is the content and the trust anchor — it renders identically in both appearances, framed by the dark canvas. (The processing screen was designed dark-first for exactly this reason.)
  • Elevation reads through surface, not shadow: true-black grouped canvas, #1C1C1E cards, #2C2C2E tertiary fills. Shadows stay system-default; glass gets its depth from the material, not from heavier borders.
  • Contrast is re-verified in dark, including the amber attention treatments on #1C1C1E and white-on-Ledger-Green in its dark variant. Test at Increased Contrast and with Reduce Transparency, which replaces glass with solid surfaces.

Build it with

  • One asset-catalog color set per custom token (LedgerGreen, ExpenseFood…), each with Any + Dark appearances; SwiftUI Color("LedgerGreen") / generated symbols. No @Environment(\.colorScheme) in feature code.
  • Semantic colors via SwiftUI defaults (.background, .secondary, list styles) — grouped lists, bars and sheets are already correct.
  • Verify with the Xcode appearance switcher on every screen in design/ui-spec.html; acceptance #10 (fully usable in dark mode) covers this.
Screen group 1

Onboarding & the Apple Intelligence gates

Onboarding does two jobs — say what the app does, get Apple Intelligence on — then gets out of the way. One welcome screen, one conditional gate. Most users never see the gate at all.

9:41
Intelli-Expense
Scan a receipt. Confirm the details.
Export the trip.
Capture in secondsPoint the camera at any paper receipt — or import a photo or PDF.
Confirm at a glanceOn-device intelligence reads the vendor, date and total. You approve every field.
Export the tripOne zip per trip — dated receipt images and a summary.csv your finance team loves.
Everything happens on your device. Your data syncs only to your private iCloud.
Continue
Welcome — shown once. Value line, the core loop in three rows, privacy footnote, one button.
9:41
Turn on Apple Intelligence
Intelli-Expense reads receipts entirely on your iPhone using Apple Intelligence. It's switched off right now.
Settings → Apple Intelligence & Siri
then turn on Apple Intelligence. This screen dismisses automatically when it's on.
Open Settings
Checks again each time you return
Gate — not enabled. Exact settings path, one action, auto-dismisses on foreground re-check. No dead end.
9:41
Getting ready…
Apple Intelligence is downloading its on-device model. You can start scanning now — smart extraction switches on automatically when it's ready.
Get Started
Gate — model downloading. Non-blocking: the user enters the app; the pipeline runs deterministically until ready.

Why it's world-class

  • Two screens, zero friction. No feature tour, no permission pre-prompts (camera is requested just-in-time at first scan, per platform convention). The welcome screen's three rows are the mental model of the whole app.
  • The gate treats availability honestly. Blocking only for genuinely blocking states (.appleIntelligenceNotEnabled, .deviceNotEligible); .modelNotReady lets the user in immediately — hard-blocking a person standing at a cash register would lose the receipt.
  • Auto-dismissal is the delight. Turn on Apple Intelligence, flip back, and the gate melts away without a relaunch — the app is watching scenePhase and re-checking SystemLanguageModel.default.availability.
  • Copy discipline: value in one line, privacy in one line, every button says exactly what it does ("Open Settings", not "Learn more").

Build it with

  • SystemLanguageModel.default.availability branching per PRD §5.0; re-check on scenePhase == .active.
  • Open Settings via public UIApplication.openSettingsURLString only — no private App-prefs: URLs (App Review risk, PRD §5.0).
  • "Welcome seen" in @AppStorage, never in CloudKit.
Screen group 2 · Two options

App structure, Groups home & the Add affordance

Three tabs (Groups · Receipts · Settings) is settled by the PRD. The open decision is the "prominent Add affordance." iOS 26 gives us a new, honest answer: the tab bar's bottom accessory. Both options below show the same Groups home screen.

9:41
Berlin — June 2026
Jun 12–18 · 14 receipts
€1,284.50
+ US$62.00
Zürich — March 2026
Mar 3–6 · 8 receipts
CHF 942.00
Client visit — Pune
Apr 21–23 · 5 receipts
₹18,450.00
Unfiled
Unfiled receipts
2 receipts · not in any group
€31.90
Scan ReceiptBerlin — June 2026
Groups
Receipts
Settings
Option A · Capture bar (tab-bar bottom accessory)
A persistent Liquid Glass capture bar rides above the tab bar — tap to scan into the shown group, for photo / file / manual.
9:41
Scan Document
Choose Photo
Import File
Enter Manually
Berlin — June 2026
Jun 12–18 · 14 receipts
€1,284.50
+ US$62.00
Zürich — March 2026
Mar 3–6 · 8 receipts
CHF 942.00
Client visit — Pune
Apr 21–23 · 5 receipts
₹18,450.00
Unfiled
Unfiled receipts
2 receipts
€31.90
Groups
Receipts
Settings
Option B · Toolbar “+” with capture menu
The PRD's fallback: a nav-bar + on Tabs 1 & 2 opening the four capture modes as a menu that morphs from the button.
9:41
Start your first trip
Make a group like “Berlin — June 2026”, then scan your first receipt into it.
New Group
or scan a receipt now — file it later
Scan ReceiptFiles to Unfiled until you choose
Groups
Receipts
Settings
Empty state (first run)
The empty state carries the teaching load after onboarding — one primary action, one escape hatch. Never a blank list.

The row anatomy (both options)

  • Name → date range → count in descending weight; the per-currency total right-aligned in tabular numerals with the secondary currency beneath — never summed across currencies (PRD §7).
  • Tiny type dots preview the trip's expense mix without numbers — glanceable, not a chart (charts are a PRD non-goal).
  • Delete asks "keep receipts?" → default moves them to Unfiled, which appears as a pseudo-section only when needed.

Why Option A wins

  • It's the honest iOS 26 pattern for a single dominant action. tabViewBottomAccessory(content:) (iOS 26.0+, per the docset) floats the accessory above the tab bar and collapses inline with it when the bar minimizes on scroll — capture stays one thumb-tap away on both browsing tabs without stealing a tab slot or hiding in a corner.
  • It's context-aware. The subtitle shows where the receipt will land ("Berlin — June 2026" when that group is open, "Unfiled" otherwise) — the group-defaulting logic of PRD §5.4 made visible before capture, not after.
  • Camera-first, menu-second. Tapping the bar goes straight to the document camera (the 90% case at a cash register); the ⋯ opens Choose Photo / Import File / Enter Manually. Long-press on the bar offers the same menu.
  • Honest tradeoff: Apple's canonical accessory is status-like (Music's mini-player). Using it as an action bar is a deliberate, defensible stretch — it's still a system component with system behavior. Hide it on Settings via tab-selection state; if App Review or usability testing pushes back, Option B is a one-day swap.
Recommendation: Option A. The capture bar makes the app's one job physically present on every browsing screen — the "prominent add affordance" of PRD §5 in native iOS 26 material. Keep Option B's menu as the ⋯ / long-press content, so both options share one implementation.
Screen group 3

Group detail — the trip ledger

Where a trip becomes a record: one hero total per currency, the per-type breakdown as quiet chips, then receipts by date. Export lives in the toolbar, exactly where iOS puts share actions.

9:41
€1,284.50 + US$62.00
14 receipts · Jun 12–18, 2026
€412.106 food
€540.003 hotel
€210.002 flight
€98.402 taxi
All
Food
Hotel
Flight
Card
Saturday, June 14
Lufthansa
Flight · Card
€420.00
Hotel Adlon
Hotel · Card · 2 pages
€180.00
REWE City
Food · Cash
€84.50
Friday, June 13
Taxi Berlin
Taxi · Cash
€23.90
Scan ReceiptBerlin — June 2026
Groups
Receipts
Settings
Group detail. Hero total, per-type chips, filter row, receipts by issue date. Capture bar now targets this group.

Why it's world-class

  • The ledger convention: date section headers ("Saturday, June 14"), tabular amounts on a right edge, thumbnails of the actual paper. It reads like the expense report the user will eventually submit — the export is a zero-surprise projection of this screen.
  • One hero number. €1,284.50 in light weight; the second currency deliberately secondaryLabel-small. No pie charts, no progress rings — PRD non-goals kept out.
  • Breakdown chips are also filters-in-waiting: tapping a chip in the header row scrolls into the filtered state (same components as the filter row).
  • Capture bar context switch: inside a group, its subtitle names the group — capture-to-group without a picker (PRD §5.4 default-group rule).
  • Filters compose (type × payment) and are visible as chips — state you can see, not a buried filter sheet. Empty-filter result gets its own designed state.

Build it with

  • List + sections keyed by receipt date; header card as a plain section header view (no custom bar backgrounds — glass stays system-owned).
  • Export = ToolbarItem with square.and.arrow.up → §8 zip flow. Toolbar icons only, labeled for VoiceOver (docset toolbars guidance).
  • Totals are computed properties on ExpenseGroup (PRD §4.1), formatted with Decimal.FormatStyle.Currency per receipt currency.
Screen group 4

Capture → Processing — the wait that doesn't feel like waiting

VisionKit's document camera is system UI — we design what happens the instant the shutter closes. The receipt itself is the loading indicator: the user watches their capture being read, never a spinner.

9:41
REWE CITY
SUMME€84,50
Understanding receipt…
Reading text · Finding the total
Cancel
Processing. Scan-line sweep over the actual capture, honest stage hints, always cancellable.

Why it's world-class

  • The image appears instantly — persisted to SwiftData the moment it exists (PRD §9: a crash after this point can never lose the capture). The scan-line treatment says "being read," which is literally true: OCR → parse → Foundation Models.
  • Stage hints are sequential truth, not theater: "Reading text…" (Vision) → "Understanding receipt…" (Foundation Models). If the model times out (~10s) or the language is unsupported, the flow proceeds to review with deterministic values and a one-line note — the user never learns the word "error."
  • Cancel is a promise: it jumps straight to review with whatever exists — even just the image and empty fields. The receipt is captured; nothing else matters.
  • Reduced-motion: scan line becomes a static shimmer; stage text carries the progress story alone.

Build it with

  • VNDocumentCameraViewController wrapped in UIViewControllerRepresentable; multi-page → one ReceiptAttachment per page.
  • Scan line: TimelineView + linear gradient mask, gated on accessibilityReduceMotion.
  • Target p50 <5s: run OCR while the camera sheet dismisses; the processing screen often only lives ~2–3s.
Screen group 5 · The heart · Two options

Review & Confirm — where trust is won

The PRD calls this screen the explicit priority. Both options obey the same laws: the paper stays visible, ambiguity is two tappable chips, required fields (type + payment) are structurally unmissable, and Save is the single hero action. They differ in what comes first: the document order or the user's attention.

9:41
REWE CITY
SUMME€84,50
Tap to zoom
Vendor
REWE City
Date
June 14, 2026
Total amount
€84.50bottom line “SUMME”
€8.45line above
Edit
Two possible totals found — which is correct?
Currency
EUR — €
Expense type · required
Food
Hotel
Flight
Taxi
Other
Payment · required
Card
Cash
Group
Berlin — June 2026
Save Receipt
Discard
Option A · Anchored form (document order)
Fields in the PRD's fixed order. Confirmed fields get quiet checkmarks; the one uncertain field gets chips inline, right where the field lives.
9:41
REWE City
June 14, 2026 · EUR · Food
4 read
Needs a look · 2
Total amount
€84.50“SUMME” line
€8.45line above
Edit
Payment method
Card
Cash
“BAR” found on receipt — cash suggested
Looks right — tap to change
Vendor
REWE City
Date
June 14, 2026
Expense type
Food
Group
Berlin — June 2026
Save Receipt
Discard
Option B · Triage-first (attention order)
Uncertain fields are promoted into a “Needs a look” section; confirmed fields collapse into a compact, still-tappable summary.
9:41
Possible duplicate
REWE City · Jun 14 · €84.50 already in Berlin — June 2026
View
REWE CITY
SUMME€84,50
Vendor
REWE City
Date
June 14, 2026
Total amount
€84.50
Smart extraction is getting ready — values below are from text reading only
Save Receipt
Discard
Edge states, same screen
Duplicate warning as a non-blocking banner with a peek; deterministic-only fallback gets one quiet line, never a gate.

Laws both options obey

  • Ambiguity = two chips + edit, max. Each chip carries a tiny model-supplied reason ("bottom line SUMME") — provenance in nine characters. Picking one fires .selection haptics and fills the field; more than ~3 ambiguous fields ⇒ fall back to empty editable fields (PRD §5.4's wall-of-choices rule).
  • Attention treatment is a 2.5pt warm edge tint + label, never red walls. Confirmed fields earn small green checkmarks — the screen visibly "completes" as you resolve it.
  • Required ≠ validation nagging. Type and payment are always-visible selectors, not text fields that can fail; Save stays disabled (60% opacity) until both are set, with the reason inline under the button when tapped early.
  • Save is glass, floating, singular. Discard is a plain text button that confirms only if the user edited something. Success = .success haptic + the receipt visibly flying into its group row.

How they differ

  • A (document order) matches the PRD's field order literally; the receipt image is the anchor and every field is a full editor. Predictable, spatial, zero relearning between a clean scan and a messy one.
  • B (attention order) optimizes the cash-register moment: worst case (2 ambiguities) is 2 taps + Save, all above the fold. Cost: field positions change between scans, and "Looks right" rows hide their editors one tap deeper.
Recommendation: Option A for v1 — the PRD's fixed field order is a spec requirement, and spatial stability is what makes review feel "boringly obvious" scan after scan. Fold B's best idea in: when a scan has ambiguities, auto-scroll to the first one and show the "2 fields need a look" count in the header. B remains the A/B-test candidate once real usage shows how often multi-ambiguity scans occur.
Screen group 6

Receipts tab & Receipt detail

The flat, searchable record of everything — and the per-receipt page where the original paper and the extracted record live side by side, forever editable.

9:41
All
Berlin ✕
Taxi
Cash
No photo
June 2026
Lufthansa
Jun 14 · Berlin — June 2026
€420.00
Flight
REWE City
Jun 14 · Berlin — June 2026
€84.50
Food
Airport taxi
Jun 12 · manual entry
€38.00
Taxi
April 2026
Hotel Blue Diamond
Apr 22 · Client visit — Pune
₹9,600.00
Hotel
Scan ReceiptMost recent: Berlin — June 2026
Groups
Receipts
Settings
Receipts. Month-grouped, searchable, composable filter chips. Manual entries show their type glyph where a thumbnail would be.
9:41
REWE CITY
SUMME€84,50
Zoom
Total
€84.50
Food · Cash
Vendor
REWE City
Date
June 14, 2026
Group
Berlin — June 2026
Notes
Team dinner, day 3
View original scan text
Scanned Jun 14, 2026 · extracted on device · you corrected 1 field
Receipt detail. Paper on top (zoomable, paged), record below, provenance tucked behind one disclosure. Every field editable, always.

Why it's world-class

  • Search lives where iOS 26 wants it.searchable on this tab, sliding up with the keyboard per the Liquid Glass search conventions. Filters are chips that compose (group ∧ type ∧ payment ∧ has-photo, PRD §7) and each shows an ✕ to remove — state visible, reversible, no hidden filter drawer.
  • Photo-less manual entries don't apologize: the type glyph takes the thumbnail slot, and a prominent Add Photo action on detail runs the pipeline later — extracted values are offered in a diff-confirm, never overwriting what the user typed (PRD §5.2).
  • Provenance without clutter: "View original scan text" is one quiet disclosure over ExtractionRecord.rawOCRText; the footnote states when it was scanned and that extraction was on-device. Trust is shown, not claimed.
  • Editing after saving is the same form as review — one component, one muscle memory, edits update group totals live (acceptance #8).

Build it with

  • .searchable + #Predicate-backed filters; month sections from Date.FormatStyle().month(.wide).year().
  • Image pager: TabView(.page) over attachments; zoom via full-screen cover with pinch (MagnifyGesture).
  • Row thumbnails from thumbnailData only — 1,000-receipt scroll stays smooth (PRD §9).
Screen group 7

Manual entry & Settings

Manual entry is the review form minus the image, opened as an inset sheet — four required fields and nothing else demanding attention. Settings is deliberately boring: defaults, status, privacy, version.

Amount · EUR
€38.00|
Date
June 12, 2026
Expense type · required
Food
Hotel
Flight
Taxi
Other
Payment · required
Card
Cash
Vendor, group, notes…
Save Expense
Manual entry sheet. Amount is the hero with the decimal pad up; optional fields fold behind one disclosure. Photo attachable later from detail.
9:41
Defaults
Currency
EUR — €
Payment method
Card
Export
CSV format
Matches your region — comma, 1,284.50
iCloud
Sync
Up to date · 2 min ago
Storage
312 receipts · 148 MB
All processing happens on your device. Your data syncs only to your private iCloud. Intelli-Expense makes no other network connections.
Version 1.0 (42)
Groups
Receipts
Settings
Settings. Defaults, CSV style, iCloud status with a quiet green dot, the privacy statement, version. No account. No capture bar here.

Why it's world-class

  • Manual entry respects the moment: you're in a taxi typing an amount — decimal pad already up, amount as the single hero, four required fields visible, everything optional behind "Vendor, group, notes…". Ten seconds, done.
  • The sheet is iOS 26 native: inset from the display edges, content peeking beneath, grabber on top — .presentationDetents([.large]) with system glass. No custom chrome to maintain.
  • Settings earns trust by being boring. iCloud state is a green dot and "Up to date", not a sync console; offline shows "Waiting for iCloud — your data is safe on this device" (PRD §6.4). The privacy statement sits exactly where users look for the catch, and there is none.
  • No in-app language picker — locale, currency defaults and CSV decimal style key off Locale.current including per-app language (PRD §3.2-A).
Screen group 8

Export — the payoff moment

The whole app funnels here: group detail → share icon → a zip your finance team opens without questions. The UI is a progress card that morphs into the system share sheet.

Berlin-June-2026.zip
14 receipts · 13 images · summary.csv
Preparing 11 of 14 · date-named folders
summary.csv includes per-currency totals
Opens in Excel & Numbers · UTF-8
AirDrop
Mail
Files
More
Export. Progress with honest counts, then the system share sheet. Cancellable; temp files cleaned up either way.

Why it's world-class

  • The filename is the feature: Berlin-June-2026.zip, date folders, 2026-06-14_Lufthansa_EUR-420.00.jpg — a finance admin can audit it without opening the app. Manual entries get a .txt stub so the record is complete (PRD §8).
  • Progress states what it's doing ("11 of 14 · date-named folders") because 200+ receipts must not block the UI (PRD §8) — background task + determinate bar, cancellable.
  • System share sheet, zero custom destinations. AirDrop-to-laptop is the killer path; we don't reinvent it.

Build it with

  • Zip assembly off the main actor; deliver via ShareLink / UIActivityViewController from a temp URL; clean up in defer.
  • CSV: fixed English headers, RFC-4180, UTF-8 BOM; localized comment row only (PRD §3.2-A5).
Handoff

Screen → SwiftUI mapping & decision summary

ScreenOptions shownRecommendationPrimary APIs (verified in docset)
Welcome + AI gates1 (+2 gate states)As shown; gate auto-dismisses on foregroundSystemLanguageModel.default.availability, @AppStorage, scenePhase
Structure / AddA capture accessory · B toolbar +A, with B's menu behind ⋯ / long-pressTabView + Tab, tabViewBottomAccessory(content:) (iOS 26.0+), tabBarMinimizeBehavior
Groups home1 (+ empty state)Inset-grouped rows, per-currency totals, type dotsList, ContentUnavailableView, swipe actions
Group detail1Hero total + breakdown chips + date sectionsList sections, ToolbarItem export, computed totals on ExpenseGroup
Processing1Scan-line over the capture, stage hints, CancelVNDocumentCameraViewController, RecognizeDocumentsRequest, TimelineView
Review & ConfirmA anchored form · B triage-firstA + B's ambiguity count/auto-scroll; B = A/B candidateForm/List, choice chips as Button rows, @Generable alternates, sensoryFeedback
Receipts1Month sections, composable filter chips.searchable, #Predicate, TabView(.page) in detail
Manual entry1Amount-hero sheet, optional fields folded.presentationDetents, .keyboardType(.decimalPad), Decimal end-to-end
Settings1Defaults · CSV · iCloud status · privacy · versionForm grouped, CloudKit status via CKContainer.accountStatus
Export1Progress card → system share sheetShareLink, background zip, temp-dir cleanup

Evidence trail