01 Overview & Philosophy
Nummeric is built entirely on Apple's first-party frameworks with zero external dependencies. No CocoaPods, no SPM packages, no Carthage. Every line of code is either written in-house or provided by Apple's SDKs.
This isn't an arbitrary constraint. It's a deliberate architectural decision that enables:
- Day-zero OS adoption — No waiting for third-party libraries to support new OS releases
- Binary size control — No unused framework code bundled into the app
- Security auditing — The entire dependency graph is Apple's responsibility
- Reproducible builds — No network fetches during build, no version resolution conflicts
- Privacy guarantees — No third-party SDK can phone home because none exist
Expression evaluation uses JavaScriptCore via an Objective-C exception bridge rather than any third-party math parsing library. This provides a battle-tested expression evaluator that's already on every Apple device.
Tech Stack at a Glance
| Layer | Technology |
|---|---|
| Language | Swift 5.9 with strict concurrency |
| UI | SwiftUI (100% declarative) |
| Persistence | SwiftData with schema versioning |
| Sync | CloudKit via SwiftData automatic mirroring |
| AI | FoundationModels (on-device LLM) |
| Math Engine | JavaScriptCore + ObjC bridge |
| Build System | XcodeGen (project.yml) |
| CI/CD | GitHub Actions (macOS 15) |
| Concurrency | Swift Structured Concurrency (async/await) |
02 Platform Targets
Nummeric ships as 5 distinct targets compiled from a shared model/utility layer, each tailored to its platform's interaction paradigm.
All targets share the same bundle identifier (com.arix.calculator) enabling Universal Purchase — one purchase unlocks the app across all Apple platforms.
Code Sharing Strategy
Rather than using a Swift Package or framework target, Nummeric includes shared source files directly in each platform target via XcodeGen's sources paths. The Mac and visionOS targets include Arix/Models/ and Arix/Utilities/ with platform-specific file excludes. Mac provides compatibility stubs in ArixMac/Compat/ for iOS-only APIs like haptics and camera scanning.
03 Architecture Pattern
Nummeric uses an Observable Model with Environment Injection pattern — a lightweight MVVM variant that leverages Swift 5.9's Observation framework without a separate ViewModel layer.
Key Principles
- No ViewModels.
@Observableclasses (e.g.,CalculatorEngine,ThemeManager,ConverterEngine) are injected directly into the SwiftUI environment. Views read them via@Environment(CalculatorEngine.self). - Pure logic engines are stateless. Evaluators, parsers, and solvers are
enumorstructtypes withstaticmethods. No instance state, no side effects, fully testable. - Strict
@MainActorisolation. All observable models are@MainActor-annotated. Background work usesTask.detachedwith structured concurrency. The project compiles withSWIFT_STRICT_CONCURRENCY: complete. - Singletons for cross-cutting concerns. Services like
CurrencyRates.shared,HapticManager.shared, andAuthenticationManager.shareduse the singleton pattern for app-wide state.
04 Feature Modules
Features are organized by domain in the Views/ directory. Each feature is a self-contained SwiftUI view backed by either an @Observable engine or inline state.
Calculator
Scientific, expression editing, history, memory, paper tape, RPN mode
Graphing
2D/3D plotting, multi-equation, trace mode, table of values, audio trace
Step Solver
AI-powered + local fallback, algebra/calculus/trig, verification
Notebook
Soulver-style live lines, named variables, handwriting canvas, export
Unit Converter
All SI categories, live currency rates, instant swap, search
214 Calculators
Declarative spec system, one generic view, formula steps, geometry diagrams
Matrix Algebra
Operations, determinants, inverses, step-by-step solutions
Programmer Mode
Hex/oct/bin, bitwise ops, number base explorer
Statistics
Data tables, visualizations, descriptive stats, distributions
AI Chat
On-device LLM, tool use, math context, streaming responses
Camera Scan
VisionKit DataScanner, receipt OCR, equation recognition
Voice Input
Speech recognition, NL math interpretation, on-device processing
Tab Management (iPhone)
All 10 primary tabs remain instantiated simultaneously in a ZStack with opacity(0) and allowsHitTesting(false) for hidden tabs. This preserves transient state (scroll position, partial input, graph zoom level) at negligible render cost since SwiftUI skips layout for invisible views. iPad uses a NavigationSplitView sidebar instead.
05 Data & Persistence
Nummeric uses SwiftData (Apple's Swift-native successor to CoreData) for all persistent storage, with schema versioning prepared for future migrations.
Persistent Models
| Model | Purpose | Key Fields |
|---|---|---|
CalculationRecord | Full calculation history | expression, result, timestamp, title, isBookmarked |
NotebookDocument | Multi-document math notebooks | title, body, createdAt, modifiedAt |
ScratchpadSlot | Saved intermediate values | label, value, createdAt, sortOrder |
Storage Tiers
The ModelContainer is constructed with a three-tier fallback strategy:
Additional Storage
- UserDefaults — All preferences (angle unit, decimal places, theme, calculator mode) via
@AppStorage - App Group (
group.com.arix.calculator) —SharedCalcStatefor widget extension communication. Uses a singleCodablesnapshot to prevent torn reads during concurrent widget refreshes - Custom file type —
.arixnote(UTI:com.arix.calculator.note) withTransferableconformance for notebook sharing
06 iCloud Sync
Cross-device synchronization is opt-in (disabled by default for privacy) and uses SwiftData's automatic CloudKit mirroring.
- Opt-in toggle in Settings:
iCloudSyncEnabled(defaults tofalse) - Container:
iCloud.com.arix.calculator - visionOS excluded — spatial sessions are ephemeral; local-only store
- Failure handling: Logs error, sets
cloudKitFailedflag for UI warning, falls back to local store - Handoff via
NSUserActivity(com.arix.calculator.currentCalculation) — continue a calculation on another device
07 Extension Ecosystem
Nummeric ships with 3 app extensions, bringing the calculator to every surface of the OS.
Widget Extension
Home screen, Lock screen, Control Center, and Dynamic Island Live Activities. Interactive calculation, conversion, graphing widgets. iOS 18+ Control Center buttons.
Keyboard Extension
Custom system keyboard with a mini calculator. UIKit-based UIInputViewController. Standalone engine (no shared code with main app). 5-row grid with Insert button to paste result.
iMessage Extension
MSMessagesAppViewController with SwiftUI hosting. Compact/expanded modes. Composes rich message bubbles with expression/result. Recipients can continue the calculation.
App Intents
Siri Shortcuts integration and Spotlight indexing. Calculate, convert, and save to scratchpad via voice or Shortcuts automation.
Widget Types
| Widget | Type | Description |
|---|---|---|
CalcWidget | Static | Shows last calculation result |
QuickCalcWidget | Interactive | Perform calculations directly from home screen |
ConversionWidget | Static | Last unit conversion |
GraphWidget | Static | Mini graph preview |
NotebookWidget | Static | Notebook content preview |
NummericLiveActivity | Live Activity | Dynamic Island + Lock Screen progress |
CalculatorControlWidget | Control | iOS 18 Control Center button |
08 Apple Intelligence
All AI features run entirely on-device using Apple's FoundationModels framework (iOS 26+ / macOS 26+). No data ever leaves the device. When the device doesn't support AI, every feature has a deterministic fallback.
SystemLanguageModel.default.availability verified at launch
LanguageModelSession instances keyed by topic (capped at 8)
@Generable structs for typed responses (e.g., ParsedMath, BillScan)
session.streamResponse(to:) for real-time token output in chat UI
CalculateTool, ConvertTool, SaveToScratchpadTool, SearchHistoryTool
AI-Powered Features
| Feature | AI Implementation | Fallback |
|---|---|---|
| Natural language math | @Generable struct ParsedMath | Regex-based NLMathInterpreter |
| Calculation explanations | Streaming LLM response | heuristicExplanation() pattern matching |
| Ask Nummeric chat | Multi-turn session with tools | Not available (feature hidden) |
| Step-by-step solver | StepSolver with structured steps | LocalStepSolver (linear, quadratic, systems) |
| Notebook title suggestion | Content summarization | First line of body text |
| Bill scanning | @Generable struct BillScan | VisionKit text extraction only |
09 Calculator Engine
The core expression evaluator uses JavaScriptCore via an Objective-C exception bridge. This provides a battle-tested math evaluation runtime without any third-party dependency.
Why JavaScriptCore?
- Already on every Apple device — zero additional binary size
- Handles operator precedence, parentheses, floating point correctly
- Nummeric extends it with custom functions (
sin,cos,log,factorial, etc.) injected into the JS context - The ObjC bridge (
ObjCExceptionBridge) wraps evaluation in@try/@catchsince Swift cannot catch ObjC exceptions from JSContext
Thread Safety
A shared JSContext instance is protected by a serial DispatchQueue. All evaluations are synchronized to prevent concurrent access, which JSContext does not support.
Supporting Engines
| Engine | Purpose | Type |
|---|---|---|
CalculatorEngine | Main state management | @Observable class |
RPNEngine | Reverse Polish Notation stack | @Observable class |
ExpressionEvaluator | JS evaluation wrapper | enum (static) |
MathExpressionParser | Token parsing / validation | enum (static) |
GraphEvaluator | Function plotting | enum (static) |
GraphAnalysis | Roots, extrema, inflections | enum (static) |
MatrixEngine | Matrix operations | @Observable class |
NumberBaseEngine | Hex/oct/bin conversion | @Observable class |
LocalStepSolver | Deterministic equation solving | enum (static) |
StatisticsEngine | Descriptive stats / distributions | @Observable class |
10 Calculator Registry
The 214 specialty calculators (BMI, mortgage, compound interest, pace, etc.) are expressed entirely as data — no per-calculator view code. A single generic FormulaCalculatorView renders any calculator from its spec.
Spec Structure
Why this matters: Adding a new calculator requires writing ~15 lines of data declaration. No new views, no new files, no new tests. The generic view and evaluation pipeline handle everything. This is how 214 calculators ship in a single-person project without technical debt.
Categories
Calculators span fitness, health, finance, engineering, cooking, geometry, physics, chemistry, and everyday utilities. Each shows the formula, step-by-step derivation, and (where applicable) a vector-drawn geometry diagram.
11 Testing & CI
Nummeric has a comprehensive test suite with 31 unit test files and 24 UI test files, run on every push via GitHub Actions.
Unit Tests (31 files)
Engine Tests
CalculatorEngine, RPNEngine, Converter, Matrix, NumberBase, Statistics
Parser/Evaluator
ExpressionEvaluator, MathExpressionParser, LineParser
Solver Tests
LocalStepSolver, RichFormulaStep, StepDiffEngine
Graph Tests
GraphAnalysis, GraphEvaluator
Integration
Intents, ThemeManager, CurrencyRates, SpotlightIndexer
Special
FuzzTests, PerformanceTests, SnapshotTests, AppleIntelligenceMath
UI Tests (24 files)
Per-feature smoke tests for every major tab, accessibility tests, error recovery, theme switching, tab persistence, and hamburger menu navigation.
CI Pipeline
- Sanitizers: Address Sanitizer (ASan) + Undefined Behavior Sanitizer (UBSan) in main test run; Thread Sanitizer (TSan) in separate job (incompatible with ASan)
- Simulators: iPhone 16 Pro + iPad Pro 13-inch matrix
- Coverage gate: 40% minimum (warns below 60%)
- Caching: DerivedData + Homebrew dependencies cached between runs
- Linting: SwiftLint with relaxed rules for math code (no line_length, identifier_name, file_length enforcement)
12 Design Decisions
Key architectural choices and the reasoning behind them.
Zero External Dependencies
Every third-party dependency is a liability: it can break on OS updates, introduce security vulnerabilities, add unused code weight, or be abandoned. By building on Apple's stack exclusively, Nummeric can adopt new OS features on day one and guarantees that no third-party code ever touches user data.
ObjC Exception Bridge for JSContext
JavaScript evaluation can throw ObjC exceptions that Swift cannot catch. Rather than using a third-party expression parser, Nummeric wraps JSContext.evaluateScript() in an Objective-C @try/@catch block. This provides safe, battle-tested math evaluation with zero additional binary size.
Declarative Calculator Specs
With 214 specialty calculators, a naive approach would require 214 view files. Instead, each calculator is a pure data declaration consumed by one generic view. Adding a calculator is a 15-line struct. This keeps the codebase linear rather than exponential.
ZStack Tab Preservation
SwiftUI's TabView destroys and recreates tab content on switch. Nummeric keeps all tabs alive in a ZStack with visibility toggles, preserving partial input, scroll position, and graph zoom across tab switches. The render cost is negligible since invisible views skip layout.
Atomic Widget State
SharedCalcState writes a single Codable snapshot to the App Group container instead of using separate UserDefaults keys. This prevents torn reads when the widget timeline refreshes concurrently with a main app write.
AI with Deterministic Fallbacks
Every Apple Intelligence feature has a working non-AI path. The step solver includes a full local algebraic solver (linear, quadratic, systems of equations). Natural language parsing falls back to regex. This ensures the app is fully functional on devices that don't support Apple Intelligence.
Theme System with Thread Safety
Seven visual themes (paper, pop, cyberpunk, terminal, brutalist, memphis, blueprint) with per-theme background patterns rendered via drawingGroup() for Metal-accelerated composition. Theme state resolution uses OSAllocatedUnfairLock for thread safety across concurrent view renders.
Audio Graph Accessibility
AudioTraceEngine sonifies graph curves using stereo panning (x-position maps to left/right), timbre shift (positive/negative y values), and click sounds at intersections (roots). This makes graphing accessible to visually impaired users.
Strict Concurrency
The project compiles with SWIFT_STRICT_CONCURRENCY: complete — the strictest setting. All @Observable models are @MainActor. Background work uses structured concurrency (Task.detached, async/await). No data races are possible at compile time.