---
stepsCompleted: [1, 2, 3, 4]
inputDocuments:
  - calcpad-product-requirements.md
---

# CalcPad — Epic Breakdown

## Overview

This document provides the complete epic and story breakdown for CalcPad, a modern cross-platform notepad calculator. CalcPad ships in three versions (Web, macOS, Windows) all sharing a single Rust calculation engine. This breakdown decomposes the product requirements into 16 implementable epics with 106 user stories, each with full Given/When/Then acceptance criteria.

## Requirements Inventory

### Functional Requirements

- FR1: Basic arithmetic with arbitrary precision (integers, decimals, scientific notation)
- FR2: Natural language math expressions ("5kg in pounds", "20% of 50")
- FR3: Variable declaration, assignment, and dependency tracking
- FR4: Line references and previous-line references
- FR5: Section aggregators (sum, total, average, min, max, count) and subtotals
- FR6: 200+ unit conversions across 13+ categories with SI prefix support
- FR7: CSS/screen unit conversions (px, pt, em, rem) with configurable PPI
- FR8: Binary vs decimal data unit differentiation (KiB vs kB)
- FR9: Custom user-defined units
- FR10: Live fiat currency conversion (180+ currencies)
- FR11: Live cryptocurrency conversion (50+ coins)
- FR12: Historical currency rate lookups
- FR13: Currency symbol and ISO code recognition
- FR14: Multi-currency arithmetic
- FR15: Date math (addition, subtraction, duration between dates)
- FR16: Time math with 12h/24h support
- FR17: Time zone conversions with city names and IANA zones
- FR18: Business day calculations with holiday calendars
- FR19: Unix timestamp conversions
- FR20: Relative time expressions
- FR21: Trigonometric, logarithmic, and exponential functions
- FR22: Factorial and combinatorics
- FR23: Financial functions (compound interest, mortgage)
- FR24: Proportions / rule of three
- FR25: Conditionals (if/then/else)
- FR26: Rounding functions
- FR27: List operations (min, max, gcd, lcm)
- FR28: Video timecode arithmetic
- FR29: Two-column notepad editor (input left, answers right)
- FR30: Syntax highlighting per platform
- FR31: Headers (#), comments (//), labels (:)
- FR32: Click-to-copy answer
- FR33: Drag-to-resize columns
- FR34: Answer column formatting (decimal places, notation, separators)
- FR35: Line numbers
- FR36: Find & replace with regex
- FR37: Autocomplete for variables, units, functions
- FR38: macOS native app (SwiftUI) with FFI bridge to Rust engine
- FR39: macOS menu bar mode and global hotkey (QuickCalc)
- FR40: macOS sheets sidebar with NavigationSplitView
- FR41: iCloud sync for macOS
- FR42: Spotlight and Quick Look integration
- FR43: Alfred / Raycast extension
- FR44: macOS WidgetKit widgets
- FR45: macOS keyboard shortcuts
- FR46: macOS export (PDF, HTML, Markdown, text, JSON, CSV)
- FR47: macOS App Store and notarized distribution
- FR48: Windows native app (Rust + iced) with direct engine linking
- FR49: Windows system tray mode and global hotkey
- FR50: Windows file management in %APPDATA%
- FR51: Windows theming (Mica/Acrylic)
- FR52: Windows auto-update with signature verification
- FR53: Windows portable mode
- FR54: Windows installer (.msi, winget, portable .zip)
- FR55: Web app with WASM engine in Web Worker
- FR56: CodeMirror 6 editor with custom syntax highlighting
- FR57: PWA support for offline use
- FR58: Shareable URL links with optional password/expiration
- FR59: Embeddable widget via iframe
- FR60: Collaborative real-time editing (CRDT-based)
- FR61: User accounts with cloud storage (Supabase)
- FR62: Keyboard shortcuts overlay
- FR63: CLI single expression evaluation
- FR64: CLI pipe/stdin mode
- FR65: CLI output formats (plain, JSON, CSV)
- FR66: CLI interactive REPL
- FR67: CLI distribution (cargo install, Homebrew)
- FR68: Plugin API via Rust trait
- FR69: Scripting layer (Rhai or Lua) for lightweight plugins
- FR70: Plugin marketplace
- FR71: Built-in stock price lookups
- FR72: Built-in crypto/DeFi data lookups

### Non-Functional Requirements

- NFR1: Real-time evaluation (< 16ms per line, < 100ms for 500-line sheet)
- NFR2: DAG-based dependency graph with lazy evaluation
- NFR3: Web Worker offloading for non-blocking UI
- NFR4: Crash recovery with auto-save every 2 seconds
- NFR5: >95% test coverage with unit, integration, property-based, and benchmark tests
- NFR6: Screen reader support (VoiceOver, UI Automation, ARIA)
- NFR7: High contrast mode (WCAG AAA)
- NFR8: Full keyboard-only operation
- NFR9: RTL language support
- NFR10: Localization in 8+ languages
- NFR11: WASM bundle < 500KB gzipped
- NFR12: macOS Universal Binary < 15MB
- NFR13: Windows portable .exe < 10MB
- NFR14: Privacy-respecting analytics (GDPR/CCPA compliant)
- NFR15: Arbitrary precision arithmetic (30+ significant digits, WASM-compatible)

### Additional Requirements

- AR1: C FFI layer with stable ABI for Swift integration
- AR2: WASM bindings via wasm-bindgen for web integration
- AR3: Error handling — invalid lines return errors without breaking sheet
- AR4: SheetContext struct for stateful multi-line evaluation
- AR5: Dark/light/auto theme on all platforms

### UX Design Requirements

- UX1: Two-column layout with synced scrolling
- UX2: Consistent parsing behavior across all platforms (engine-driven)
- UX3: Onboarding tutorial (5-step interactive walkthrough)
- UX4: Template library (7 pre-built templates)
- UX5: In-app feedback mechanism
- UX6: Post-update "What's New" changelog panel

### FR Coverage Map

- FR1-FR2: Epic 1 — Core Calculation Engine
- FR3-FR5, FR37: Epic 5 — Variables, Line References & Aggregators
- FR6-FR9: Epic 2 — Unit Conversion System
- FR10-FR14: Epic 3 — Currency & Cryptocurrency
- FR15-FR20: Epic 4 — Date, Time & Time Zones
- FR21-FR28: Epic 6 — Advanced Math & Functions
- FR29-FR30, FR38-FR47: Epic 7 — macOS App
- FR29-FR30, FR48-FR54: Epic 8 — Windows App
- FR29-FR30, FR55-FR62: Epic 9 — Web App
- FR31-FR36: Epic 10 — Notepad UX
- FR63-FR67: Epic 11 — CLI Tool
- FR68-FR72: Epic 12 — Plugin & Extension System
- NFR1-NFR5: Epic 13 — Performance & Reliability
- NFR6-NFR10: Epic 14 — Accessibility & Internationalization
- AR1-AR4: Epic 1 — Core Calculation Engine (Stories 1.5-1.8)
- UX1-UX2: Epic 10 — Notepad UX
- UX3-UX6: Epics 15 & 16 — Monetization, Onboarding, Analytics

## Epic List

### Epic 1: Core Calculation Engine (Rust Crate)
Build calcpad-engine as a standalone Rust crate — lexer, parser, interpreter, arbitrary precision, FFI, and WASM bindings.
**FRs covered:** FR1, FR2, AR1, AR2, AR3, AR4

### Epic 2: Unit Conversion System
200+ units across 13+ categories with SI prefixes, CSS units, binary/decimal data, and custom units.
**FRs covered:** FR6, FR7, FR8, FR9

### Epic 3: Currency & Cryptocurrency
Live and historical currency conversion with 180+ fiat and 50+ crypto.
**FRs covered:** FR10, FR11, FR12, FR13, FR14

### Epic 4: Date, Time & Time Zones
Full date/time math, business days, timezone conversions, unix timestamps.
**FRs covered:** FR15, FR16, FR17, FR18, FR19, FR20

### Epic 5: Variables, Line References & Aggregators
Variables, line refs, aggregators, subtotals, and autocomplete.
**FRs covered:** FR3, FR4, FR5, FR37

### Epic 6: Advanced Math & Functions
Trig, log, factorial, finance, proportions, conditionals, rounding, timecodes.
**FRs covered:** FR21, FR22, FR23, FR24, FR25, FR26, FR27, FR28

### Epic 7: macOS App (Swift/SwiftUI)
Native macOS experience with FFI bridge, menu bar, iCloud, Spotlight, widgets.
**FRs covered:** FR29, FR30, FR38-FR47

### Epic 8: Windows App (Rust + iced)
Native Windows app — single .exe, system tray, auto-update, portable mode.
**FRs covered:** FR29, FR30, FR48-FR54

### Epic 9: Web App (React + WASM)
Zero-install web experience with PWA, sharing, collaboration, accounts.
**FRs covered:** FR29, FR30, FR55-FR62

### Epic 10: Notepad UX (Cross-Platform Spec)
Consistent editor behavior — headers, comments, copy, resize, formatting, find.
**FRs covered:** FR31-FR36, UX1, UX2

### Epic 11: CLI Tool
Command-line evaluation, pipes, output formats, REPL, distribution.
**FRs covered:** FR63-FR67

### Epic 12: Plugin & Extension System
Rust trait API, scripting layer, marketplace, stock prices, crypto/DeFi.
**FRs covered:** FR68-FR72

### Epic 13: Performance & Reliability
Real-time eval, dependency graph, Web Worker, crash recovery, test suite.
**NFRs covered:** NFR1-NFR5

### Epic 14: Accessibility & Internationalization
Screen readers, high contrast, keyboard-only, RTL, localization.
**NFRs covered:** NFR6-NFR10

### Epic 15: Monetization & Onboarding
Free/Pro tiers, pricing, onboarding tutorial, template library.
**UX covered:** UX3, UX4

### Epic 16: Analytics, Feedback & Iteration
Privacy analytics, in-app feedback, changelog.
**UX covered:** UX5, UX6

---

# CalcPad BMAD Stories -- Epics 1-4

---

## EPIC 1 -- Core Calculation Engine (Rust Crate)

**Goal:** Build `calcpad-engine` as a standalone Rust crate that powers all platforms. This is the foundation.

---

### Story 1.1: Lexer & Tokenizer

As a CalcPad engine consumer,
I want input lines tokenized into a well-defined token stream,
So that the parser can build an AST from structured, unambiguous tokens rather than raw text.

**Acceptance Criteria:**

**Given** an input line containing an integer such as `42`
**When** the lexer tokenizes the input
**Then** it produces a single `Number` token with value `42`
**And** no heap allocations occur for this simple expression

**Given** an input line containing a decimal number such as `3.14`
**When** the lexer tokenizes the input
**Then** it produces a single `Number` token with value `3.14`

**Given** an input line containing a negative number such as `-7`
**When** the lexer tokenizes the input
**Then** it produces tokens representing the negation operator and the number `7`

**Given** an input line containing scientific notation such as `6.022e23`
**When** the lexer tokenizes the input
**Then** it produces a single `Number` token with value `6.022e23`

**Given** an input line containing SI scale suffixes such as `5k`, `2.5M`, or `1B`
**When** the lexer tokenizes the input
**Then** it produces `Number` tokens with values `5000`, `2500000`, and `1000000000` respectively

**Given** an input line containing currency symbols such as `$20`, `€15`, `£10`, `¥500`, or `R$100`
**When** the lexer tokenizes the input
**Then** it produces `CurrencySymbol` tokens paired with their `Number` tokens
**And** multi-character symbols like `R$` are recognized as a single token

**Given** an input line containing unit suffixes such as `5kg`, `200g`, or `3.5m`
**When** the lexer tokenizes the input
**Then** it produces `Number` tokens followed by `Unit` tokens

**Given** an input line containing arithmetic operators `+`, `-`, `*`, `/`, `^`, `%`
**When** the lexer tokenizes the input
**Then** it produces the corresponding `Operator` tokens

**Given** an input line containing natural language operators such as `plus`, `minus`, `times`, or `divided by`
**When** the lexer tokenizes the input
**Then** it produces the same `Operator` tokens as their symbolic equivalents
**And** `divided by` is recognized as a single two-word operator

**Given** an input line containing a variable assignment such as `x = 10`
**When** the lexer tokenizes the input
**Then** it produces an `Identifier` token, an `Assign` token, and a `Number` token

**Given** an input line containing a comment such as `// this is a note`
**When** the lexer tokenizes the input
**Then** it produces a `Comment` token containing the comment text
**And** the comment token is preserved for display but excluded from evaluation

**Given** an input line containing plain text with no calculable expression
**When** the lexer tokenizes the input
**Then** it produces a `Text` token representing the entire line

**Given** an input line containing mixed content such as `$20 in euro - 5% discount`
**When** the lexer tokenizes the input
**Then** it produces tokens for the currency value, the conversion keyword, the currency target, the operator, the percentage, and the keyword
**And** each token includes its byte span (start, end) within the input

---

### Story 1.2: Parser & AST

As a CalcPad engine consumer,
I want tokens parsed into a typed abstract syntax tree respecting operator precedence and natural language constructs,
So that the interpreter can evaluate expressions with correct semantics.

**Acceptance Criteria:**

**Given** a token stream for `2 + 3 * 4`
**When** the parser builds the AST
**Then** multiplication binds tighter than addition
**And** the tree evaluates as `2 + (3 * 4)` yielding `14`

**Given** a token stream for `(2 + 3) * 4`
**When** the parser builds the AST
**Then** the parenthesized sub-expression is grouped as a single child node
**And** the tree evaluates as `(2 + 3) * 4` yielding `20`

**Given** a token stream for `2^3^2`
**When** the parser builds the AST
**Then** exponentiation is right-associative
**And** the tree evaluates as `2^(3^2)` yielding `512`

**Given** a token stream for `5kg + 200g`
**When** the parser builds the AST
**Then** it produces a `BinaryOp(Add)` node with unit-attached number children `5 kg` and `200 g`

**Given** a token stream for `$20 in euro - 5% discount`
**When** the parser builds the AST
**Then** it produces a `Conversion` node wrapping `$20` targeting `euro`, followed by a `PercentOp(Subtract, 5%)` node
**And** the percentage operation applies to the result of the conversion

**Given** a token stream for implicit multiplication such as `2(3+4)` or `3pi`
**When** the parser builds the AST
**Then** it inserts an implicit `Multiply` operator between the adjacent terms

**Given** a token stream for a natural language phrase such as `5 plus 3 times 2`
**When** the parser builds the AST
**Then** it applies the same operator precedence as symbolic operators
**And** the tree evaluates as `5 + (3 * 2)` yielding `11`

**Given** a token stream for a proportion expression such as `3 is to 6 as what is to 10?`
**When** the parser builds the AST
**Then** it produces a `Proportion` node with known values `3`, `6`, `10` and an unknown placeholder
**And** the unknown resolves to `5`

**Given** a token stream for a conditional expression such as `if x > 5 then x * 2`
**When** the parser builds the AST
**Then** it produces an `IfThen` node with a condition sub-tree and a result sub-tree
**And** an optional `else` branch is supported

**Given** a token stream containing a syntax error such as `5 + + 3`
**When** the parser attempts to build the AST
**Then** it returns a `ParseError` with a descriptive message and the span of the offending token
**And** it does not panic

---

### Story 1.3: Interpreter / Evaluator

As a CalcPad engine consumer,
I want the AST evaluated into typed results with metadata,
So that each line produces a meaningful, displayable calculation result.

**Acceptance Criteria:**

**Given** an AST representing `2 + 3`
**When** the interpreter evaluates it
**Then** it returns a `CalcResult` of type `Number` with value `5`

**Given** an AST representing `5kg + 200g`
**When** the interpreter evaluates it
**Then** it returns a `CalcResult` of type `UnitValue` with value `5.2 kg`
**And** the result unit matches the left-hand operand's unit

**Given** an AST representing `$20 in EUR`
**When** the interpreter evaluates it within a context that has exchange rates loaded
**Then** it returns a `CalcResult` of type `CurrencyValue` with the converted amount and target currency `EUR`

**Given** an AST representing `today + 3 weeks`
**When** the interpreter evaluates it
**Then** it returns a `CalcResult` of type `DateTime` with the correct future date

**Given** an AST representing `March 12 to July 30`
**When** the interpreter evaluates it
**Then** it returns a `CalcResult` of type `TimeDelta` representing the duration between the two dates

**Given** an AST representing `5 > 3`
**When** the interpreter evaluates it
**Then** it returns a `CalcResult` of type `Boolean` with value `true`

**Given** an AST representing `100 - 20%`
**When** the interpreter evaluates it
**Then** it returns a `CalcResult` of type `Number` with value `80`
**And** the percentage is interpreted as "20% of 100" subtracted from 100

**Given** an AST representing `$100 + 10%`
**When** the interpreter evaluates it
**Then** it returns a `CalcResult` of type `CurrencyValue` with value `$110`

**Given** an AST with incompatible unit arithmetic such as `5kg + 3m`
**When** the interpreter evaluates it
**Then** it returns a `CalcResult` of type `Error` with a message indicating incompatible units
**And** the error includes the span of the offending expression

**Given** any evaluated result
**When** the result is produced
**Then** it includes metadata: the input span, the result type, display-formatted string, and raw numeric value where applicable

---

### Story 1.4: Arbitrary Precision Arithmetic

As a CalcPad user,
I want calculations precise to at least 30 significant digits with no floating-point drift,
So that financial calculations and large factorials produce exact, trustworthy results.

**Acceptance Criteria:**

**Given** the expression `0.1 + 0.2`
**When** the engine evaluates it
**Then** the result is exactly `0.3`
**And** there is no floating-point representation error

**Given** the expression `100!`
**When** the engine evaluates it
**Then** it returns the exact integer value (158 digits)
**And** no precision is lost

**Given** the expression `1 / 3`
**When** the engine evaluates it
**Then** it returns a result precise to at least 30 significant digits
**And** the display is appropriately rounded for the user

**Given** the expression `0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1`
**When** the engine evaluates it
**Then** the result is exactly `1.0`

**Given** a financial expression such as `$1000000.01 * 365`
**When** the engine evaluates it
**Then** the result preserves cents-level precision with no drift
**And** the result is `$365000003.65`

**Given** the engine is compiled to WASM
**When** arbitrary precision operations execute
**Then** the `dashu` crate functions correctly in the WASM target
**And** no platform-specific numeric behavior differs from native

**Given** an expression involving very large numbers such as `2^1000`
**When** the engine evaluates it
**Then** the exact integer result is returned
**And** the engine does not overflow or panic

---

### Story 1.5: C FFI Layer (for Swift)

As a macOS/iOS developer integrating CalcPad,
I want a stable C ABI with explicit memory ownership and no panics crossing the FFI boundary,
So that Swift can safely call into the Rust engine without undefined behavior.

**Acceptance Criteria:**

**Given** a Swift caller invoking `calcpad_eval_line` with a C string input
**When** the function executes
**Then** it returns a pointer to a `CalcResult` struct (or JSON-serialized string) allocated on the Rust heap
**And** the caller is responsible for freeing the result via `calcpad_free_result`

**Given** a Swift caller invoking `calcpad_eval_sheet` with multiple lines
**When** the function executes
**Then** it returns an array of results corresponding to each line
**And** variable assignments on earlier lines are visible to later lines

**Given** an expression that would cause a Rust panic (e.g., internal bug)
**When** the FFI function is called
**Then** `catch_unwind` intercepts the panic
**And** an error result is returned instead of unwinding into Swift

**Given** the FFI result is serialized as JSON
**When** the result crosses the FFI boundary
**Then** the JSON schema is versioned so Swift can handle backward-compatible changes
**And** the JSON includes result type, display value, raw value, and any error information

**Given** a `CalcResult` pointer returned from `calcpad_eval_line`
**When** the Swift caller calls `calcpad_free_result` with that pointer
**Then** the Rust allocator deallocates the memory
**And** no double-free or use-after-free is possible from correct usage

**Given** a null or invalid pointer passed to `calcpad_free_result`
**When** the function is called
**Then** it safely handles the null/invalid input without crashing
**And** no undefined behavior occurs

---

### Story 1.6: WASM Bindings (for Web)

As a web developer integrating CalcPad,
I want `wasm-bindgen` exports that return JSON-compatible results and fit within a 500KB gzipped bundle,
So that the engine runs performantly in the browser without large download overhead.

**Acceptance Criteria:**

**Given** a JavaScript caller invoking the `eval_line` WASM export with a string input
**When** the function executes
**Then** it returns a `JsValue` that can be deserialized to a JavaScript object
**And** the object includes `type`, `display`, `rawValue`, and `error` fields

**Given** a JavaScript caller invoking `eval_sheet` with an array of lines
**When** the function executes
**Then** it returns an array of result objects corresponding to each line
**And** variable context flows from earlier lines to later lines

**Given** the WASM crate is built with `wasm-pack build --release`
**When** the output `.wasm` file is gzip-compressed
**Then** the compressed size is under 500KB

**Given** the WASM module is loaded in a browser environment
**When** `eval_line` is called with `0.1 + 0.2`
**Then** the result is `0.3` (arbitrary precision works in WASM)
**And** performance is comparable to native for typical expressions (under 1ms per line)

**Given** the WASM module is loaded in a Web Worker
**When** `eval_sheet` is called
**Then** it does not block the main thread
**And** results can be posted back via standard `postMessage`

---

### Story 1.7: Error Handling & Graceful Degradation

As a CalcPad user,
I want lines with errors to show a subtle indicator without breaking the rest of my sheet,
So that a typo on one line does not disrupt my entire calculation flow.

**Acceptance Criteria:**

**Given** a sheet with 10 lines where line 5 contains a syntax error
**When** the sheet is evaluated
**Then** lines 1-4 and 6-10 produce correct results
**And** line 5 returns a `CalcResult::Error` with a human-readable message

**Given** an error result for a line
**When** the result is inspected
**Then** it includes the error message, the byte span of the problem, and an error category (Syntax, Type, Reference, Runtime)

**Given** a line that references an undefined variable `x`
**When** the engine evaluates it
**Then** it returns a `CalcResult::Error` with category `Reference` and message indicating `x` is undefined
**And** other lines that do not depend on `x` are unaffected

**Given** a line that produces a division-by-zero
**When** the engine evaluates it
**Then** it returns a `CalcResult::Error` with category `Runtime` and an appropriate message
**And** the engine does not panic

**Given** any possible input string (including empty, whitespace-only, binary data, or extremely long input)
**When** the engine evaluates it
**Then** it never panics
**And** it returns either a valid result or an error within a bounded time

**Given** a line that previously had an error but is now corrected
**When** the sheet is re-evaluated
**Then** the corrected line produces a valid result
**And** any dependent lines are also re-evaluated successfully

---

### Story 1.8: Sheet Context

As a CalcPad engine consumer,
I want a `SheetContext` struct that holds all evaluation state including variables, line results, and dependency graphs,
So that multi-line sheets are evaluated correctly and undo/redo is cheap.

**Acceptance Criteria:**

**Given** a new `SheetContext` is created via `SheetContext::new()`
**When** it is inspected
**Then** it has no lines, no variables, and an empty dependency graph

**Given** a `SheetContext` with `ctx.set_line(0, "x = 10")` and `ctx.set_line(1, "x * 2")`
**When** `ctx.eval()` is called
**Then** it returns a `Vec<CalcResult>` of length 2
**And** the first result is `10` and the second result is `20`

**Given** a `SheetContext` where line 0 defines `x = 10` and line 2 references `x`
**When** line 0 is changed to `x = 20` and `ctx.eval()` is called
**Then** line 2 is re-evaluated and reflects the updated value `x = 20`

**Given** a `SheetContext` with established state
**When** it is cloned via `ctx.clone()`
**Then** the clone is a deep copy that can be modified independently
**And** the clone operation is cheap enough for undo/redo snapshots (sub-millisecond for 100-line sheets)

**Given** a `SheetContext` with a dependency graph where line 3 depends on line 1
**When** only line 1 is modified
**Then** the engine re-evaluates line 1 and line 3 but skips lines that are not in the dependency chain
**And** unchanged, independent lines are not recomputed

**Given** a `SheetContext` where a circular dependency is introduced (e.g., line 1 references line 2 and line 2 references line 1)
**When** `ctx.eval()` is called
**Then** both lines return `CalcResult::Error` with a circular dependency message
**And** the engine does not enter an infinite loop

**Given** a `SheetContext` with lines that include aggregator references such as `total` or `sum`
**When** `ctx.eval()` is called
**Then** the aggregators compute over the appropriate preceding lines
**And** the dependency graph correctly tracks which lines feed into each aggregator

---

## EPIC 2 -- Unit Conversion System

**Goal:** Support 200+ units across all major categories with SI prefix support.

---

### Story 2.1: Unit Registry & Base Conversion

As a CalcPad user,
I want a comprehensive unit registry covering all major measurement categories with correct base-unit conversions,
So that I can convert between any supported units accurately.

**Acceptance Criteria:**

**Given** the unit registry is initialized
**When** the list of supported categories is queried
**Then** it includes length, mass, volume, area, speed, temperature, data, angle, time, pressure, energy, power, and force
**And** at least 200 units are registered across all categories

**Given** a linear unit conversion such as `5 miles in km`
**When** the engine evaluates it
**Then** it converts via the base unit (meters) using the stored conversion ratio
**And** the result is `8.04672 km`

**Given** a non-linear unit conversion such as `100 °F in °C`
**When** the engine evaluates it
**Then** it applies the formula `(F - 32) * 5/9` rather than a simple ratio
**And** the result is `37.777... °C`

**Given** a conversion between units in the same category such as `1 gallon in liters`
**When** the engine evaluates it
**Then** it converts via the base unit for volume
**And** the result is `3.78541 liters` (US gallon)

**Given** a conversion between incompatible categories such as `5 kg in meters`
**When** the engine evaluates it
**Then** it returns a `CalcResult::Error` indicating that mass and length are incompatible

**Given** the registry data
**When** the engine is compiled
**Then** the registry is static (built at compile time, not loaded at runtime)
**And** lookup of any unit by name or abbreviation is O(1) or near-O(1)

**Given** a unit that has multiple common names (e.g., `meter`, `metre`, `m`)
**When** any of these names is used in an expression
**Then** they all resolve to the same unit definition

---

### Story 2.2: SI Prefix Support

As a CalcPad user,
I want SI prefixes (nano through tera) recognized on any applicable unit in both short and long form,
So that I can naturally write expressions like `5 km` or `200 MB` without needing separate unit entries for every prefixed variant.

**Acceptance Criteria:**

**Given** the expression `5 km in miles`
**When** the engine evaluates it
**Then** it recognizes `km` as `kilo` + `meter`
**And** the result is approximately `3.10686 miles`

**Given** the expression `200 MB in GB`
**When** the engine evaluates it
**Then** it recognizes `MB` as `mega` + `byte` and `GB` as `giga` + `byte`
**And** the result is `0.2 GB`

**Given** the expression `3 µs in ms`
**When** the engine evaluates it
**Then** it recognizes `µs` as `micro` + `second` and `ms` as `milli` + `second`
**And** the result is `0.003 ms`

**Given** the expression `5 kilometers in miles`
**When** the engine evaluates it
**Then** it recognizes the long-form prefix `kilo` combined with `meters`
**And** the result matches the short-form `5 km in miles`

**Given** SI prefixes from nano (10^-9) through tera (10^12)
**When** each prefix is applied to a compatible base unit
**Then** the conversion factor is correctly applied: nano=10^-9, micro=10^-6, milli=10^-3, centi=10^-2, kilo=10^3, mega=10^6, giga=10^9, tera=10^12

**Given** an SI prefix applied to a unit where it does not make sense (e.g., `kilofahrenheit`)
**When** the engine encounters it
**Then** it does not recognize the combination as a valid unit
**And** it returns an appropriate error or treats the input as text

---

### Story 2.3: CSS & Screen Units

As a web designer or developer using CalcPad,
I want to convert between CSS units (px, pt, em, rem) using configurable PPI and base font size,
So that I can quickly calculate screen measurements for my designs.

**Acceptance Criteria:**

**Given** the default configuration (PPI=96, em=16px)
**When** the expression `12pt in px` is evaluated
**Then** the result is `16 px` (12pt * 96/72 = 16px)

**Given** the default configuration (PPI=96, em=16px)
**When** the expression `2em in px` is evaluated
**Then** the result is `32 px`

**Given** the user sets `ppi = 326` (Retina display)
**When** the expression `12pt in px` is evaluated
**Then** the result uses the custom PPI: `12 * 326/72 = 54.333... px`

**Given** the user sets `em = 20px`
**When** the expression `2em in px` is evaluated
**Then** the result is `40 px`

**Given** the expression `1rem in px` with default configuration
**When** the engine evaluates it
**Then** it treats `rem` as root em with the same base font size as `em` (16px by default)
**And** the result is `16 px`

**Given** a conversion from `px` to `pt`
**When** the expression `96px in pt` is evaluated at default PPI
**Then** the result is `72 pt`

---

### Story 2.4: Data Units (Binary vs Decimal)

As a CalcPad user working with digital storage,
I want correct differentiation between binary (KiB, MiB, GiB) and decimal (kB, MB, GB) data units,
So that my data size calculations match industry standards.

**Acceptance Criteria:**

**Given** the expression `1 MB in KB`
**When** the engine evaluates it
**Then** the result is `1000 KB` (decimal: 1 MB = 1000 KB)

**Given** the expression `1 MiB in KiB`
**When** the engine evaluates it
**Then** the result is `1024 KiB` (binary: 1 MiB = 1024 KiB)

**Given** the expression `1 GB in MB`
**When** the engine evaluates it
**Then** the result is `1000 MB`

**Given** the expression `1 GiB in MiB`
**When** the engine evaluates it
**Then** the result is `1024 MiB`

**Given** the expression `1 byte in bits`
**When** the engine evaluates it
**Then** the result is `8 bits`

**Given** the expression `1 MB in Mb` (megabytes to megabits)
**When** the engine evaluates it
**Then** the result is `8 Mb`
**And** case sensitivity distinguishes bytes (B) from bits (b)

**Given** the expression `5 GiB in GB`
**When** the engine evaluates it
**Then** the result correctly cross-converts: `5 * 1024^3 / 1000^3 = 5.36870912 GB`

---

### Story 2.5: Natural Language Unit Expressions

As a CalcPad user,
I want to write unit conversions using natural language keywords like "in", "to", and "as" with plural and singular unit names,
So that I can express conversions the way I naturally think about them.

**Acceptance Criteria:**

**Given** the expression `5 inches in cm`
**When** the engine evaluates it
**Then** it recognizes `in` as a conversion operator and `cm` as the target unit
**And** the result is `12.7 cm`

**Given** the expression `100 ft to meters`
**When** the engine evaluates it
**Then** it recognizes `to` as a conversion operator
**And** the result is `30.48 meters`

**Given** the expression `72 kg as pounds`
**When** the engine evaluates it
**Then** it recognizes `as` as a conversion operator
**And** the result is approximately `158.733 pounds`

**Given** the expression `1 foot in inches`
**When** the engine evaluates it
**Then** it recognizes both the singular `foot` and plural `inches`
**And** the result is `12 inches`

**Given** the expression `5 lbs in kilograms`
**When** the engine evaluates it
**Then** it recognizes the abbreviation `lbs` and full name `kilograms`
**And** the result is approximately `2.26796 kilograms`

**Given** the word `in` used in a context that is not a unit conversion (e.g., `5 in + 3 in`)
**When** the engine evaluates it
**Then** it correctly interprets `in` as the unit "inches" rather than a conversion operator
**And** the result is `8 in`

---

### Story 2.6: Custom User-Defined Units

As a CalcPad user,
I want to define my own units such as `1 sprint = 2 weeks` or `1 story_point = 4 hours`,
So that I can use domain-specific units in my calculations.

**Acceptance Criteria:**

**Given** the user writes `1 sprint = 2 weeks` on a line
**When** the engine evaluates the sheet
**Then** it registers `sprint` as a custom unit convertible to and from `weeks`
**And** subsequent lines can use `sprint` in expressions

**Given** a custom unit `1 sprint = 2 weeks` is defined
**When** the expression `3 sprints in days` is evaluated
**Then** the result is `42 days`

**Given** a custom unit `1 story_point = 4 hours` is defined
**When** the expression `10 story_points in hours` is evaluated
**Then** the result is `40 hours`

**Given** a custom unit is defined in one sheet
**When** the user configures it as a global custom unit
**Then** it is available in all sheets
**And** sheet-local definitions override global ones if there is a name conflict

**Given** a custom unit definition that creates a circular dependency (e.g., `1 foo = 2 bar` and `1 bar = 3 foo`)
**When** the engine processes these definitions
**Then** it detects the circular dependency and returns an error
**And** it does not enter an infinite loop

**Given** a custom unit name that conflicts with a built-in unit name
**When** the user defines it
**Then** the engine warns that the built-in unit is being shadowed
**And** the custom definition takes precedence within that sheet

---

## EPIC 3 -- Currency & Cryptocurrency

**Goal:** Real-time and historical currency conversion with 180+ fiat and 50+ crypto.

---

### Story 3.1: Fiat Currency Provider

As a CalcPad user,
I want live exchange rates for 180+ fiat currencies that are cached locally for offline use,
So that currency conversions are accurate and available even without internet.

**Acceptance Criteria:**

**Given** the engine is initialized with network access
**When** currency rates are requested for the first time
**Then** it fetches live rates from the configured provider (Open Exchange Rates or exchangerate.host)
**And** the rates cover at least 180 fiat currencies

**Given** rates have been fetched successfully
**When** the rates are stored
**Then** they are cached locally on disk with a timestamp
**And** the cache includes the provider name and base currency

**Given** cached rates exist and are less than the configured staleness threshold (e.g., 1 hour)
**When** currency conversion is requested
**Then** the cached rates are used without a network call

**Given** the device is offline
**When** a currency conversion is requested
**Then** the engine uses the most recent cached rates
**And** the result metadata indicates "offline -- rates from [timestamp]"

**Given** a successful rate fetch
**When** the result is displayed to the user
**Then** metadata includes "rates updated [relative time, e.g., '5 minutes ago']"

**Given** the rate provider API is unreachable and no cache exists
**When** a currency conversion is requested
**Then** the engine returns a `CalcResult::Error` indicating rates are unavailable
**And** non-currency calculations on the sheet are unaffected

---

### Story 3.2: Cryptocurrency Provider

As a CalcPad user,
I want live cryptocurrency rates for the top 50+ coins updated hourly,
So that I can convert between crypto and fiat currencies.

**Acceptance Criteria:**

**Given** the engine is initialized with network access
**When** crypto rates are requested
**Then** it fetches live rates from CoinGecko free API (or configured provider)
**And** at least 50 top cryptocurrencies by market cap are included (BTC, ETH, SOL, ADA, etc.)

**Given** crypto rates have been fetched
**When** the rates are stored
**Then** they are cached locally on disk with a timestamp
**And** the cache refresh interval is configurable (default: 1 hour)

**Given** the expression `1 BTC in USD`
**When** the engine evaluates it with current rates
**Then** it returns the current USD value of 1 Bitcoin
**And** the result metadata includes the rate timestamp

**Given** the expression `$1000 in ETH`
**When** the engine evaluates it
**Then** it returns the equivalent amount in Ethereum

**Given** the device is offline
**When** a crypto conversion is requested
**Then** the engine uses cached rates
**And** the result metadata indicates the age of the cached rates

**Given** a crypto symbol that could be ambiguous (e.g., a future coin with a symbol matching a fiat currency)
**When** the engine encounters it
**Then** fiat currencies take precedence by default
**And** the user can disambiguate using explicit notation (e.g., `crypto:SOL`)

---

### Story 3.3: Historical Rates

As a CalcPad user,
I want to convert currencies using historical exchange rates on specific dates,
So that I can calculate what an amount was worth at a particular point in time.

**Acceptance Criteria:**

**Given** the expression `$100 in EUR on 2024-01-15`
**When** the engine evaluates it
**Then** it fetches (or uses cached) the EUR/USD rate from January 15, 2024
**And** the result reflects the historical conversion

**Given** historical rates for a specific date have been fetched before
**When** the same date is queried again
**Then** the cached historical rates are used without a network call

**Given** a historical rate result
**When** the result is displayed
**Then** the metadata clearly indicates "historical rate from [date]"
**And** it is visually distinct from live rate results

**Given** the expression `$100 in EUR on 1900-01-01` (a date before available data)
**When** the engine evaluates it
**Then** it returns a `CalcResult::Error` indicating historical rates are not available for that date

**Given** the device is offline and no cached historical rate exists for the requested date
**When** a historical conversion is requested
**Then** the engine returns a `CalcResult::Error` indicating the historical rate is not available offline

**Given** the expression `€500 in GBP on March 5, 2023`
**When** the engine evaluates it
**Then** it correctly parses the natural-language date format
**And** fetches the historical rate for 2023-03-05

---

### Story 3.4: Currency Symbol & Code Recognition

As a CalcPad user,
I want to use both currency symbols ($, EUR) and ISO codes interchangeably,
So that I can write currency expressions naturally without remembering specific format rules.

**Acceptance Criteria:**

**Given** the expression `$100 to EUR`
**When** the engine evaluates it
**Then** `$` is recognized as `USD` and the conversion to `EUR` is performed

**Given** the expression `100 USD to EUR`
**When** the engine evaluates it
**Then** `USD` is recognized as the same currency as `$`
**And** the result matches `$100 to EUR`

**Given** the expression `€50 in dollars`
**When** the engine evaluates it
**Then** `€` is recognized as `EUR` and `dollars` is resolved to `USD`

**Given** the expression `R$500 in dollars`
**When** the engine evaluates it
**Then** `R$` is recognized as `BRL` (Brazilian Real)
**And** the result is the equivalent in USD

**Given** the expression `¥10000 in USD`
**When** the engine evaluates it
**Then** `¥` is resolved based on context (default: JPY; CNY if specified)

**Given** the expression `100 GBP to £`
**When** the engine evaluates it
**Then** it recognizes that the source and target are the same currency
**And** the result is `£100`

---

### Story 3.5: Multi-Currency Arithmetic

As a CalcPad user,
I want to add and subtract amounts in different currencies within a single expression,
So that I can calculate totals across currencies without manual conversions.

**Acceptance Criteria:**

**Given** the expression `$20 + €15 + £10 in BRL`
**When** the engine evaluates it
**Then** each amount is converted to BRL using current rates
**And** the results are summed to produce a single BRL total

**Given** the expression `$100 - €30`
**When** the engine evaluates it
**Then** `€30` is converted to USD (the currency of the left-hand operand)
**And** the result is expressed in USD

**Given** the expression `$50 + $30`
**When** the engine evaluates it
**Then** no conversion is needed (same currency)
**And** the result is `$80`

**Given** the expression `$20 + €15 + £10` with no explicit target currency
**When** the engine evaluates it
**Then** all amounts are converted to the currency of the first operand (USD)
**And** the result is expressed in USD

**Given** the expression `$20 * €15`
**When** the engine evaluates it
**Then** it returns a `CalcResult::Error` because multiplying two currency values is not meaningful
**And** the error message is descriptive

**Given** the expression `($100 + €50) * 2 in GBP`
**When** the engine evaluates it
**Then** the addition is performed first (converting EUR to USD), then multiplied by 2, then converted to GBP
**And** the result is a single GBP value

---

## EPIC 4 -- Date, Time & Time Zones

**Goal:** Full date/time math, business day calculations, and timezone awareness.

---

### Story 4.1: Date Math

As a CalcPad user,
I want to perform arithmetic on dates including addition, subtraction, and duration between dates,
So that I can quickly compute deadlines, durations, and future/past dates.

**Acceptance Criteria:**

**Given** the expression `today + 3 weeks 2 days`
**When** the engine evaluates it
**Then** it returns a `DateTime` result representing the date 23 days from the current date
**And** the result is formatted in the user's preferred date format

**Given** the expression `March 12 to July 30`
**When** the engine evaluates it
**Then** it returns a `TimeDelta` representing the duration between the two dates
**And** the result shows days (and optionally months/weeks) e.g., "140 days" or "4 months 18 days"

**Given** the expression `days until Christmas`
**When** the engine evaluates it on a date before December 25 of the current year
**Then** it returns the number of days until December 25 of the current year

**Given** the expression `days until Christmas`
**When** the engine evaluates it on December 26 or later
**Then** it returns the number of days until December 25 of the next year

**Given** the expression `January 15, 2025 - 30 days`
**When** the engine evaluates it
**Then** the result is `December 16, 2024`

**Given** the expression `today - March 1`
**When** the engine evaluates it
**Then** it returns the number of days between March 1 of the current year and today

**Given** a date expression with an ambiguous format such as `3/4/2025`
**When** the engine evaluates it
**Then** it uses the user's configured date format preference (US: month/day, EU: day/month)
**And** the default is US format (MM/DD/YYYY) unless configured otherwise

---

### Story 4.2: Time Math

As a CalcPad user,
I want to add and subtract durations from times and calculate durations between times,
So that I can compute meeting end times, elapsed time, and scheduling math.

**Acceptance Criteria:**

**Given** the expression `3:35 am + 9 hours 20 minutes`
**When** the engine evaluates it
**Then** the result is `12:55 pm`

**Given** the expression `3:35 PM + 9 hours 20 minutes`
**When** the engine evaluates it
**Then** the result is `12:55 AM` (next day)
**And** the result indicates the day rollover if applicable

**Given** the expression `14:30 - 2 hours 45 minutes`
**When** the engine evaluates it in 24-hour mode
**Then** the result is `11:45`

**Given** the user's time format preference is 12-hour
**When** time results are displayed
**Then** they use AM/PM notation

**Given** the user's time format preference is 24-hour
**When** time results are displayed
**Then** they use 24-hour notation (e.g., `14:30` instead of `2:30 PM`)

**Given** the expression `9:00 AM to 5:30 PM`
**When** the engine evaluates it
**Then** the result is `8 hours 30 minutes`

**Given** the expression `11:00 PM to 2:00 AM`
**When** the engine evaluates it
**Then** it assumes the time crosses midnight
**And** the result is `3 hours`

---

### Story 4.3: Time Zone Conversions

As a CalcPad user,
I want to convert times between time zones using city names and IANA timezone identifiers,
So that I can coordinate across geographies without looking up UTC offsets.

**Acceptance Criteria:**

**Given** the expression `3pm Tokyo in London`
**When** the engine evaluates it
**Then** it converts 3:00 PM JST (Asia/Tokyo) to the equivalent time in GMT/BST (Europe/London)
**And** the result accounts for the current DST status of both zones

**Given** the expression `New York time`
**When** the engine evaluates it
**Then** it returns the current time in the America/New_York timezone

**Given** the expression `now in PST`
**When** the engine evaluates it
**Then** it returns the current time converted to Pacific Standard Time (America/Los_Angeles)

**Given** the engine's timezone database
**When** queried for supported zones
**Then** it supports all IANA timezones via the `chrono-tz` crate
**And** at least 500 city name aliases map to their corresponding IANA zones

**Given** the expression `3pm EST in CET`
**When** the engine evaluates it
**Then** it converts from Eastern Standard Time to Central European Time
**And** DST transitions are handled correctly (e.g., EST vs EDT, CET vs CEST)

**Given** an ambiguous city name (e.g., `Portland` which exists in Oregon and Maine)
**When** the engine encounters it
**Then** it uses the most commonly referenced city by default (Portland, Oregon = America/Los_Angeles)
**And** the user can disambiguate with state/country (e.g., `Portland, ME`)

**Given** the expression `9am Monday New York in Tokyo`
**When** the engine evaluates it
**Then** it converts 9:00 AM on the next Monday in New York to the equivalent time in Tokyo
**And** if the conversion crosses a date boundary, the result shows the correct date and time

---

### Story 4.4: Business Day Calculations

As a CalcPad user,
I want to calculate dates by business days (skipping weekends and optionally holidays),
So that I can compute deadlines and delivery dates in a work context.

**Acceptance Criteria:**

**Given** the expression `10 business days from today`
**When** the engine evaluates it on a Monday
**Then** it returns the date two weeks from today (skipping two weekends)

**Given** the expression `10 business days from today`
**When** the engine evaluates it on a Wednesday
**Then** it counts forward 10 weekdays, correctly skipping Saturday and Sunday
**And** the result is the Wednesday of the following week plus the remaining days

**Given** the expression `5 business days from Friday`
**When** the engine evaluates it
**Then** it starts counting from the next Monday (skipping the weekend)
**And** the result is the following Friday

**Given** a holiday calendar is configured for the US locale
**When** the expression `3 business days from December 23, 2025` is evaluated
**Then** it skips December 25 (Christmas) in addition to weekends
**And** the result reflects the correct business day

**Given** no holiday calendar is configured
**When** business day calculations are performed
**Then** only weekends (Saturday and Sunday) are skipped
**And** all other days are treated as business days

**Given** the expression `today - 5 business days`
**When** the engine evaluates it
**Then** it counts backward, skipping weekends
**And** the result is the correct past business date

---

### Story 4.5: Unix Timestamp Conversions

As a CalcPad user (particularly a developer),
I want to convert between Unix timestamps and human-readable dates,
So that I can debug timestamps in logs and APIs.

**Acceptance Criteria:**

**Given** the expression `1733823083 to date`
**When** the engine evaluates it
**Then** it recognizes the number as a Unix timestamp (seconds since epoch)
**And** returns the corresponding date and time in the user's local timezone

**Given** the expression `1733823083000 to date`
**When** the engine evaluates it
**Then** it recognizes the 13-digit number as a millisecond Unix timestamp
**And** returns the corresponding date and time

**Given** the expression `now to unix`
**When** the engine evaluates it
**Then** it returns the current Unix timestamp in seconds

**Given** the expression `January 1, 2025 to unix`
**When** the engine evaluates it
**Then** it returns the Unix timestamp `1735689600` (midnight UTC on that date)

**Given** the expression `0 to date`
**When** the engine evaluates it
**Then** it returns `January 1, 1970 00:00:00 UTC` (the Unix epoch)

**Given** a negative timestamp such as `-86400 to date`
**When** the engine evaluates it
**Then** it returns `December 31, 1969` (one day before the epoch)

**Given** the expression `1733823083 to date in Tokyo`
**When** the engine evaluates it
**Then** it converts the timestamp and displays the result in Asia/Tokyo timezone

---

### Story 4.6: Relative Time Expressions

As a CalcPad user,
I want to write relative time expressions like "2 hours ago" or "next Wednesday",
So that I can reference dates and times naturally without specifying absolute values.

**Acceptance Criteria:**

**Given** the expression `2 hours ago`
**When** the engine evaluates it
**Then** it returns a `DateTime` representing the current time minus 2 hours

**Given** the expression `next Wednesday`
**When** the engine evaluates it
**Then** it returns the date of the next upcoming Wednesday
**And** if today is Wednesday, it returns the following Wednesday (not today)

**Given** the expression `last Friday at 3pm`
**When** the engine evaluates it
**Then** it returns the date of the most recent past Friday at 3:00 PM in the user's local timezone

**Given** the expression `3 days ago`
**When** the engine evaluates it
**Then** it returns the date that was 3 days before today

**Given** the expression `in 2 weeks`
**When** the engine evaluates it
**Then** it returns the date 14 days from today

**Given** the expression `yesterday`
**When** the engine evaluates it
**Then** it returns the date of the previous day

**Given** the expression `tomorrow at noon`
**When** the engine evaluates it
**Then** it returns tomorrow's date at 12:00 PM in the user's local timezone

**Given** the expression `last Monday` when evaluated on a Monday
**When** the engine evaluates it
**Then** it returns the previous Monday (7 days ago), not today
# CalcPad — BMAD Stories: Epics 5–8

---

## EPIC 5 — Variables, Line References & Aggregators

**Goal:** Transform the notepad into a lightweight computational document.

---

### Story 5.1: Variable Declaration & Usage

As a **CalcPad user**,
I want to declare named variables and use them in subsequent expressions,
So that I can build readable, self-documenting calculations that update automatically when I change an input.

**Acceptance Criteria:**

**Given** a line containing an assignment expression like `monthly_rent = $1250`
**When** the engine evaluates that line
**Then** the variable `monthly_rent` is stored with the value `1250` (with currency context preserved)
**And** the answer column displays `$1,250` for that line

**Given** a variable `monthly_rent` has been declared on a previous line
**When** the user writes `monthly_rent * 12` on a subsequent line
**Then** the engine resolves `monthly_rent` to its current value and displays `$15,000`

**Given** a variable `monthly_rent` is used on lines 3, 5, and 7
**When** the user changes the declaration on line 1 from `monthly_rent = $1250` to `monthly_rent = $1400`
**Then** every dependent line (3, 5, 7) re-evaluates automatically using the new value
**And** the results update within the same evaluation cycle (no stale values visible)

**Given** a variable `x = 10` declared on line 1 and `y = x * 2` on line 2
**When** the user changes line 1 to `x = 20`
**Then** line 2 re-evaluates to `40` via the dependency graph (transitive update)

**Given** a variable name that conflicts with a built-in function or unit name (e.g., `min = 5`)
**When** the engine evaluates the assignment
**Then** the engine either rejects the assignment with an error or clearly shadows the built-in with a warning indicator

**Given** the user writes a variable name using valid identifier characters (letters, digits, underscores, starting with a letter or underscore)
**When** the engine parses the line
**Then** the variable is accepted
**And** names like `tax_rate`, `_temp`, `item1` are all valid

**Given** the user writes a variable name with invalid characters (e.g., `my-var = 5` or `3x = 10`)
**When** the engine parses the line
**Then** the line is treated as an error or as a non-assignment expression (not silently misinterpreted)

---

### Story 5.2: Line References

As a **CalcPad user**,
I want to reference the result of a specific line by its line number,
So that I can build calculations that depend on intermediate results without naming every value.

**Acceptance Criteria:**

**Given** line 1 evaluates to `100`
**When** the user writes `line1 * 2` on any subsequent line
**Then** the engine resolves `line1` to `100` and the result is `200`

**Given** line 1 evaluates to `100`
**When** the user writes `#1 * 2` on any subsequent line
**Then** the engine resolves `#1` to `100` and the result is `200`

**Given** line 3 contains `line1 * 2` referencing line 1
**When** the user inserts a new line above line 1 (shifting the original line 1 to line 2)
**Then** the reference updates so it still points to the original line (now line 2)
**And** the expression on the shifted line becomes `line2 * 2` or the reference is internally resolved correctly

**Given** line 1 contains `#2 * 2` and line 2 contains `#1 + 3`
**When** the engine evaluates the sheet
**Then** a circular reference error is detected and reported on both lines
**And** neither line produces a numeric result (both show an error indicator)

**Given** a line references `#99` but only 10 lines exist
**When** the engine evaluates the sheet
**Then** the line displays an error such as "invalid line reference"

**Given** line 5 is a comment or empty line with no numeric result
**When** another line references `#5`
**Then** the engine reports an error or returns `0` with a clear indication that the referenced line has no value

---

### Story 5.3: Previous Line Reference

As a **CalcPad user**,
I want to use `prev` or `ans` to reference the result of the immediately preceding line,
So that I can chain calculations naturally without tracking line numbers or variable names.

**Acceptance Criteria:**

**Given** line 1 evaluates to `50`
**When** the user writes `prev * 2` on line 2
**Then** the result on line 2 is `100`

**Given** line 1 evaluates to `50`
**When** the user writes `ans + 10` on line 2
**Then** the result on line 2 is `60`

**Given** `prev` is used on line 1 (the very first line)
**When** the engine evaluates the sheet
**Then** the engine reports an error such as "no previous line" (there is no preceding result)

**Given** line 3 is a comment or blank line, and line 4 uses `prev`
**When** the engine evaluates the sheet
**Then** `prev` resolves to the most recent line above that produced a numeric result (e.g., line 2)

**Given** line 2 uses `prev` referencing line 1, and the user inserts a new line between them
**When** the engine re-evaluates
**Then** `prev` on the original line now references the newly inserted line (it always means "the line directly above")

**Given** line 1 evaluates to `$50` (with currency)
**When** line 2 uses `prev + $25`
**Then** the result is `$75` with the currency context preserved

---

### Story 5.4: Section Aggregators

As a **CalcPad user**,
I want to use aggregation keywords like `sum`, `total`, `average`, `min`, `max`, and `count` to compute over a section of lines,
So that I can quickly summarize a group of related values without manually writing out each reference.

**Acceptance Criteria:**

**Given** lines 1–4 contain numeric values `10`, `20`, `30`, `40` and line 5 is empty
**When** the user writes `sum` on line 5 (or after the group)
**Then** the result is `100` (sum of lines 1–4)

**Given** lines 1–4 contain numeric values and line 5 is a heading `## Monthly Costs`
**When** the user writes `total` immediately after lines 6–9 (which contain values under that heading)
**Then** `total` sums only lines 6–9 (the section bounded by the heading above and the aggregator line)

**Given** a section with values `10`, `20`, `30`
**When** the user writes `average` (or `avg`) at the end of the section
**Then** the result is `20`

**Given** a section with values `5`, `12`, `3`, `8`
**When** the user writes `min` at the end of the section
**Then** the result is `3`

**Given** a section with values `5`, `12`, `3`, `8`
**When** the user writes `max` at the end of the section
**Then** the result is `12`

**Given** a section with values `5`, `12`, `3`, `8`
**When** the user writes `count` at the end of the section
**Then** the result is `4` (number of lines with numeric results)

**Given** a section contains a mix of numeric lines and comment lines
**When** an aggregator is applied
**Then** only lines with numeric results are included in the aggregation (comments and blank lines are skipped)

**Given** an empty section (heading immediately followed by another heading or end-of-document)
**When** the user writes `sum`
**Then** the result is `0` (or an appropriate indication that there are no values to aggregate)

---

### Story 5.5: Subtotals & Grand Total

As a **CalcPad user**,
I want subtotal lines scoped to their respective sections and a grand total that sums all subtotals,
So that I can build structured documents like budgets with section summaries and an overall total.

**Acceptance Criteria:**

**Given** a sheet with two sections, each ending with a `subtotal` line
**When** the engine evaluates the sheet
**Then** each `subtotal` computes the sum of numeric lines in its own section only (not lines from other sections)

**Given** Section A has values `100`, `200` (subtotal = `300`) and Section B has values `50`, `75` (subtotal = `125`)
**When** the user writes `grand total` after both sections
**Then** the result is `425` (the sum of all subtotal lines: `300 + 125`)

**Given** a sheet with three sections, each with a `subtotal`
**When** the user adds a fourth section with its own `subtotal`
**Then** `grand total` at the bottom automatically includes the new subtotal in its sum

**Given** a subtotal line exists but its section has no numeric lines
**When** the engine evaluates
**Then** the subtotal is `0`
**And** the grand total still includes this `0` subtotal without error

**Given** a section uses `total` instead of `subtotal`
**When** a `grand total` line appears later
**Then** `grand total` sums all `subtotal` lines specifically (it does not include `total` lines unless they are explicitly subtotal-scoped; the distinction should be clearly documented)

**Given** a user writes multiple `subtotal` lines within the same section
**When** the engine evaluates
**Then** each `subtotal` line sums the lines above it within its section scope, and `grand total` sums all subtotal lines in the document

---

### Story 5.6: Autocomplete

As a **CalcPad user**,
I want an autocomplete popup that suggests variables, units, and functions as I type,
So that I can work faster and avoid typos in long variable names or function calls.

**Acceptance Criteria:**

**Given** the user has declared variables `monthly_rent`, `monthly_insurance`, and `mortgage_payment`
**When** the user types `mo` in the editor (2+ characters)
**Then** an autocomplete popup appears showing `monthly_rent`, `monthly_insurance`, `mortgage_payment` as suggestions

**Given** the autocomplete popup is visible with suggestions
**When** the user presses Tab or Enter
**Then** the currently highlighted suggestion is inserted into the editor at the cursor position
**And** the popup dismisses

**Given** the autocomplete popup is visible
**When** the user presses Escape
**Then** the popup dismisses without inserting anything
**And** the user's partially typed text remains unchanged

**Given** the user types `sq` in the editor
**When** the autocomplete popup appears
**Then** it includes built-in functions like `sqrt` and any user-defined variables starting with `sq`

**Given** the user types a single character (e.g., `m`)
**When** the engine checks whether to show autocomplete
**Then** the popup does NOT appear (threshold is 2+ characters)

**Given** the autocomplete popup is showing and the user continues typing
**When** the typed text no longer matches any known variable, unit, or function
**Then** the popup automatically dismisses

**Given** the user types `k` after a number (e.g., `50k`)
**When** the engine checks autocomplete context
**Then** the autocomplete recognizes this as a unit/suffix context and suggests `kg`, `km`, `kB`, etc. (not variable names)

**Given** the user navigates the autocomplete list with arrow keys
**When** the user presses Up or Down
**Then** the highlighted suggestion changes accordingly
**And** the list scrolls if necessary to keep the selection visible

---

## EPIC 6 — Advanced Math & Functions

**Goal:** Scientific, financial, and power-user math.

---

### Story 6.1: Trigonometric Functions

As a **CalcPad user**,
I want to evaluate trigonometric functions including sin, cos, tan, and their inverses and hyperbolic variants,
So that I can perform scientific and engineering calculations directly in CalcPad.

**Acceptance Criteria:**

**Given** the angle mode is set to radians (default)
**When** the user writes `sin(3.14159)` (approximately pi)
**Then** the result is approximately `0` (within floating-point tolerance)

**Given** the angle mode is set to degrees
**When** the user writes `sin(90)`
**Then** the result is `1`

**Given** the user writes `sin(45°)` with the degree symbol
**When** the engine evaluates the expression
**Then** the engine treats the argument as degrees regardless of the global angle mode setting
**And** the result is approximately `0.7071`

**Given** the engine is in radians mode
**When** the user writes `cos(0)`
**Then** the result is `1`

**Given** the user writes `tan(45°)`
**When** the engine evaluates
**Then** the result is approximately `1`

**Given** the user writes `asin(1)` in radians mode
**When** the engine evaluates
**Then** the result is approximately `1.5708` (pi/2)

**Given** the user writes `acos(0.5)` in degrees mode
**When** the engine evaluates
**Then** the result is `60` (degrees)

**Given** the user writes `atan(1)` in degrees mode
**When** the engine evaluates
**Then** the result is `45`

**Given** the user writes `sinh(1)`
**When** the engine evaluates
**Then** the result is approximately `1.1752`

**Given** the user writes `cosh(0)`
**When** the engine evaluates
**Then** the result is `1`

**Given** the user writes `tanh(0)`
**When** the engine evaluates
**Then** the result is `0`

**Given** the user writes `sin(x)` where `x` is out of domain for an inverse trig function (e.g., `asin(2)`)
**When** the engine evaluates
**Then** an error is displayed indicating the argument is out of the valid domain

**Given** the user wants to switch angle modes
**When** the user sets the mode via a configuration directive (e.g., `angle mode: degrees`)
**Then** all subsequent trig evaluations use the selected mode until changed again

---

### Story 6.2: Logarithmic & Exponential Functions

As a **CalcPad user**,
I want to use logarithmic and exponential functions including ln, log, log2, exp, pow, sqrt, cbrt, and nth-root,
So that I can perform scientific, financial, and engineering calculations involving growth, decay, and roots.

**Acceptance Criteria:**

**Given** the user writes `ln(1)`
**When** the engine evaluates
**Then** the result is `0`

**Given** the user writes `ln(e)` where `e` is Euler's number
**When** the engine evaluates
**Then** the result is `1`

**Given** the user writes `log(100)`
**When** the engine evaluates
**Then** the result is `2` (base-10 logarithm)

**Given** the user writes `log2(256)`
**When** the engine evaluates
**Then** the result is `8`

**Given** the user writes `exp(1)`
**When** the engine evaluates
**Then** the result is approximately `2.71828` (e^1)

**Given** the user writes `2^10`
**When** the engine evaluates
**Then** the result is `1024`

**Given** the user writes `pow(2, 10)`
**When** the engine evaluates
**Then** the result is `1024`

**Given** the user writes `e^3`
**When** the engine evaluates
**Then** the result is approximately `20.0855`

**Given** the user writes `sqrt(144)`
**When** the engine evaluates
**Then** the result is `12`

**Given** the user writes `cbrt(27)`
**When** the engine evaluates
**Then** the result is `3`

**Given** the user writes an nth-root expression such as `4th root of 625` or `root(625, 4)`
**When** the engine evaluates
**Then** the result is `5`

**Given** the user writes `ln(0)` or `log(-1)`
**When** the engine evaluates
**Then** the engine displays an error indicating the argument is out of the valid domain

**Given** the user writes `sqrt(-4)`
**When** the engine evaluates
**Then** the engine displays an error (complex numbers are out of scope unless explicitly supported)

---

### Story 6.3: Factorial & Combinatorics

As a **CalcPad user**,
I want to compute factorials, permutations, and combinations,
So that I can solve probability, statistics, and combinatorics problems.

**Acceptance Criteria:**

**Given** the user writes `10!`
**When** the engine evaluates
**Then** the result is `3628800`

**Given** the user writes `0!`
**When** the engine evaluates
**Then** the result is `1` (by mathematical convention)

**Given** the user writes `nPr(10, 3)`
**When** the engine evaluates
**Then** the result is `720` (10! / 7!)

**Given** the user writes `nCr(10, 3)`
**When** the engine evaluates
**Then** the result is `120` (10! / (3! * 7!))

**Given** the user writes `100!`
**When** the engine evaluates
**Then** the result is computed using arbitrary precision arithmetic and is exact (not a floating-point approximation)
**And** the full integer result is displayed (or a scrollable/expandable representation for very large numbers)

**Given** the user writes `(-3)!`
**When** the engine evaluates
**Then** an error is displayed (factorial of negative integers is undefined)

**Given** the user writes `3.5!`
**When** the engine evaluates
**Then** either the gamma function is used to compute `Gamma(4.5)` approximately `11.6317`, or an error indicates that only non-negative integer factorials are supported

**Given** the user writes `nCr(5, 7)` where k > n
**When** the engine evaluates
**Then** the result is `0` (by combinatorial convention)

---

### Story 6.4: Financial Functions

As a **CalcPad user**,
I want to use natural-language financial expressions for compound interest and mortgage calculations,
So that I can quickly answer personal finance questions without looking up formulas.

**Acceptance Criteria:**

**Given** the user writes `$5000 after 3 years at 7.5%`
**When** the engine evaluates (compound interest, annual compounding by default)
**Then** the result is approximately `$6,211.94`

**Given** the user writes `mortgage $350000 at 6.5% for 30 years`
**When** the engine evaluates (standard monthly payment amortization)
**Then** the result is approximately `$2,212.24` per month

**Given** the user writes `$10000 after 5 years at 5% compounded monthly`
**When** the engine evaluates
**Then** the result is approximately `$12,833.59`

**Given** the user writes `mortgage $200000 at 4% for 15 years`
**When** the engine evaluates
**Then** the result is approximately `$1,479.38` per month

**Given** the user writes a compound interest expression with 0% interest
**When** the engine evaluates
**Then** the result equals the principal (no growth)

**Given** the user writes a mortgage expression with 0% interest
**When** the engine evaluates
**Then** the monthly payment is the principal divided by the total number of months

**Given** the user writes `$5000 after 0 years at 7.5%`
**When** the engine evaluates
**Then** the result is `$5,000` (the principal unchanged)

**Given** the user writes a financial expression with missing or invalid parameters (e.g., `mortgage at 5%`)
**When** the engine evaluates
**Then** an error is displayed indicating the required format or missing parameters

---

### Story 6.5: Proportions / Rule of Three

As a **CalcPad user**,
I want to solve proportion problems using natural language,
So that I can quickly compute unknown values in ratios without manually setting up equations.

**Acceptance Criteria:**

**Given** the user writes `3 is to 6 as what is to 10?`
**When** the engine evaluates
**Then** the result is `5`

**Given** the user writes `3 is to 6 as 5 is to what?`
**When** the engine evaluates
**Then** the result is `10`

**Given** the user writes `10 is to 5 as 20 is to what?`
**When** the engine evaluates
**Then** the result is `10`

**Given** the user writes `0 is to 5 as what is to 10?`
**When** the engine evaluates
**Then** the result is `0`

**Given** the user writes `3 is to 0 as 5 is to what?`
**When** the engine evaluates
**Then** an error is displayed (division by zero in the proportion)

---

### Story 6.6: Conditionals

As a **CalcPad user**,
I want to write conditional expressions using `if/then/else`,
So that I can model decision logic such as tax brackets and tiered pricing directly in my calculations.

**Acceptance Criteria:**

**Given** a variable `revenue = 150000`
**When** the user writes `if revenue > 100k then revenue * 0.15 else revenue * 0.10`
**Then** the result is `22500` (the `then` branch is taken because 150000 > 100000)

**Given** a variable `revenue = 80000`
**When** the user writes `if revenue > 100k then revenue * 0.15 else revenue * 0.10`
**Then** the result is `8000` (the `else` branch is taken because 80000 is not > 100000)

**Given** the user writes `if 5 > 3 then 10 else 20`
**When** the engine evaluates
**Then** the result is `10`

**Given** the user writes `if 2 > 3 then 10 else 20`
**When** the engine evaluates
**Then** the result is `20`

**Given** the user writes `if x > 10 then x * 2 else x` where `x` is undefined
**When** the engine evaluates
**Then** an error is displayed indicating that `x` is not defined

**Given** the user writes a conditional comparing values with units, e.g., `if distance > 100 km then 50 else 30`
**When** the engine evaluates with `distance = 150 km`
**Then** the result is `50` (the comparison respects unit context)

**Given** the user writes a conditional without an `else` clause, e.g., `if revenue > 100k then revenue * 0.15`
**When** the engine evaluates with `revenue = 80000`
**Then** the result is `0` or `null`/blank (a sensible default when the condition is false and no else is provided)

**Given** the user writes a nested conditional: `if a > 10 then if a > 20 then 3 else 2 else 1`
**When** the engine evaluates with `a = 25`
**Then** the result is `3`

---

### Story 6.7: Rounding Functions

As a **CalcPad user**,
I want rounding functions including natural-language "round to nearest" and standard floor/ceil/round,
So that I can control numeric precision in my calculations.

**Acceptance Criteria:**

**Given** the user writes `round 21 up to nearest 5`
**When** the engine evaluates
**Then** the result is `25`

**Given** the user writes `round 22 down to nearest 5`
**When** the engine evaluates
**Then** the result is `20`

**Given** the user writes `round 23 to nearest 5`
**When** the engine evaluates
**Then** the result is `25` (standard rounding: 23 is closer to 25 than 20)

**Given** the user writes `floor(3.7)`
**When** the engine evaluates
**Then** the result is `3`

**Given** the user writes `floor(-3.2)`
**When** the engine evaluates
**Then** the result is `-4` (floor rounds toward negative infinity)

**Given** the user writes `ceil(3.2)`
**When** the engine evaluates
**Then** the result is `4`

**Given** the user writes `ceil(-3.7)`
**When** the engine evaluates
**Then** the result is `-3` (ceil rounds toward positive infinity)

**Given** the user writes `round(3.456, 2)`
**When** the engine evaluates
**Then** the result is `3.46` (rounded to 2 decimal places)

**Given** the user writes `round(3.456, 0)`
**When** the engine evaluates
**Then** the result is `3`

**Given** the user writes `round(2.5)`
**When** the engine evaluates
**Then** the result is `3` (round half up, or the chosen rounding convention is documented)

---

### Story 6.8: List Operations

As a **CalcPad user**,
I want to use list-based functions like min, max, gcd, and lcm with multiple arguments,
So that I can perform aggregate computations on explicit sets of values.

**Acceptance Criteria:**

**Given** the user writes `min(5, 3, 7)`
**When** the engine evaluates
**Then** the result is `3`

**Given** the user writes `max(5, 3, 7)`
**When** the engine evaluates
**Then** the result is `7`

**Given** the user writes `gcd(10, 20, 5)`
**When** the engine evaluates
**Then** the result is `5`

**Given** the user writes `lcm(210, 40, 8)`
**When** the engine evaluates
**Then** the result is `840`

**Given** the user writes `min(5)` with a single argument
**When** the engine evaluates
**Then** the result is `5`

**Given** the user writes `gcd(0, 5)`
**When** the engine evaluates
**Then** the result is `5` (gcd(0, n) = n)

**Given** the user writes `lcm(0, 5)`
**When** the engine evaluates
**Then** the result is `0` (lcm(0, n) = 0)

**Given** the user writes `min()` with no arguments
**When** the engine evaluates
**Then** an error is displayed indicating that at least one argument is required

**Given** the user writes `gcd(12.5, 7.5)`
**When** the engine evaluates
**Then** an error is displayed (gcd is defined for integers only) or the values are truncated/rounded with a warning

---

### Story 6.9: Video Timecodes

As a **CalcPad user**,
I want to perform arithmetic on video timecodes in HH:MM:SS:FF format with support for common frame rates,
So that I can calculate durations and offsets for video editing workflows.

**Acceptance Criteria:**

**Given** the user writes `01:30:00:00 + 00:15:30:00` (at the default frame rate)
**When** the engine evaluates
**Then** the result is `01:45:30:00`

**Given** the user writes `01:00:00:00 - 00:30:00:00`
**When** the engine evaluates
**Then** the result is `00:30:00:00`

**Given** the frame rate is set to 24 fps and the user writes `00:00:00:23 + 00:00:00:01`
**When** the engine evaluates
**Then** the result is `00:00:01:00` (frame 23 + 1 frame = 1 second at 24 fps)

**Given** the frame rate is set to 29.97 fps (drop-frame)
**When** the user writes `00:00:59:29 + 00:00:00:01`
**Then** the result correctly accounts for drop-frame timecode rules (frame numbers 0 and 1 are skipped at certain minute boundaries)

**Given** the user writes a timecode with a frame value exceeding the frame rate (e.g., `00:00:00:30` at 24 fps)
**When** the engine evaluates
**Then** an error is displayed indicating the frame value is out of range for the current frame rate

**Given** the user configures the frame rate to one of the supported values: 23.976, 24, 25, 29.97, 30, 60 fps
**When** subsequent timecode arithmetic is performed
**Then** the engine uses the configured frame rate for all carry and overflow calculations

**Given** the user writes a subtraction that would result in a negative timecode (e.g., `00:00:10:00 - 00:01:00:00`)
**When** the engine evaluates
**Then** the result is displayed as a negative timecode or an error is shown, depending on the design decision (clearly documented)

**Given** the user writes `01:30:00:00` without any arithmetic
**When** the engine evaluates
**Then** the timecode is recognized and displayed as-is in HH:MM:SS:FF format (parsed but not altered)

---

## EPIC 7 — macOS App (Swift/SwiftUI)

**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.

---

### Story 7.1: SwiftUI Two-Column Editor

As a **macOS user**,
I want a native two-column layout with an editor on the left and results on the right,
So that I can see my calculations and their results side by side in a familiar, platform-native interface.

**Acceptance Criteria:**

**Given** the app is launched
**When** the main window appears
**Then** it displays a two-column layout: a text editor on the left and an answer column on the right
**And** the editor uses a custom NSTextView-backed view with system font rendering

**Given** the user types a calculation on line N in the editor
**When** the engine evaluates the line
**Then** the result appears on the corresponding line N in the right-hand answer column, vertically aligned

**Given** the document has more lines than fit in the viewport
**When** the user scrolls the editor
**Then** the answer column scrolls in sync so that line alignment is always maintained

**Given** the user has Dynamic Type set to a large accessibility size in System Settings
**When** the app renders text
**Then** both the editor and answer column respect the Dynamic Type setting and scale appropriately
**And** the layout remains usable without truncation or overlap

**Given** a document with 1000+ lines
**When** the user scrolls rapidly
**Then** scrolling is smooth (60 fps) with no visible jank or dropped frames

---

### Story 7.2: Rust FFI Bridge

As a **macOS developer** integrating CalcPad,
I want a Swift package that wraps the Rust engine's C FFI into idiomatic Swift types,
So that the macOS app can call the engine safely and efficiently without manual pointer management.

**Acceptance Criteria:**

**Given** the Rust engine is compiled as a C-compatible dynamic library
**When** the Swift package is imported
**Then** a `CalcPadEngine` class is available with methods `evaluateLine(_ line: String) -> LineResult` and `evaluateSheet(_ text: String) -> SheetResult`

**Given** the Swift app calls `evaluateLine("2 + 2")`
**When** the FFI bridge processes the call
**Then** the result is returned as a Swift `LineResult` struct (not a raw pointer)
**And** all memory allocated by Rust is properly freed (no leaks)

**Given** the Swift app calls `evaluateSheet` with a multi-line string
**When** the FFI bridge processes the call
**Then** an array of `LineResult` values is returned, one per line, in order

**Given** the Rust engine returns an error for a malformed expression
**When** the Swift bridge receives the error
**Then** it is converted to a Swift `Error` type or an error variant in `LineResult` (not a crash or nil)

**Given** the app is profiled with Instruments (Leaks tool)
**When** `evaluateLine` and `evaluateSheet` are called repeatedly over many cycles
**Then** no memory leaks are detected from the FFI boundary

**Given** the bridge is used on a background thread
**When** multiple calls are made concurrently
**Then** the bridge handles thread safety correctly (either by being thread-safe or by documenting single-threaded use with appropriate assertions)

---

### Story 7.3: Syntax Highlighting

As a **macOS user**,
I want syntax highlighting in the editor that colors numbers, operators, units, variables, comments, and errors,
So that I can visually parse my calculations quickly and spot mistakes at a glance.

**Acceptance Criteria:**

**Given** the user types `monthly_rent = $1250 // housing cost`
**When** the text is rendered in the editor
**Then** `monthly_rent` is colored as a variable, `=` as an operator, `$1250` as a number/currency, and `// housing cost` as a comment
**And** each category has a distinct color

**Given** the user types an invalid expression that produces an error
**When** the text is rendered
**Then** the erroneous portion is highlighted with an error color (e.g., red underline or red text)

**Given** the system appearance is set to Light mode
**When** the editor renders syntax highlighting
**Then** colors are appropriate for a light background (sufficient contrast, readable)

**Given** the system appearance is set to Dark mode
**When** the editor renders syntax highlighting
**Then** colors are appropriate for a dark background (sufficient contrast, readable)

**Given** the user types or edits text
**When** characters are inserted or deleted
**Then** syntax highlighting updates incrementally without visible flicker or lag

**Given** the editor uses a custom `NSTextStorage` subclass
**When** the engine tokenizer produces token spans for a line
**Then** the `NSTextStorage` applies the corresponding `NSAttributedString` attributes to match those spans

---

### Story 7.4: Menu Bar Mode

As a **macOS user**,
I want CalcPad to live in the menu bar with a popover mini calculator,
So that I can perform quick calculations without switching to a full window.

**Acceptance Criteria:**

**Given** the app is running
**When** the user looks at the macOS menu bar
**Then** a CalcPad icon is visible in the menu bar area

**Given** the CalcPad icon is in the menu bar
**When** the user clicks the icon
**Then** a popover appears with a mini calculator view (editor + results)

**Given** the popover is open
**When** the user clicks anywhere outside the popover
**Then** the popover automatically dismisses

**Given** the user has configured a global hotkey for the menu bar popover
**When** the user presses the hotkey from any app
**Then** the popover toggles (opens if closed, closes if open)

**Given** the popover is open
**When** the user clicks a "Detach to Window" button in the popover
**Then** the popover closes and a full CalcPad window opens with the same content

**Given** the user is working in the popover
**When** the user types calculations
**Then** the engine evaluates them and shows results inline, just like the full editor

---

### Story 7.5: Global Hotkey (QuickCalc)

As a **macOS user**,
I want a configurable global hotkey that opens a floating QuickCalc panel,
So that I can type a quick calculation, copy the result, and dismiss without leaving my current workflow.

**Acceptance Criteria:**

**Given** the default configuration
**When** the user presses Option+Space (⌥Space) from any application
**Then** a floating QuickCalc panel appears centered on screen

**Given** the QuickCalc panel is open
**When** the user types a calculation (e.g., `1920 / 3`)
**Then** the result is displayed immediately in the panel

**Given** the QuickCalc panel shows a result
**When** the user presses Cmd+C
**Then** the result value is copied to the system clipboard

**Given** the result has been copied
**When** the user presses Escape
**Then** the QuickCalc panel dismisses

**Given** the user wants to customize the hotkey
**When** the user opens CalcPad preferences and changes the QuickCalc shortcut
**Then** the new shortcut is immediately active and the old one is deregistered

**Given** another app has registered the same hotkey
**When** CalcPad attempts to register it
**Then** CalcPad notifies the user of the conflict and allows selecting an alternative

**Given** the QuickCalc panel is open
**When** the user presses Cmd+V to paste into the frontmost app
**Then** the panel dismisses first, then the paste occurs in the previously frontmost app (or the user can manually switch)

---

### Story 7.6: Sheets Sidebar

As a **macOS user**,
I want a sidebar that lists my sheets and folders,
So that I can organize, navigate, and manage multiple calculation documents easily.

**Acceptance Criteria:**

**Given** the app is launched with multiple saved sheets
**When** the main window appears
**Then** a `NavigationSplitView` sidebar on the left shows all sheets and folders in a tree structure

**Given** the sidebar is visible
**When** the user clicks on a sheet name
**Then** the two-column editor loads that sheet's content

**Given** multiple sheets exist in the sidebar
**When** the user drags a sheet to a new position
**Then** the sheet reorders in the sidebar and the new order persists

**Given** a sheet exists in the sidebar
**When** the user swipes left on the sheet name (trackpad gesture)
**Then** a delete action is revealed; confirming it deletes the sheet

**Given** a sheet or folder exists in the sidebar
**When** the user right-clicks (or Control-clicks) on the item
**Then** a context menu appears with options such as Rename, Duplicate, Move to Folder, Delete

**Given** the user creates a new folder in the sidebar
**When** the user drags sheets into that folder
**Then** the sheets appear nested under the folder, and the folder can be collapsed/expanded

---

### Story 7.7: iCloud Sync

As a **macOS user** with multiple Apple devices,
I want my CalcPad settings and sheets to sync via iCloud,
So that I can access my calculations on any device without manual import/export.

**Acceptance Criteria:**

**Given** the user is signed into iCloud on two Macs
**When** the user changes a CalcPad setting (e.g., angle mode) on Mac A
**Then** the setting syncs to Mac B via `NSUbiquitousKeyValueStore`
**And** Mac B reflects the updated setting without restarting

**Given** the user creates a new sheet on Mac A
**When** iCloud sync completes
**Then** the sheet appears in the sidebar on Mac B

**Given** the user edits a sheet on Mac A while Mac B is offline
**When** Mac B comes online
**Then** the changes sync to Mac B and the sheet is updated

**Given** the user edits different lines of the same sheet on Mac A and Mac B simultaneously
**When** both devices sync
**Then** non-overlapping edits are merged successfully
**And** the final document contains both sets of changes

**Given** the user edits the same line on Mac A and Mac B simultaneously
**When** both devices sync
**Then** a last-write-wins strategy is applied
**And** no data corruption occurs (one version is chosen cleanly)

**Given** the user is not signed into iCloud
**When** the app launches
**Then** sheets are stored locally only and no sync errors are shown

---

### Story 7.8: Spotlight & Quick Look

As a **macOS user**,
I want CalcPad sheets to be indexed by Spotlight and previewable via Quick Look,
So that I can find and preview my calculations from Finder and Spotlight search without opening the app.

**Acceptance Criteria:**

**Given** a sheet named "Tax Calculations 2026" is saved
**When** the user searches "Tax Calculations" in Spotlight
**Then** the CalcPad sheet appears in the search results
**And** selecting it opens the sheet in CalcPad

**Given** CalcPad sheets are saved as files
**When** the app indexes them using `CSSearchableItem`
**Then** the index includes the sheet title, content keywords, and last-modified date

**Given** the user selects a `.calcpad` file in Finder and presses Space
**When** Quick Look is triggered
**Then** a formatted preview of the sheet is displayed showing the two-column layout (expressions and results)

**Given** a sheet is deleted in CalcPad
**When** the deletion is processed
**Then** the corresponding Spotlight index entry is removed

---

### Story 7.9: Alfred / Raycast Extension

As a **macOS power user**,
I want an Alfred or Raycast extension that evaluates CalcPad expressions inline,
So that I can perform quick calculations from my launcher without opening the full app.

**Acceptance Criteria:**

**Given** the user has installed the CalcPad Alfred/Raycast extension
**When** the user types `= 1920 / 3` or `calc 1920 / 3` in the launcher
**Then** the CalcPad CLI (`calcpad-cli`) is invoked and the result `640` is displayed inline

**Given** the extension shows a result
**When** the user presses Enter
**Then** the result is copied to the clipboard

**Given** the user types an expression with units, e.g., `= 5 km to miles`
**When** the extension evaluates it
**Then** the result `3.10686 miles` is displayed inline

**Given** the `calcpad-cli` binary is not installed or not found in PATH
**When** the extension attempts to invoke it
**Then** a helpful error message is displayed directing the user to install the CLI

**Given** the user types an invalid expression
**When** the extension evaluates it
**Then** an error message is displayed inline (not a crash)

---

### Story 7.10: Widgets (Notification Center)

As a **macOS user**,
I want CalcPad widgets in Notification Center and the Lock Screen,
So that I can glance at pinned calculation results without opening the app.

**Acceptance Criteria:**

**Given** the user adds a CalcPad small widget to Notification Center
**When** the widget is configured with a pinned calculation
**Then** the widget displays the current result of that calculation

**Given** the user adds a CalcPad medium widget to Notification Center
**When** the widget is configured with a pinned sheet
**Then** the widget displays the last 5 lines of that sheet with their results

**Given** a pinned calculation's inputs change (e.g., a variable in the sheet is updated)
**When** the widget timeline refreshes
**Then** the widget shows the updated result

**Given** the user adds a CalcPad Lock Screen widget
**When** the Lock Screen is displayed
**Then** the widget shows the pinned calculation result in a compact format

**Given** the user taps/clicks on the widget
**When** the system processes the interaction
**Then** CalcPad opens and navigates to the relevant sheet

---

### Story 7.11: Keyboard Shortcuts

As a **macOS user**,
I want comprehensive keyboard shortcuts for all common actions,
So that I can work efficiently without reaching for the mouse.

**Acceptance Criteria:**

**Given** the app is active
**When** the user presses Cmd+N
**Then** a new sheet is created

**Given** the app is active
**When** the user presses Cmd+T
**Then** a new tab is opened (if tabbed interface is supported)

**Given** the user selects text in the answer column
**When** the user presses Cmd+Shift+C
**Then** the selected result value is copied to the clipboard (formatted)

**Given** the app is active
**When** the user presses Cmd+Comma (⌘,)
**Then** the Preferences/Settings window opens

**Given** the user is in the editor
**When** the user presses Cmd+Enter
**Then** the current line is evaluated (or the designated action for Cmd+Enter is triggered)

**Given** the user wants to customize shortcuts
**When** the user opens System Settings > Keyboard > Keyboard Shortcuts > App Shortcuts
**Then** CalcPad menu items are available for custom shortcut assignment

**Given** a custom shortcut conflicts with a system shortcut
**When** the user assigns it
**Then** macOS standard behavior applies (app shortcut is overridden by system, or the user is warned)

---

### Story 7.12: Dark/Light/Auto Theme

As a **macOS user**,
I want CalcPad to follow the system appearance or let me manually override the theme,
So that the app always looks good in my preferred visual environment.

**Acceptance Criteria:**

**Given** the system appearance is set to Light mode and CalcPad is set to "Auto"
**When** the app renders its UI
**Then** the app uses a light theme

**Given** the system appearance is set to Dark mode and CalcPad is set to "Auto"
**When** the app renders its UI
**Then** the app uses a dark theme

**Given** the user sets CalcPad's theme to "Dark" manually
**When** the system is in Light mode
**Then** CalcPad uses a dark theme regardless of the system setting

**Given** the user sets CalcPad's theme to "Light" manually
**When** the system is in Dark mode
**Then** CalcPad uses a light theme regardless of the system setting

**Given** the system appearance changes while CalcPad is running (and CalcPad is set to "Auto")
**When** the system switches from Light to Dark (or vice versa)
**Then** CalcPad transitions to the new theme immediately without requiring a restart

**Given** the theme preference is implemented
**When** the SwiftUI view hierarchy renders
**Then** the `preferredColorScheme` modifier is applied at the root level

---

### Story 7.13: Export

As a **macOS user**,
I want to export my CalcPad sheets in multiple formats,
So that I can share my calculations with others or archive them in a standard format.

**Acceptance Criteria:**

**Given** a sheet is open with calculations and results
**When** the user selects File > Export > PDF
**Then** a PDF is generated showing the two-column layout (expressions on the left, results on the right)
**And** the file is saved to the user-selected location

**Given** a sheet is open
**When** the user selects File > Export > HTML
**Then** an HTML file is generated with a styled two-column layout that looks good in any browser

**Given** a sheet is open
**When** the user selects File > Export > Markdown
**Then** a Markdown file is generated with expressions and results formatted in a readable way (e.g., a table or code block)

**Given** a sheet is open
**When** the user selects File > Export > Plain Text
**Then** a `.txt` file is generated with expressions and results in a simple aligned format

**Given** a sheet is open
**When** the user selects File > Export > JSON
**Then** a JSON file is generated with structured data including each line's expression, result, and metadata

**Given** a sheet is open
**When** the user selects File > Export > CSV
**Then** a CSV file is generated with columns for line number, expression, and result

**Given** an export format is selected
**When** the user uses the macOS Share Sheet instead of File > Export
**Then** the same export functionality is available through the share interface

---

### Story 7.14: App Store & Notarization

As a **macOS user**,
I want CalcPad to be available on the Mac App Store and as a direct download,
So that I can install it through my preferred distribution channel with confidence that it is safe and verified.

**Acceptance Criteria:**

**Given** the app is built for distribution
**When** the build process completes
**Then** the binary is a Universal Binary supporting both Apple Silicon (arm64) and Intel (x86_64)

**Given** the app is submitted to the Mac App Store
**When** Apple reviews the submission
**Then** the app passes review because it is properly sandboxed, uses only approved APIs, and declares required entitlements

**Given** the app is distributed as a direct download (.dmg)
**When** the binary is built
**Then** it is signed with a Developer ID certificate and notarized with Apple
**And** the Hardened Runtime is enabled

**Given** a user downloads the .dmg from the website
**When** the user opens the app
**Then** macOS Gatekeeper allows the app to launch without any "unidentified developer" warning

**Given** the app uses CalcPad engine via FFI
**When** the app is sandboxed
**Then** the Rust dynamic library is embedded within the app bundle and loads correctly under sandbox restrictions

---

## EPIC 8 — Windows App (Rust + iced)

**Goal:** Fast, lightweight native Windows app — single .exe, no runtime dependencies.

---

### Story 8.1: iced Two-Column Editor

As a **Windows user**,
I want a two-column editor with input on the left and results on the right, rendered with GPU acceleration,
So that I get a fast, smooth editing experience even with large documents.

**Acceptance Criteria:**

**Given** the app is launched on Windows
**When** the main window appears
**Then** it displays a two-column layout: a custom `TextEditor` widget on the left for input and aligned `Text` widgets on the right for answers

**Given** the user types a calculation on line N
**When** the engine evaluates the line
**Then** the result appears aligned on line N in the answer column

**Given** the system has a GPU with wgpu support
**When** the app renders
**Then** it uses GPU-accelerated rendering via wgpu for smooth text and UI rendering

**Given** the system does not have compatible GPU support (e.g., older hardware, VM, RDP session)
**When** the app starts
**Then** it falls back to software rendering via tiny-skia without crashing or user intervention

**Given** a document with 1000+ lines
**When** the user scrolls and edits
**Then** the app maintains 60 fps with no perceptible lag

**Given** the user resizes the window
**When** the layout recalculates
**Then** the two columns adjust proportionally and text reflows appropriately

---

### Story 8.2: Engine Integration (Direct Linking)

As a **Windows developer** building CalcPad,
I want the iced app to directly link the Rust CalcPad engine with zero serialization overhead,
So that the app is as fast as possible with no FFI boundary costs.

**Acceptance Criteria:**

**Given** the iced app's `Cargo.toml` includes `calcpad_engine` as a dependency
**When** the app is compiled
**Then** the engine is statically linked into the single `.exe` binary

**Given** the app calls `SheetContext::new()` and `context.evaluate_line("2 + 2")`
**When** the engine processes the call
**Then** the result is returned as a native Rust type (no JSON/string serialization boundary)

**Given** the app evaluates a full sheet
**When** `context.evaluate_sheet(text)` is called
**Then** all lines are evaluated with full dependency resolution and results are returned as a `Vec<LineResult>`

**Given** the engine is used directly via `use calcpad_engine::SheetContext;`
**When** the app binary is built in release mode
**Then** the engine code is inlined and optimized by the Rust compiler as if it were part of the app itself

---

### Story 8.3: Syntax Highlighting

As a **Windows user**,
I want syntax highlighting in the editor that colors numbers, operators, units, variables, comments, and errors,
So that I can visually parse my calculations quickly.

**Acceptance Criteria:**

**Given** the user types `total_cost = $1250 + $300 // monthly`
**When** the text is rendered
**Then** `total_cost` is colored as a variable, `=` and `+` as operators, `$1250` and `$300` as numbers/currency, and `// monthly` as a comment

**Given** the user types an expression that produces an error
**When** the text is rendered
**Then** the erroneous portion is highlighted with an error indicator (e.g., red underline or distinct color)

**Given** the Windows system theme is Light mode
**When** the editor renders
**Then** syntax colors have sufficient contrast on a light background

**Given** the Windows system theme is Dark mode
**When** the editor renders
**Then** syntax colors have sufficient contrast on a dark background

**Given** the syntax highlighting is implemented
**When** colors are applied to text spans
**Then** the engine tokenizer provides the span boundaries and token types, and the iced text renderer applies corresponding colors

---

### Story 8.4: System Tray Mode

As a **Windows user**,
I want CalcPad to run in the system tray,
So that it is always available without taking up taskbar space.

**Acceptance Criteria:**

**Given** the app is running
**When** the user looks at the Windows system tray (notification area)
**Then** a CalcPad icon is visible

**Given** the tray icon is present
**When** the user right-clicks the tray icon
**Then** a context menu appears with options: Show, New Sheet, Recent Sheets (submenu), and Quit

**Given** the tray icon is present
**When** the user left-clicks the tray icon
**Then** the main CalcPad window toggles visibility (shows if hidden, hides if shown)

**Given** the user selects "New Sheet" from the tray menu
**When** the action is processed
**Then** the main window opens (if hidden) and a new empty sheet is created

**Given** the user closes the main window (clicks the X button)
**When** "background running" is enabled in settings
**Then** the app minimizes to the tray instead of quitting
**And** the tray icon remains active

**Given** the user selects "Quit" from the tray context menu
**When** the action is processed
**Then** the app fully terminates (saves state and exits)

**Given** "background running" is disabled in settings
**When** the user closes the main window
**Then** the app exits completely (no tray persistence)

---

### Story 8.5: Global Hotkey (QuickCalc)

As a **Windows user**,
I want a configurable global hotkey that opens a floating QuickCalc panel,
So that I can type a quick calculation, copy the result, and dismiss without leaving my current workflow.

**Acceptance Criteria:**

**Given** the default configuration
**When** the user presses Alt+Space from any application
**Then** a floating QuickCalc window appears centered on the active monitor

**Given** the QuickCalc window is open
**When** the user types a calculation (e.g., `1920 / 3`)
**Then** the result `640` is displayed immediately

**Given** the QuickCalc window shows a result
**When** the user presses Ctrl+C
**Then** the result value is copied to the Windows clipboard

**Given** the user has copied a result
**When** the user presses Escape
**Then** the QuickCalc window dismisses and focus returns to the previously active application

**Given** the user wants to change the hotkey
**When** the user opens CalcPad settings and configures a new shortcut
**Then** the old hotkey is unregistered via `UnregisterHotKey` and the new one is registered via `RegisterHotKey`

**Given** the configured hotkey conflicts with an existing system or application hotkey
**When** `RegisterHotKey` fails
**Then** the user is notified of the conflict and prompted to choose an alternative

**Given** the QuickCalc window is open
**When** the user clicks outside the window
**Then** the window dismisses (loses focus behavior)

---

### Story 8.6: File Management

As a **Windows user**,
I want my sheets stored as files in a standard location with a sidebar for navigation,
So that I can organize my calculations and find them through Windows Search.

**Acceptance Criteria:**

**Given** the app is installed
**When** the user creates a new sheet
**Then** it is saved as a `.calcpad` JSON file in `%APPDATA%\CalcPad\sheets\`

**Given** sheets exist in the storage directory
**When** the app's sidebar is displayed
**Then** all sheets and folders appear in a tree view matching the directory structure

**Given** the sidebar shows sheets and folders
**When** the user clicks a sheet name
**Then** the editor loads that sheet's content

**Given** sheets are stored as `.calcpad` files
**When** Windows Search indexes the directory
**Then** sheets are discoverable by file name and (if a search protocol handler is registered) by content

**Given** the user renames a sheet via the sidebar
**When** the rename is confirmed
**Then** the underlying file is renamed on disk and the sidebar updates

**Given** the user creates a folder via the sidebar
**When** the folder is created
**Then** a corresponding subdirectory is created in the storage path
**And** sheets can be dragged into it

**Given** the `.calcpad` file format
**When** a sheet is saved
**Then** the JSON contains the sheet's lines, variable state, metadata (title, created date, modified date), and format version

---

### Story 8.7: Windows Theming

As a **Windows user**,
I want CalcPad to match the Windows system theme including dark/light mode and accent colors,
So that the app looks like a native Windows 11 application.

**Acceptance Criteria:**

**Given** Windows is set to Light mode
**When** CalcPad launches
**Then** the app renders with a light theme

**Given** Windows is set to Dark mode
**When** CalcPad launches
**Then** the app renders with a dark theme

**Given** the system theme changes while CalcPad is running
**When** the registry key for `AppsUseLightTheme` changes
**Then** CalcPad detects the change and transitions to the new theme without restart

**Given** the user has a custom accent color set in Windows Settings
**When** CalcPad renders interactive elements (selections, focused borders, buttons)
**Then** the system accent color is used for those elements

**Given** the app is running on Windows 11
**When** the window renders
**Then** the custom iced theme approximates the Mica or Acrylic material design where feasible (or uses a flat theme that harmonizes with Windows 11 aesthetics)

**Given** the app is running on Windows 10
**When** the window renders
**Then** the theme degrades gracefully (no Mica/Acrylic, but still matches light/dark mode)

---

### Story 8.8: Auto-Update

As a **Windows user**,
I want CalcPad to check for updates automatically and apply them easily,
So that I always have the latest features and bug fixes without manual effort.

**Acceptance Criteria:**

**Given** the app is running and connected to the internet
**When** the app performs its periodic update check (e.g., on launch and every 24 hours)
**Then** it queries the update server for the latest version

**Given** a new version is available
**When** the update check completes
**Then** the `.msi` installer is downloaded in the background without interrupting the user

**Given** the update has been downloaded
**When** the download completes
**Then** the user is prompted with a non-blocking notification: "Update available. Restart to apply?"

**Given** the user clicks "Restart to apply"
**When** the update process starts
**Then** CalcPad saves current state, launches the installer, and closes
**And** the installer completes and relaunches CalcPad

**Given** an update has been downloaded
**When** the binary is verified
**Then** its Authenticode signature is checked against the CalcPad signing certificate before installation proceeds

**Given** the signature verification fails
**When** the update process checks the binary
**Then** the update is rejected, the downloaded file is deleted, and the user is warned about a potential integrity issue

**Given** the user declines the update
**When** the notification is dismissed
**Then** the user is not prompted again until the next version is available (or a configurable reminder period passes)

---

### Story 8.9: Portable Mode

As a **Windows user** who uses a USB drive,
I want CalcPad to run in portable mode when a marker file is present,
So that I can carry my calculator and all its data on a USB stick without installing anything on the host machine.

**Acceptance Criteria:**

**Given** a file named `portable.marker` exists in the same directory as `calcpad.exe`
**When** the app launches
**Then** all data (sheets, settings, configuration) is stored in a `data/` subdirectory alongside the `.exe` instead of in `%APPDATA%`

**Given** `portable.marker` does NOT exist alongside the `.exe`
**When** the app launches
**Then** the app uses the standard `%APPDATA%\CalcPad\` directory for storage (normal installed mode)

**Given** the app is in portable mode
**When** the user creates sheets and changes settings
**Then** all changes are written to the local `data/` directory
**And** no files are written to the system `%APPDATA%` or registry

**Given** the user copies the CalcPad folder (exe + portable.marker + data/) to a USB drive
**When** the user runs calcpad.exe from the USB on a different Windows machine
**Then** all sheets and settings from the original machine are available

**Given** the app is in portable mode
**When** the auto-update feature checks for updates
**Then** auto-update is disabled (or updates are downloaded to the portable directory) to avoid modifying the host system

---

### Story 8.10: Installer & Distribution

As a **Windows user**,
I want professional installation and distribution options,
So that I can install CalcPad through my preferred method with confidence in its authenticity.

**Acceptance Criteria:**

**Given** the release build is complete
**When** the `.msi` installer is generated via WiX
**Then** it installs CalcPad to `%ProgramFiles%\CalcPad\`, creates Start Menu shortcuts, registers the `.calcpad` file association, and sets up the uninstaller

**Given** the installer is built
**When** the `.msi` is signed
**Then** it has a valid Authenticode signature so Windows SmartScreen does not block it

**Given** the user prefers winget
**When** the user runs `winget install calcpad`
**Then** CalcPad is installed from the winget repository using the published manifest

**Given** the user wants a portable version
**When** they download the `.zip` distribution
**Then** the zip contains `calcpad.exe`, `portable.marker`, and a `data/` directory
**And** the user can run it immediately without installation

**Given** the release binary is built
**When** its file size is measured
**Then** the `.exe` is under 15 MB (including the statically linked engine)

**Given** the installer is run
**When** installation completes
**Then** no runtime dependencies are required (no .NET, no MSVC runtime, no WebView2) — CalcPad runs standalone
# CalcPad BMAD Stories — Epics 9-12

---

## EPIC 9 — Web App (React + WASM)

**Goal:** Zero-install web experience with real-time collaboration.

---

### Story 9.1: WASM Engine Integration

As a web user,
I want the CalcPad engine running natively in my browser via WebAssembly,
So that I get full calculation fidelity without installing anything.

**Acceptance Criteria:**

**Given** the calcpad-engine Rust crate is configured with wasm-pack targets
**When** the WASM module is compiled with `wasm-pack build --target web`
**Then** a valid `.wasm` binary and JS glue code are produced
**And** the gzipped WASM bundle is less than 500KB

**Given** the web app is loaded in a browser
**When** the WASM engine is initialized
**Then** it is loaded inside a dedicated Web Worker thread
**And** the main UI thread remains unblocked during computation

**Given** the WASM engine is running in a Web Worker
**When** the UI sends an evaluation request
**Then** communication occurs via `postMessage` serialization
**And** the worker returns the computed result back to the main thread via `postMessage`

**Given** the WASM engine is initialized in the worker
**When** a user types a valid CalcPad expression
**Then** the engine evaluates it with identical results to the native Rust engine
**And** all supported operations (arithmetic, units, conversions, variables, functions) produce matching output

---

### Story 9.2: CodeMirror 6 Editor

As a web user,
I want a rich text editor with CalcPad syntax highlighting and inline answers,
So that I have a polished, responsive editing experience in the browser.

**Acceptance Criteria:**

**Given** the CodeMirror 6 editor is loaded
**When** a user types CalcPad syntax (numbers, units, operators, variables, functions, comments, headers)
**Then** each token type is highlighted with distinct, theme-appropriate colors
**And** the highlighting is provided by a custom CodeMirror 6 language extension

**Given** the editor is displaying a CalcPad sheet
**When** a line produces a computed result
**Then** the answer is displayed in a custom gutter or right-aligned overlay column
**And** the answer column is visually distinct from the input column

**Given** a user is typing in the editor
**When** they pause or a keystroke occurs
**Then** the sheet is re-evaluated after a 50ms debounce period
**And** the answer column updates in place without full page re-render

**Given** a line contains a syntax error or invalid expression
**When** the evaluation runs
**Then** the error is indicated inline (e.g., red underline or gutter marker)
**And** the answer column shows an error indicator or remains blank for that line

---

### Story 9.3: PWA Support

As a web user,
I want CalcPad to work offline and be installable on my device,
So that I can use it like a native app without network dependency.

**Acceptance Criteria:**

**Given** the web app has been visited at least once
**When** the user loses network connectivity
**Then** the service worker serves the cached application shell and assets
**And** all previously loaded sheets remain accessible and editable offline

**Given** a user visits the web app in a supported browser
**When** the browser detects the PWA manifest
**Then** an "Add to Home Screen" / install prompt is available
**And** installing creates a standalone app icon on the user's device

**Given** the app is running offline
**When** a user performs a currency or unit conversion that relies on exchange rates
**Then** the most recently cached rates are used for evaluation
**And** a subtle indicator shows that rates may be stale

**Given** a user navigates to the web app for the first time on a reasonable connection
**When** the initial page load completes
**Then** the time to interactive is less than 2 seconds
**And** the app scores above 90 on Lighthouse PWA, Performance, and Best Practices audits

---

### Story 9.4: Shareable URL Links

As a web user,
I want to share my CalcPad sheet via a unique URL,
So that others can view my calculations without needing an account.

**Acceptance Criteria:**

**Given** a user has an open CalcPad sheet
**When** they click the "Share" button
**Then** a unique URL is generated in the format `calcpad.app/s/{shortcode}`
**And** the URL is copied to the clipboard with confirmation feedback

**Given** a recipient opens a shared URL
**When** the page loads
**Then** the sheet is rendered in read-only mode with full formatting and answer display
**And** the recipient cannot edit the original sheet

**Given** a user is generating a share link
**When** they enable the "Password Protection" option and set a password
**Then** recipients are prompted to enter the password before viewing
**And** incorrect passwords display an error and deny access

**Given** a user is generating a share link
**When** they set an expiration (e.g., 1 hour, 1 day, 7 days, 30 days)
**Then** the link becomes inaccessible after the expiration period
**And** visiting an expired link shows a friendly "This link has expired" message

---

### Story 9.5: Embeddable Widget

As a content creator or developer,
I want to embed a CalcPad sheet in my website via an iframe,
So that my readers can view or interact with calculations in context.

**Acceptance Criteria:**

**Given** a user has a shared CalcPad sheet
**When** they select "Embed" from the share options
**Then** an `<iframe>` embed code is generated with a copyable snippet
**And** the snippet includes the sheet URL with embed-specific query parameters

**Given** an embed code is placed on a third-party webpage
**When** the `mode=readonly` parameter is set
**Then** the widget displays the sheet with answers but disallows editing
**And** no cursor or text input is active in the iframe

**Given** an embed code is placed on a third-party webpage
**When** the `mode=interactive` parameter is set
**Then** users can edit expressions and see live-updated answers within the iframe

**Given** an embed is configured with a theme override parameter (e.g., `theme=dark`)
**When** the widget renders
**Then** it uses the specified theme regardless of the host page's styling

**Given** an embed is configured with `answers=hidden`
**When** the widget renders
**Then** the answer column is not displayed
**And** only the input expressions are visible

**Given** the iframe is placed in a responsive container
**When** the container width changes (e.g., mobile vs. desktop)
**Then** the widget resizes fluidly without horizontal scrolling or clipping

---

### Story 9.6: Collaborative Editing

As a team member,
I want to edit a CalcPad sheet simultaneously with others,
So that we can build and review calculations together in real time.

**Acceptance Criteria:**

**Given** a sheet owner enables "Edit" sharing on a sheet
**When** they share the link with collaborators
**Then** each collaborator can open the sheet and make live edits

**Given** multiple users have the same sheet open for editing
**When** one user types or modifies a line
**Then** the change appears on all other users' screens within 500ms
**And** each user's cursor is visible with a distinct color and name label

**Given** two users edit the same line simultaneously
**When** both changes arrive at the server
**Then** conflicts are resolved automatically via CRDT (Yjs or Automerge)
**And** no data is lost from either user's input

**Given** a user is on the Free plan
**When** they share a sheet for collaborative editing
**Then** up to 5 simultaneous editors are allowed
**And** additional users beyond 5 see a "sheet is full" message with an upgrade prompt

**Given** a user is on the Pro plan
**When** they share a sheet for collaborative editing
**Then** up to 25 simultaneous editors are allowed

---

### Story 9.7: User Accounts & Storage

As a returning user,
I want to create an account and have my sheets stored in the cloud,
So that I can access my work from any device.

**Acceptance Criteria:**

**Given** a new visitor to the web app
**When** they click "Sign Up"
**Then** they can register using email/password, Google OAuth, or GitHub OAuth
**And** a verified account is created and they are logged in

**Given** an authenticated user creates or edits a sheet
**When** the sheet is saved
**Then** it is persisted to Supabase cloud storage under their account
**And** the sheet is accessible from any device when logged in

**Given** a user is on the Free plan
**When** they attempt to create an 11th sheet
**Then** they are prevented from creating it
**And** a message prompts them to upgrade to Pro or delete an existing sheet

**Given** a user is on the Pro plan
**When** they create sheets
**Then** there is no limit on the number of stored sheets

**Given** a user is logged in on a new device
**When** the app loads
**Then** all their sheets are synced and available in the sheet list

---

### Story 9.8: Keyboard Shortcuts Overlay

As a power user,
I want to see all available keyboard shortcuts in an overlay panel,
So that I can learn and use shortcuts to speed up my workflow.

**Acceptance Criteria:**

**Given** a user is in the CalcPad editor
**When** they press `Ctrl+/` (or `Cmd+/` on macOS)
**Then** a modal overlay panel appears listing all keyboard shortcuts
**And** pressing `Ctrl+/` again or pressing `Escape` dismisses the overlay

**Given** the shortcuts overlay is displayed
**When** the user views the panel
**Then** shortcuts are grouped by category (e.g., Navigation, Editing, Formatting, Sharing)
**And** each shortcut shows the key combination and a brief description

**Given** the shortcuts overlay is open
**When** the user clicks outside the overlay or presses Escape
**Then** the overlay closes
**And** focus returns to the editor at the previous cursor position

---

## EPIC 10 — Notepad UX (Cross-Platform Spec)

**Goal:** Consistent editor behavior specified once, implemented per platform.

---

### Story 10.1: Headers, Comments & Labels

As a user,
I want to organize my sheets with headers, comments, and labels,
So that my calculations are readable, structured, and self-documenting.

**Acceptance Criteria:**

**Given** a user types a line beginning with `#`
**When** the line is parsed by the engine
**Then** it is treated as a section heading and is not evaluated
**And** it is rendered visually as a heading (larger/bold text) on all platforms

**Given** a user types a line beginning with `//`
**When** the line is parsed by the engine
**Then** it is treated as a comment, is not evaluated, and produces no answer
**And** it is rendered in a dimmed/muted style on all platforms

**Given** a user types a line with a prefix ending in `:` before an expression (e.g., `Rent: 1500`)
**When** the line is parsed by the engine
**Then** the label portion (before and including `:`) is not evaluated
**And** only the expression after the label is evaluated and its result displayed in the answer column

**Given** headers, comments, and labels are used in a sheet
**When** the sheet is opened on any supported platform (macOS, web, CLI)
**Then** each element type is parsed identically by the shared engine
**And** visual rendering is appropriate to each platform's conventions

---

### Story 10.2: Click-to-Copy Answer

As a user,
I want to copy a calculated answer to my clipboard by clicking on it,
So that I can quickly paste results into other applications.

**Acceptance Criteria:**

**Given** a line has a computed answer displayed in the answer column
**When** the user single-clicks the answer
**Then** the raw numeric value (without unit) is copied to the system clipboard
**And** a brief flash or checkmark animation provides visual confirmation

**Given** a line has a computed answer with a unit (e.g., `11.023 lbs`)
**When** the user double-clicks the answer
**Then** the full value with unit (e.g., `11.023 lbs`) is copied to the clipboard
**And** a brief flash or checkmark animation provides visual confirmation

**Given** a line has no computed answer (comment, header, or error)
**When** the user clicks in the answer column area for that line
**Then** nothing is copied and no feedback is shown

---

### Story 10.3: Drag to Resize Columns

As a user,
I want to drag the divider between the input and answer columns,
So that I can allocate screen space based on my content.

**Acceptance Criteria:**

**Given** the editor is displaying a sheet with input and answer columns
**When** the user hovers over the column divider
**Then** the cursor changes to a resize handle (e.g., `col-resize`)

**Given** the user presses and drags the column divider
**When** they move it left or right
**Then** the input column and answer column resize proportionally in real time
**And** content in both columns reflows to fit the new widths

**Given** the user drags the divider to an extreme position
**When** either column would become smaller than its minimum width
**Then** the divider stops at the minimum width boundary
**And** neither column collapses below a usable size

**Given** the user has repositioned the column divider
**When** they close and reopen the same sheet
**Then** the divider position is restored to their last setting
**And** the position is stored per sheet, not globally

---

### Story 10.4: Answer Column Formatting

As a user,
I want to control how numbers are displayed in the answer column,
So that results match my preferred notation and precision.

**Acceptance Criteria:**

**Given** a user opens the formatting settings
**When** they configure global decimal places (0-10), notation (standard, scientific, SI), thousands separator (comma, period, space, none), and currency symbol position (prefix, suffix)
**Then** all answers in the sheet render according to these settings

**Given** a user right-clicks (or long-presses) on a specific answer
**When** they select "Format this line" from the context menu
**Then** they can override the global formatting for that line only
**And** the per-line override persists when the sheet is saved and reopened

**Given** a global format is set and a per-line override exists
**When** the sheet is evaluated
**Then** lines with overrides use their specific formatting
**And** all other lines use the global formatting

**Given** a user selects "Scientific notation" for a line
**When** the result is `1500000`
**Then** the answer column displays `1.5e6` (or `1.5 x 10^6` depending on rendering)

**Given** a user selects "SI notation" for a line
**When** the result is `1500000`
**Then** the answer column displays `1.5M`

---

### Story 10.5: Line Numbers

As a user,
I want optional line numbers in the editor gutter,
So that I can reference specific lines when collaborating or reviewing.

**Acceptance Criteria:**

**Given** the user enables line numbers in settings
**When** the editor renders a sheet
**Then** a gutter column on the left displays sequential line numbers starting at 1
**And** the line numbers are rendered in a dimmed/muted color to avoid visual clutter

**Given** line numbers are enabled
**When** the user inserts or deletes lines
**Then** all line numbers update immediately to reflect the new sequence

**Given** line numbers are enabled
**When** the user toggles line numbers off in settings
**Then** the gutter column is hidden
**And** the input column expands to reclaim the gutter space

**Given** the user has toggled line numbers on or off
**When** they reopen the app
**Then** their line number preference is remembered and applied

---

### Story 10.6: Find & Replace

As a user,
I want to find and replace text in my sheet,
So that I can quickly locate expressions and make bulk edits.

**Acceptance Criteria:**

**Given** a user is in the editor
**When** they press `Ctrl+F` (or `Cmd+F` on macOS)
**Then** a find bar appears at the top or bottom of the editor
**And** focus moves to the search input field

**Given** the find bar is open and the user types a search term
**When** matches exist in the sheet
**Then** all matching occurrences are highlighted in the editor
**And** the current match is distinctly highlighted (e.g., different color)
**And** a match count indicator shows "N of M" matches

**Given** the user presses `Ctrl+H` (or `Cmd+H` on macOS)
**When** the replace bar appears
**Then** it includes both a search field and a replacement field
**And** "Replace" and "Replace All" buttons are available

**Given** a user clicks "Replace All"
**When** matches exist
**Then** all occurrences are replaced simultaneously
**And** the operation is recorded as a single undoable action (Ctrl+Z reverts all replacements)

**Given** a user enables the regex toggle in the find bar
**When** they type a regular expression pattern
**Then** the search uses regex matching
**And** invalid regex shows an error indicator without crashing

---

## EPIC 11 — CLI Tool

**Goal:** Command-line tool for scripting and automation, same Rust engine.

---

### Story 11.1: Single Expression Evaluation

As a developer or power user,
I want to evaluate a single CalcPad expression from the command line,
So that I can quickly compute values without opening an editor.

**Acceptance Criteria:**

**Given** the user has `calcpad` installed
**When** they run `calcpad "5kg in lbs"`
**Then** the output is `11.023 lbs` (or equivalent precision) printed to stdout
**And** the process exits with code 0

**Given** the user passes an invalid expression
**When** they run `calcpad "5kg in ???"`
**Then** an error message is printed to stderr describing the issue
**And** the process exits with code 1

**Given** the user passes a plain arithmetic expression
**When** they run `calcpad "2 + 3 * 4"`
**Then** the output is `14` printed to stdout
**And** the process exits with code 0

---

### Story 11.2: Pipe / Stdin Mode

As a developer,
I want to pipe input into CalcPad from other commands,
So that I can integrate calculations into shell scripts and pipelines.

**Acceptance Criteria:**

**Given** the user pipes a single expression to calcpad
**When** they run `echo "100 USD in EUR" | calcpad`
**Then** the converted value is printed to stdout
**And** the process exits with code 0

**Given** the user pipes multi-line input to calcpad
**When** they run `cat mysheet.cp | calcpad` or pipe multiple lines via heredoc
**Then** each line is evaluated as part of a sheet (variables carry forward between lines)
**And** each line's result is printed on a corresponding output line

**Given** stdin contains a mix of expressions, comments, and blank lines
**When** calcpad processes the input
**Then** comments and blank lines produce empty output lines
**And** expressions produce their computed results on the corresponding lines

---

### Story 11.3: Output Formats

As a developer,
I want to control the output format of CalcPad results,
So that I can integrate results into different toolchains and data pipelines.

**Acceptance Criteria:**

**Given** the user runs calcpad without a `--format` flag
**When** the expression is evaluated
**Then** the output is in plain text format (default), showing only the result value and unit

**Given** the user runs `calcpad --format json "5kg in lbs"`
**When** the expression is evaluated
**Then** the output is valid JSON containing at minimum: `value`, `unit`, and `type` fields
**And** example output resembles `{"value": 11.023, "unit": "lbs", "type": "mass"}`

**Given** the user runs `calcpad --format csv` with multi-line stdin
**When** the input is evaluated
**Then** the output is CSV formatted with columns for line number, expression, value, and unit
**And** the output can be directly imported into spreadsheet software

**Given** the user specifies an unrecognized `--format` value
**When** the command is executed
**Then** an error message lists the valid format options (plain, json, csv)
**And** the process exits with code 1

---

### Story 11.4: Interactive REPL

As a power user,
I want an interactive CalcPad session in my terminal,
So that I can iteratively build up calculations with persistent variables.

**Acceptance Criteria:**

**Given** the user runs `calcpad --repl`
**When** the REPL starts
**Then** a prompt is displayed (e.g., `> `) indicating readiness for input
**And** the REPL session begins with a clean variable scope

**Given** the user is in the REPL
**When** they type `x = 10` and press Enter, then type `x * 5` and press Enter
**Then** the first line outputs `10` and the second line outputs `50`
**And** variables persist for the duration of the session

**Given** the user is in the REPL
**When** they press the Up arrow key
**Then** the previous expression is recalled into the input line
**And** history navigation (up/down) works via rustyline or equivalent library

**Given** the user is in the REPL
**When** they type `exit`, `quit`, or press `Ctrl+D`
**Then** the REPL session ends cleanly
**And** the process exits with code 0

**Given** the user enters an invalid expression in the REPL
**When** they press Enter
**Then** an error message is displayed inline
**And** the REPL continues to accept input (does not crash or exit)

---

### Story 11.5: Distribution

As a user,
I want to install CalcPad CLI easily on my platform,
So that I can get started without complex build steps.

**Acceptance Criteria:**

**Given** a user has Rust and Cargo installed
**When** they run `cargo install calcpad-cli`
**Then** the CLI binary is compiled and installed into their Cargo bin directory
**And** running `calcpad --version` outputs the installed version

**Given** a macOS user has Homebrew installed
**When** they run `brew install calcpad`
**Then** the CLI binary is installed and available on their PATH
**And** running `calcpad --version` outputs the installed version

**Given** the CLI is compiled for a target platform
**When** the binary is produced
**Then** it is a single statically-linked binary with no runtime dependencies
**And** the binary size is less than 5MB

**Given** a user downloads the pre-built binary for their platform (macOS, Linux, Windows)
**When** they place it on their PATH and run `calcpad --help`
**Then** usage information is displayed with all supported flags and subcommands

---

## EPIC 12 — Plugin & Extension System

**Goal:** Let power users extend CalcPad.

---

### Story 12.1: Plugin API (Rust Trait)

As a Rust developer,
I want to implement a `CalcPadPlugin` trait to extend CalcPad with custom functions, units, and variables,
So that I can add domain-specific capabilities to the engine.

**Acceptance Criteria:**

**Given** the `CalcPadPlugin` trait is defined in the calcpad-engine crate
**When** a developer implements the trait
**Then** they can provide implementations for `register_functions()`, `register_units()`, and `register_variables()`
**And** each method receives a registry to add new capabilities

**Given** a developer compiles their plugin as a dynamic library (.dylib on macOS, .dll on Windows, .so on Linux)
**When** CalcPad loads the plugin at startup
**Then** the registered functions, units, and variables are available in expressions
**And** conflicts with built-in names are reported as warnings

**Given** a developer compiles their plugin as a WASM module
**When** CalcPad loads the WASM plugin
**Then** the registered functions, units, and variables are available in expressions
**And** the WASM plugin runs in a sandboxed environment with no filesystem or network access

**Given** a plugin registers a custom function (e.g., `bmi(weight, height)`)
**When** a user types `bmi(80kg, 1.8m)` in a sheet
**Then** the plugin function is invoked with the provided arguments
**And** the result is displayed in the answer column

---

### Story 12.2: Scripting Layer (Rhai or mlua)

As a power user without Rust experience,
I want to write lightweight scripts to add custom functions to CalcPad,
So that I can extend functionality without compiling native code.

**Acceptance Criteria:**

**Given** a user creates a script file in the `.calcpad-plugins/` directory
**When** CalcPad starts or reloads plugins
**Then** all `.rhai` (or `.lua`) files in the directory are loaded and executed
**And** functions registered by the scripts become available in expressions

**Given** a script uses the API `calcpad.add_function("double", |x| x * 2)`
**When** a user types `double(21)` in a sheet
**Then** the answer column displays `42`

**Given** a script attempts to access the filesystem, network, or system commands
**When** the script is executed
**Then** the sandboxed runtime blocks the operation
**And** an error is reported without crashing the application

**Given** a script contains a syntax error or runtime error
**When** CalcPad attempts to load it
**Then** an error message identifies the problematic script file and line number
**And** all other valid plugins continue to load and function normally

**Given** a script registers a function with the same name as a built-in
**When** the user evaluates an expression using that name
**Then** the built-in takes precedence (or a configurable priority is respected)
**And** a warning is logged about the naming conflict

---

### Story 12.3: Plugin Marketplace

As a user,
I want to browse and install community plugins from a marketplace,
So that I can extend CalcPad with curated, ready-to-use capabilities.

**Acceptance Criteria:**

**Given** a user navigates to `calcpad.app/plugins`
**When** the page loads
**Then** a directory of available plugins is displayed
**And** plugins are organized into categories (finance, science, engineering, crypto, dev)

**Given** a user finds a plugin they want
**When** they click the "Install" button
**Then** the plugin is downloaded and placed in the correct plugin directory
**And** it is available in CalcPad after the next reload or immediately if hot-reload is supported

**Given** plugins are listed in the marketplace
**When** a user views a plugin detail page
**Then** they see: description, author, version, install count, user ratings, and a readme
**And** they can submit their own star rating

**Given** a user has installed a marketplace plugin
**When** a new version is published by the author
**Then** the user is notified of the available update
**And** they can update with a single click

---

### Story 12.4: Built-in Plugin: Stock Prices

As a finance-focused user,
I want to look up stock prices directly in CalcPad expressions,
So that I can incorporate real-time market data into my calculations.

**Acceptance Criteria:**

**Given** a user types `AAPL price` in a sheet
**When** the expression is evaluated
**Then** the current price of AAPL is fetched and displayed in the answer column
**And** the result includes the currency unit (e.g., `192.53 USD`)

**Given** a user types `AAPL price on 2024-06-15` in a sheet
**When** the expression is evaluated
**Then** the historical closing price for that date is fetched and displayed
**And** if the date falls on a non-trading day, the most recent prior trading day's close is used

**Given** a user is on the Free plan
**When** they query a stock price
**Then** the price is delayed by 15 minutes
**And** an indicator shows the data is delayed (e.g., `192.53 USD (15min delay)`)

**Given** a user is on the Pro plan
**When** they query a stock price
**Then** the price is real-time (or near real-time)
**And** no delay indicator is shown

**Given** a user types an invalid ticker symbol
**When** the expression is evaluated
**Then** an error message indicates the ticker was not found
**And** no partial or incorrect data is displayed

**Given** stock prices from major exchanges (NYSE, NASDAQ, LSE, TSE, etc.) are supported
**When** a user queries a valid ticker from any supported exchange
**Then** the correct price is returned with the appropriate currency

---

### Story 12.5: Built-in Plugin: Crypto & DeFi

As a crypto-focused user,
I want to query cryptocurrency prices and DeFi protocol data in CalcPad expressions,
So that I can incorporate live blockchain data into my calculations.

**Acceptance Criteria:**

**Given** a user types `ETH gas price` in a sheet
**When** the expression is evaluated
**Then** the current Ethereum gas price is fetched (from an API such as Etherscan or similar)
**And** the result is displayed in gwei (e.g., `25 gwei`)

**Given** a user types `AAVE lending rate` in a sheet
**When** the expression is evaluated
**Then** the current AAVE lending rate is fetched from DeFi Llama or the AAVE API
**And** the result is displayed as a percentage (e.g., `3.45%`)

**Given** crypto price data is sourced from CoinGecko
**When** a user types `BTC price` or `ETH price`
**Then** the current market price is returned in USD (or user's preferred currency)
**And** the data source is CoinGecko's public API

**Given** DeFi protocol data is sourced from DeFi Llama
**When** a user queries lending rates, TVL, or yield data
**Then** the data is fetched from DeFi Llama's API
**And** the result is formatted appropriately (percentage for rates, currency for TVL)

**Given** the API is unreachable or rate-limited
**When** a user queries crypto or DeFi data
**Then** a cached value is used if available (with a staleness indicator)
**And** if no cached value exists, an error message indicates the data source is unavailable

**Given** a user types an unsupported token or protocol name
**When** the expression is evaluated
**Then** an error message indicates the token or protocol was not recognized
**And** suggestions for similar valid names are shown if possible
# CalcPad — BMAD Stories: Epics 13–16

---

## EPIC 13 — Performance & Reliability

**Goal:** Instant evaluation, never lose work.

---

### Story 13.1: Real-Time Evaluation (< 16 ms per Line)

As a CalcPad user,
I want every line to evaluate in under 16 ms so that a 500-line sheet completes in under 100 ms,
So that calculations feel instantaneous and I never experience lag while typing.

**Acceptance Criteria:**

**Given** a sheet containing 500 lines of mixed arithmetic, unit conversions, and variable references
**When** the engine evaluates the entire sheet
**Then** total evaluation time is under 100 ms on an Apple M1 or equivalent modern x86 processor
**And** no individual line takes longer than 16 ms to evaluate

**Given** a user is typing on a line that triggers re-evaluation
**When** a keystroke modifies an expression
**Then** the result column updates within one animation frame (< 16 ms) for that line
**And** the UI thread is never blocked long enough to drop a frame

**Given** a sheet with 1 000 lines of complex expressions including nested unit conversions and currency lookups
**When** the engine evaluates the full sheet
**Then** total evaluation completes in under 250 ms
**And** a performance warning is logged if any single line exceeds 16 ms

---

### Story 13.2: Dependency Graph & Lazy Evaluation

As a CalcPad user,
I want only the lines affected by a change to re-evaluate,
So that editing a variable on line 5 of a 500-line sheet does not re-evaluate unrelated lines.

**Acceptance Criteria:**

**Given** the engine maintains a directed acyclic graph (DAG) of variable dependencies across all lines
**When** a variable's value changes on a specific line
**Then** only lines that directly or transitively depend on that variable are marked dirty and re-evaluated
**And** lines with no dependency on the changed variable are not re-evaluated

**Given** a sheet where line 3 defines `tax = 0.08` and lines 10, 25, and 40 reference `tax`
**When** the user changes `tax` to `0.10` on line 3
**Then** only lines 3, 10, 25, 40 (and any lines that depend on those) are re-evaluated
**And** all other lines retain their cached results without recomputation

**Given** a circular dependency is introduced (e.g., `a = b + 1` and `b = a + 1`)
**When** the engine builds or updates the DAG
**Then** the circular dependency is detected before evaluation
**And** an error is displayed on the offending lines indicating a circular reference
**And** the rest of the sheet continues to evaluate normally

**Given** a line is deleted that other lines depend on
**When** the DAG is updated
**Then** all lines that referenced the deleted variable are marked dirty
**And** those lines display an "undefined variable" error on next evaluation

---

### Story 13.3: Web Worker Offloading (Web Only)

As a web CalcPad user,
I want the calculation engine to run in a Web Worker,
So that the main thread remains free for rendering and the UI never freezes during heavy computation.

**Acceptance Criteria:**

**Given** the web version of CalcPad is loaded in a browser
**When** the application initializes
**Then** the calcpad-engine WASM module is instantiated inside a dedicated Web Worker
**And** the main thread contains no evaluation logic

**Given** a user types an expression on the main thread
**When** the input is sent to the Web Worker for evaluation
**Then** the result is returned via postMessage to the main thread
**And** the main thread renders the result without blocking on computation

**Given** the Web Worker is processing a large sheet (500+ lines)
**When** the user continues typing on the main thread
**Then** keystrokes are captured and rendered with zero perceptible lag
**And** pending evaluation requests are queued or debounced so the latest input is always processed

**Given** the Web Worker crashes or becomes unresponsive
**When** the main thread detects the worker has not responded within 5 seconds
**Then** the worker is terminated and a new worker is spawned
**And** the current sheet state is re-sent to the new worker for evaluation
**And** the user sees a brief non-blocking notification that the engine was restarted

---

### Story 13.4: Crash Recovery

As a CalcPad user,
I want my work to be auto-saved continuously,
So that I never lose more than 2 seconds of work after a crash or unexpected quit.

**Acceptance Criteria:**

**Given** the user is actively editing a sheet on macOS
**When** the application is running
**Then** NSDocument auto-save is leveraged to persist the document state at least every 2 seconds
**And** the auto-saved state includes all sheet content, variable values, and cursor position

**Given** the user is actively editing a sheet on Windows
**When** the application is running
**Then** a journal file is written to `%APPDATA%/CalcPad/recovery/` at least every 2 seconds
**And** the journal file contains the full sheet content and metadata needed for restoration

**Given** the user is actively editing a sheet on the web
**When** the application is running
**Then** sheet state is persisted to localStorage at least every 2 seconds
**And** if the user is logged in with cloud sync enabled, the state is also queued for server sync

**Given** CalcPad crashes or is force-quit
**When** the user relaunches the application
**Then** the recovery file is detected and the user is prompted to restore their session
**And** the restored session contains all content from no more than 2 seconds before the crash
**And** the user can choose to discard the recovered state and start fresh

**Given** multiple sheets were open at the time of a crash
**When** the user relaunches the application
**Then** all sheets with recovery data are listed for restoration
**And** the user can selectively restore individual sheets

---

### Story 13.5: Test Suite

As a CalcPad developer,
I want a comprehensive test suite with > 95% code coverage on calcpad-engine,
So that regressions are caught early and every function, conversion, and multi-line interaction is verified.

**Acceptance Criteria:**

**Given** the calcpad-engine crate
**When** the full test suite is executed via `cargo test`
**Then** code coverage as measured by `cargo-tarpaulin` or equivalent exceeds 95% of lines in the engine crate
**And** all tests pass with zero failures

**Given** every public function and unit conversion in calcpad-engine
**When** unit tests are written
**Then** each function has at least one test for the happy path
**And** each function has at least one test for error/edge cases (division by zero, overflow, unknown unit, etc.)
**And** every supported unit conversion pair has a dedicated round-trip accuracy test

**Given** multi-line sheets with variables, dependencies, and mixed expressions
**When** integration tests are executed
**Then** end-to-end evaluation of representative sheets produces correct results
**And** integration tests cover: variable assignment and reference, chained dependencies, circular reference detection, unit conversion across lines, and currency conversion

**Given** the engine's parsing and evaluation logic
**When** property-based tests are run via `proptest`
**Then** random valid expressions always produce a result without panicking
**And** random malformed expressions produce a graceful error without panicking
**And** evaluation of `a + b` always equals evaluation of `b + a` for numeric values

**Given** the benchmark suite defined with `criterion`
**When** benchmarks are executed
**Then** benchmarks exist for: single-line evaluation, 100-line sheet evaluation, 500-line sheet evaluation, DAG rebuild, and unit conversion lookup
**And** benchmark results are recorded so regressions can be detected in CI

---

## EPIC 14 — Accessibility & Internationalization

**Goal:** Make CalcPad usable by everyone regardless of ability, language, or locale.

---

### Story 14.1: Screen Reader Support

As a visually impaired user,
I want CalcPad to work with screen readers on every platform,
So that I can perform calculations with the same efficiency as sighted users.

**Acceptance Criteria:**

**Given** the macOS native application
**When** VoiceOver is enabled and the user navigates the sheet
**Then** every input line has an `accessibilityLabel` that reads the expression text
**And** every result has an `accessibilityLabel` that reads the computed value with its unit
**And** error states are announced (e.g., "Line 5: error, division by zero")

**Given** the Windows application built with iced
**When** a screen reader using UI Automation is active
**Then** all input fields, results, buttons, and menus expose proper UI Automation properties (Name, Role, Value)
**And** the user can navigate lines, hear results, and interact with menus entirely through the screen reader

**Given** the web application
**When** a screen reader (NVDA, JAWS, or VoiceOver for web) is active
**Then** all interactive elements have appropriate ARIA roles and labels
**And** the result column uses `aria-live="polite"` so updated results are announced without interrupting the user
**And** navigation landmarks are defined for the editor area, sidebar, and toolbar

**Given** any platform
**When** the user adds, removes, or reorders lines
**Then** the screen reader announces the change (e.g., "Line inserted after line 4" or "Line 7 deleted")

---

### Story 14.2: High Contrast Mode

As a user with low vision,
I want a high-contrast theme that respects my system accessibility settings,
So that I can clearly distinguish all UI elements without straining.

**Acceptance Criteria:**

**Given** a dedicated high-contrast theme exists in CalcPad
**When** the user selects it from Settings > Appearance
**Then** all text meets WCAG AAA contrast ratios (minimum 7:1 for normal text, 4.5:1 for large text)
**And** the input area, result column, errors, and UI chrome are all clearly distinguishable

**Given** the user has enabled high-contrast mode in their operating system (macOS, Windows, or browser)
**When** CalcPad is launched or the system setting changes while CalcPad is running
**Then** CalcPad automatically switches to its high-contrast theme
**And** the user can override the automatic selection in CalcPad settings if preferred

**Given** the high-contrast theme is active
**When** the user views error highlights, selection highlights, and syntax differentiation
**Then** colors are not the sole means of conveying information — patterns, borders, or text labels supplement color
**And** focus indicators are bold and clearly visible (minimum 3px solid border)

---

### Story 14.3: Keyboard-Only Operation

As a user who cannot use a mouse,
I want to perform every CalcPad action using only the keyboard,
So that I have full access to all features without requiring a pointing device.

**Acceptance Criteria:**

**Given** the CalcPad application on any platform
**When** the user presses Tab
**Then** focus moves through all interactive elements in a logical, predictable order: toolbar, sheet lines, sidebar, dialogs
**And** a visible focus indicator is always present on the focused element

**Given** the user is navigating the sheet
**When** keyboard shortcuts are used
**Then** Arrow Up/Down moves between lines
**And** Enter creates a new line below the current line
**And** Cmd/Ctrl+Shift+K deletes the current line
**And** Cmd/Ctrl+/ toggles a comment on the current line
**And** Escape dismisses any open dropdown, autocomplete, or dialog

**Given** any dropdown, autocomplete suggestion list, or modal dialog is visible
**When** the user interacts via keyboard
**Then** Arrow keys navigate options, Enter selects, and Escape dismisses
**And** focus is trapped inside modal dialogs until they are dismissed

**Given** the complete set of CalcPad features (file operations, export, settings, theme switching, sharing)
**When** the feature is audited for keyboard access
**Then** every feature is reachable and operable without a mouse
**And** no interaction requires hover, drag-and-drop, or right-click as the only means of access

---

### Story 14.4: RTL Language Support

As a user who writes in Arabic or Hebrew,
I want the CalcPad interface to mirror for right-to-left languages,
So that the layout feels natural and text flows in the correct direction.

**Acceptance Criteria:**

**Given** the user's system locale or CalcPad language preference is set to an RTL language (Arabic or Hebrew)
**When** the application renders
**Then** the overall layout is mirrored: sidebar appears on the right, toolbar items are right-aligned, text input is right-aligned
**And** the result column position mirrors accordingly

**Given** an RTL layout is active
**When** the user types numeric expressions (e.g., `150 * 3`)
**Then** numbers and mathematical operators remain in left-to-right order
**And** variable names containing Latin characters remain LTR within the RTL context (bidirectional text handling)

**Given** an RTL layout is active
**When** mixed content appears (Arabic label text with numeric expressions)
**Then** the Unicode Bidirectional Algorithm is applied correctly
**And** the visual order of the expression is mathematically readable

**Given** the user switches between an RTL and LTR language in settings
**When** the language change is applied
**Then** the layout mirrors or un-mirrors immediately without requiring an application restart

---

### Story 14.5: Localization Framework

As a CalcPad user who speaks a language other than English,
I want the entire UI translated into my language,
So that I can use CalcPad comfortably in my native language.

**Acceptance Criteria:**

**Given** the macOS native application
**When** localization is implemented
**Then** all user-facing strings are defined in `.strings` and `.stringsdict` files
**And** pluralization rules use `.stringsdict` for correct plural forms per language

**Given** the Windows and web applications
**When** localization is implemented
**Then** all user-facing strings are managed through `fluent-rs` (Project Fluent `.ftl` files)
**And** message references and parameterized strings are used for dynamic content (e.g., "Sheet {$name} saved")

**Given** the localization framework is in place
**When** all strings are translated
**Then** the following 8 languages are fully supported: English (EN), French (FR), German (DE), Spanish (ES), Italian (IT), Portuguese-Brazil (PT-BR), Chinese Simplified (ZH), Korean (KO)
**And** each language has 100% string coverage — no fallback to English for any UI element

**Given** the user changes their language preference in CalcPad settings
**When** the change is applied
**Then** all menus, labels, tooltips, error messages, onboarding text, and template descriptions switch to the selected language
**And** the change takes effect immediately without restarting the application

**Given** a new language is added in the future
**When** a developer adds the translation files
**Then** no code changes are required beyond adding the new `.strings`/`.stringsdict` or `.ftl` file and registering the locale
**And** the language automatically appears in the language selection dropdown

---

## EPIC 15 — Monetization & Onboarding

**Goal:** Establish sustainable revenue with a generous free tier and frictionless onboarding.

---

### Story 15.1: Free Tier

As a CalcPad user on the free tier,
I want access to core calculation features without payment,
So that I can evaluate CalcPad fully and use it for everyday calculations at no cost.

**Acceptance Criteria:**

**Given** a user is on the free tier
**When** they use the application
**Then** they can perform unlimited calculations with no daily or monthly cap
**And** all built-in units and currency conversions are available
**And** they can create and manage up to 10 sheets

**Given** a free-tier user attempts to create an 11th sheet
**When** the sheet creation action is triggered
**Then** a non-intrusive prompt explains the 10-sheet limit
**And** the prompt offers an upgrade path to Pro
**And** existing sheets remain fully accessible and editable

**Given** a free-tier user
**When** they access appearance settings
**Then** the basic set of built-in themes is available for selection
**And** Pro-only themes are visible but marked with a Pro badge and are not selectable

**Given** a free-tier user
**When** they export a sheet
**Then** export to plain text and Markdown formats is available
**And** PDF and HTML export options are visible but marked as Pro features

**Given** a free-tier user with no internet connection
**When** they use CalcPad offline
**Then** all free-tier features work fully offline including calculations, unit conversions, and local sheet storage

---

### Story 15.2: Pro Tier

As a CalcPad Pro subscriber,
I want access to advanced features including unlimited sheets, cloud sync, and collaboration,
So that I can use CalcPad as my primary professional calculation tool.

**Acceptance Criteria:**

**Given** a user has an active Pro subscription or one-time Pro purchase
**When** they use the application
**Then** they can create unlimited sheets with no cap
**And** cloud sync is enabled across all their devices
**And** all themes including premium themes are available
**And** custom theme creation is unlocked

**Given** a Pro user
**When** they access currency and financial features
**Then** historical currency rates are available for past-date conversions
**And** stock/ticker lookups are available for real-time financial data

**Given** a Pro user
**When** they use collaboration features
**Then** they can share sheets with other users for real-time collaborative editing
**And** they can set view-only or edit permissions on shared sheets

**Given** a Pro user
**When** they export a sheet
**Then** all export formats are available: plain text, Markdown, PDF, and HTML
**And** exported PDFs include formatted results with proper typography

**Given** a Pro user
**When** they access developer features
**Then** CLI access to calcpad-engine is available for scripting and automation
**And** API access is available for programmatic sheet evaluation
**And** the plugin system is available for extending CalcPad with custom functions

**Given** a Pro subscription expires or is cancelled
**When** the user continues using CalcPad
**Then** sheets beyond the 10-sheet limit become read-only (not deleted)
**And** cloud-synced data remains accessible locally
**And** Pro-only features are gracefully disabled with clear upgrade prompts

---

### Story 15.3: Pricing Configuration

As the CalcPad product team,
I want platform-appropriate pricing with both subscription and one-time purchase options,
So that pricing aligns with platform norms and maximizes conversion.

**Acceptance Criteria:**

**Given** the web version of CalcPad
**When** a user views the pricing page or upgrade prompt
**Then** the free tier is clearly presented with its feature set
**And** Pro is offered at $5/month or $48/year (annual saves ~20%)
**And** the annual option is highlighted as the recommended choice

**Given** the macOS version of CalcPad
**When** a user views the upgrade prompt
**Then** Pro is offered as a one-time purchase of $29
**And** the Setapp distribution channel is mentioned as an alternative for Setapp subscribers
**And** there is no recurring subscription required for macOS Pro

**Given** the Windows version of CalcPad
**When** a user views the upgrade prompt
**Then** Pro is offered as a one-time purchase of $19
**And** there is no recurring subscription required for Windows Pro

**Given** any platform's purchase or subscription flow
**When** the user initiates a purchase
**Then** the payment is processed through the platform-appropriate mechanism (App Store for macOS, Stripe for web, Microsoft Store or Stripe for Windows)
**And** the Pro entitlement is activated immediately upon successful payment
**And** a confirmation is displayed with details of the purchase

**Given** a web Pro subscriber who also uses the macOS or Windows app
**When** they sign in on the desktop app
**Then** their Pro entitlement from the web subscription is recognized
**And** they are not required to purchase Pro again on the desktop platform

---

### Story 15.4: Onboarding Tutorial

As a first-time CalcPad user,
I want an interactive walkthrough that teaches me the key features,
So that I can quickly become productive without reading documentation.

**Acceptance Criteria:**

**Given** a user launches CalcPad for the first time
**When** the application finishes loading
**Then** an interactive onboarding tutorial begins with a welcome step (Step 1 of 5)
**And** the tutorial highlights the relevant UI area for each step

**Given** the onboarding tutorial is active
**When** the user progresses through the 5 steps
**Then** Step 1 covers basic math: typing expressions and seeing instant results
**And** Step 2 covers unit conversions: demonstrates converting between units (e.g., `5 kg to lb`)
**And** Step 3 covers variables: assigning and referencing values across lines
**And** Step 4 covers currency conversion: using live currency rates (e.g., `100 USD to EUR`)
**And** Step 5 covers sharing and export: how to share a sheet or export results

**Given** the onboarding tutorial is displayed
**When** the user clicks "Skip" at any step
**Then** the tutorial is dismissed immediately
**And** a "Replay Tutorial" option is available in the Help menu

**Given** a user who previously skipped or completed the tutorial
**When** they select Help > Replay Tutorial
**Then** the full 5-step tutorial restarts from Step 1

**Given** the onboarding tutorial is active on any step
**When** the user completes the prompted action (e.g., types an expression and sees a result)
**Then** the tutorial automatically advances to the next step
**And** a subtle animation or highlight confirms the successful action

---

### Story 15.5: Template Library

As a CalcPad user,
I want access to pre-built templates for common calculation tasks,
So that I can start with a useful structure instead of a blank sheet.

**Acceptance Criteria:**

**Given** the user opens the template library (File > New from Template or from the home screen)
**When** the library is displayed
**Then** the following templates are available: Budget Planning, Trip Expenses, Recipe Scaling, Developer Conversions, Freelancer Invoicing, Compound Interest, and Timezone Planner
**And** each template has a title, brief description, and preview of its contents

**Given** a user selects a template (e.g., "Budget Planning")
**When** they click "Use Template" or equivalent action
**Then** a new sheet is created as a clone of the template
**And** the cloned sheet is fully editable and independent of the original template
**And** placeholder values in the template are clearly marked for the user to customize

**Given** the "Budget Planning" template
**When** a user opens it
**Then** it includes labeled sections for income, fixed expenses, variable expenses, and a calculated total/savings line using variables

**Given** the "Developer Conversions" template
**When** a user opens it
**Then** it includes common conversions: bytes to megabytes/gigabytes, milliseconds to seconds, pixels to rem, hex to decimal, and color format examples

**Given** the "Compound Interest" template
**When** a user opens it
**Then** it includes variables for principal, rate, period, and compounding frequency
**And** the formula is expressed using CalcPad syntax with a clearly labeled result

**Given** a free-tier user
**When** they access the template library
**Then** all templates are available to free-tier users (templates are not gated behind Pro)
**And** the cloned sheet counts toward the user's 10-sheet limit

---

## EPIC 16 — Analytics, Feedback & Iteration

**Goal:** Learn from usage to improve CalcPad while respecting user privacy.

---

### Story 16.1: Privacy-Respecting Analytics

As the CalcPad product team,
I want anonymous, privacy-respecting analytics on feature usage and error rates,
So that we can make data-informed decisions without compromising user trust.

**Acceptance Criteria:**

**Given** analytics are implemented in CalcPad
**When** any analytics event is recorded
**Then** no personally identifiable information (PII) is included — no names, emails, IP addresses, or sheet contents
**And** events are limited to: feature usage counts, session duration, error type and frequency, and platform/version metadata

**Given** the macOS application
**When** analytics are active
**Then** events are sent via TelemetryDeck or PostHog (self-hosted or privacy mode)
**And** the SDK is configured to anonymize all identifiers

**Given** the web application
**When** analytics are active
**Then** events are sent via Plausible or PostHog (self-hosted or privacy mode)
**And** no cookies are used for analytics tracking

**Given** a user who does not want to participate in analytics
**When** they navigate to Settings > Privacy
**Then** an "Opt out of analytics" toggle is available
**And** disabling the toggle immediately stops all analytics collection with no data sent after opting out
**And** the opt-out preference persists across sessions and application updates

**Given** CalcPad's analytics implementation
**When** reviewed for regulatory compliance
**Then** it complies with GDPR (no data collected without legal basis, opt-out honored, no cross-site tracking)
**And** it complies with CCPA (user can opt out of data sale — though no data is sold)
**And** a clear privacy policy is accessible from Settings > Privacy describing exactly what is collected

---

### Story 16.2: In-App Feedback

As a CalcPad user,
I want to send feedback directly from within the app,
So that I can report issues or suggest improvements without leaving CalcPad.

**Acceptance Criteria:**

**Given** the user wants to send feedback
**When** they navigate to Help > Send Feedback
**Then** a feedback dialog opens with a text field for a description

**Given** the feedback dialog is open
**When** the user types a description
**Then** an optional "Attach Screenshot" button is available
**And** clicking it captures a screenshot of the current CalcPad window (not the full screen)
**And** the screenshot preview is shown in the dialog so the user can review it before sending

**Given** the feedback dialog is open
**When** the user reviews the submission
**Then** anonymized system information is pre-filled and visible: OS version, CalcPad version, and platform (macOS/Windows/web)
**And** no PII such as name or email is included unless the user voluntarily adds it to the description
**And** sheet contents are never included in the feedback payload

**Given** the user clicks "Send"
**When** the feedback is submitted
**Then** a confirmation message is displayed: "Thank you — your feedback has been sent"
**And** the feedback is delivered to the team's feedback collection system (e.g., a backend endpoint that routes to a project tracker)
**And** if the device is offline, the feedback is queued and sent when connectivity is restored

---

### Story 16.3: Changelog / "What's New" Panel

As a CalcPad user,
I want to see what changed after an update,
So that I can discover new features and understand improvements without checking external release notes.

**Acceptance Criteria:**

**Given** CalcPad has been updated to a new version
**When** the user opens the application for the first time after the update
**Then** a "What's New" panel is displayed highlighting the key changes in the new version
**And** each change includes a brief description and, where relevant, a concrete example (e.g., "New: Stock lookups — try `AAPL price`")

**Given** the "What's New" panel is displayed
**When** the user clicks "Dismiss" or clicks outside the panel
**Then** the panel closes and does not reappear for the same version

**Given** the user wants to review past changelogs
**When** they navigate to Help > What's New
**Then** the current version's changelog is shown
**And** previous versions' changelogs are accessible via a scrollable history or version dropdown

**Given** a minor patch update (e.g., 1.2.3 to 1.2.4) with only bug fixes
**When** the user opens CalcPad after the update
**Then** the "What's New" panel is shown only if there are user-facing changes
**And** if there are no user-facing changes, the panel is not shown and the changelog is silently updated in Help > What's New

**Given** the "What's New" panel content
**When** it is authored for a release
**Then** entries are categorized as "New," "Improved," or "Fixed"
**And** the content is localized into all supported languages (per Story 14.5)
