A friendly, whimsical pixel-art iOS shopping list. Adding is frictionless (a pinned bottom bar with history-backed suggestions); checking an item off pops a spark burst and an animated strikethrough, then slides it into a faded "Got it" section that clears itself after an hour.
Built with SwiftUI + SwiftData, on-device only (no account, no backend).
The look is a theme (Theme / ThemeStyle): the default is Pastel Dots
— creamy cards on a soft green pixel-dot backdrop, with pixel fonts (VT323 +
Silkscreen) and fresh fruity accents. Other themes ship in code and can be tried
via the BASKET_THEME env var (soft, pixel, dive, cozy, arcade,
fresh1…fresh10).
-
Quick add — always-visible bottom bar with a green + add button; as you type, suggestions float up as one-tap chips: your personal history first (things you've bought in the last month, ranked by frequency + recency), then a built-in food dictionary (
SuggestionDictionary) for instant autocomplete. That dictionary unifies the grocery + regional corpora with the emoji table's whole vocabulary, so anything the app can put an emoji on it can also suggest (e.g. "cord" → Cordial). Items already on the list are filtered out. -
Tap the check circle to check an item off — the check pops with a burst of gold sparks while a strikethrough draws left-to-right, then the row glides into a dimmed "Got it" section (tap it to restore it). Check several off at once and they hold their place until the last spark finishes, then glide down together — so the list never shuffles under your taps.
-
Tap the row (or its faint "+ Qty" chip) to set a quantity. An inline stepper slides down with a smart default unit guessed from the item — pour-y things start in ml, weighed things in g, everything else as a plain count. You can switch units freely from a pill row: every item can be counted in plain "units", and unrecognised items offer the full set (ml/L/g/kg/units), since we can't always know what you mean ("300 ml of milk" vs "1 bottle"). Switching ml↔L or g↔kg keeps the amount; switching to a different kind of unit starts fresh (so it never shows "500 units"). Tap the number itself (between the − and +) to type an exact amount on the keyboard — so a big quantity doesn't mean tapping + over and over. The field is forgiving: it ignores the unit letters ("750 ml" → 750), takes a comma or dot decimal, rounds plain counts to whole numbers, and quietly keeps the old amount if you clear it or type nonsense. The amount shows as a small chip on the row; long names truncate so the chip keeps its place.
-
1-hour TTL on the "Got it" section, so it tidies itself between shops — or tap Clear all in the section header to empty it immediately.
-
Duplicate-aware — re-adding something already listed bumps + flashes the existing row instead of creating a copy.
-
About sheet — the ⓘ in the header opens a small sheet with the app version and an optional tip jar (☕ / 🥪 / 🎁, in-app purchases via StoreKit). Basket is free; tipping unlocks nothing — but once you've tipped, the Basket title turns into a per-letter rainbow with a solid red heart; tap it to toggle between the rainbow and classic looks (your choice persists), as a small thank-you.
-
Little touches — a sub-second basket flourish on cold launch (never on resume), a full-screen "All done!" celebration when you check off the last item, and quiet living details: a faint time-of-day tint and the occasional seasonal accent (🎃, 🎄…) on the empty state.
-
Playful auto-emoji per item, via a three-stage cascade (
Sources/Services/Emoji.swift):- Curated table (
EmojiTable, ~1750 entries, generated bytools/gen_emoji.pyfrom inline data +tools/emoji_supplement.txt) covering produce, proteins, dairy, bakery, grains, pantry, drinks, snacks, frozen, prepared dishes, household/toiletry/baby/pet/health goods, and global cuisines — East/South/Southeast Asian, Middle Eastern, African, Latin American and European staples. The matcher prefers the longest keyword match, so prefix collisions resolve correctly (peach≠pea, ginger≠gin, hamburger≠ham). - Semantic fallback (
SemanticEmoji) — Apple's on-device word embeddings (NLEmbedding, fully offline) map novel items to the nearest "anchor" food word's emoji (e.g. "Flounder" → 🐟). This also collapses variants: "Frozen peas" resolves to the same glyph as "Peas". - Basket default (🧺) when nothing else fits.
Coverage is audited against a ~3650-item global grocery corpus (
tools/corpus/*.txt, spanning world cuisines) withtools/audit_coverage.swift— currently 100% (0 fall-throughs; ~91% curated, ~9% semantic). - Curated table (
-
A warm tri-colour (green/yellow/tomato) background bloom.
Requires Xcode's command-line tools and XcodeGen
(brew install xcodegen).
./build_run.sh # generate → build → install → launch → screenshot
./build_run.sh "iPhone 17" # target a different simulatorbuild_run.sh builds by -target with an explicit SUPPORTED_PLATFORMS because
this machine's Xcode generates a scheme whose supported-platforms list is empty.
Simulator builds need no signing. To run on a physical device, drop your Apple Team ID into a git-ignored override (the committed project stays team-agnostic):
echo 'DEVELOPMENT_TEAM = ABCDE12345' > Signing.local.xcconfig
xcodegen generateSigning.xcconfig (committed) optionally includes Signing.local.xcconfig, so
without the local file the build still works for the simulator.
Pure logic (emoji mapping, suggestion ranking, formatting) is covered two ways:
-
Tests/BasketTests.swift— the XCTest suite, run on the simulator:xcodegen generate xcodebuild test -project Basket.xcodeproj -scheme Basket \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro'
-
tools/main.swift— the same source files run natively on macOS (fast, no simulator needed):swiftc Sources/Services/Emoji.swift Sources/Services/EmojiTable.swift \ Sources/Services/SemanticEmoji.swift Sources/Services/Suggestions.swift \ Sources/Services/SuggestionDictionary.swift Sources/Models/Suggestion.swift \ Sources/Services/Formatting.swift \ Sources/Services/Measure.swift Sources/Services/Seasonality.swift \ tools/main.swift -o /tmp/basket_check && /tmp/basket_check
Note:
xcodebuild testand app-icon (asset catalog) compilation require an installed iOS simulator runtime matching the SDK. If you hit "No simulator runtime version … available", runxcodebuild -downloadPlatform iOS.