initial commit

This commit is contained in:
2026-03-16 19:54:53 -04:00
commit bfe0e01254
3341 changed files with 483939 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
---
epic: 1
story: 1.1
title: "Lexer & Tokenizer"
status: draft
---
## 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

View File

@@ -0,0 +1,65 @@
---
epic: 1
story: 1.2
title: "Parser & AST"
status: draft
---
## 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.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

View File

@@ -0,0 +1,60 @@
---
epic: 1
story: 1.3
title: "Interpreter / Evaluator"
status: draft
---
## 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.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

View File

@@ -0,0 +1,51 @@
---
epic: 1
story: 1.4
title: "Arbitrary Precision Arithmetic"
status: draft
---
## 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.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

View File

@@ -0,0 +1,47 @@
---
epic: 1
story: 1.5
title: "C FFI Layer (for Swift)"
status: draft
---
## 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.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

View File

@@ -0,0 +1,41 @@
---
epic: 1
story: 1.6
title: "WASM Bindings (for Web)"
status: draft
---
## 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.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`

View File

@@ -0,0 +1,46 @@
---
epic: 1
story: 1.7
title: "Error Handling & Graceful Degradation"
status: draft
---
## 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.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

View File

@@ -0,0 +1,50 @@
---
epic: 1
story: 1.8
title: "Sheet Context"
status: draft
---
## 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.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

View File

@@ -0,0 +1,37 @@
---
epic: 10
story: 10.1
title: "Headers, Comments & Labels"
status: draft
---
## 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

View File

@@ -0,0 +1,31 @@
---
epic: 10
story: 10.2
title: "Click-to-Copy Answer"
status: draft
---
## Epic 10 — Notepad UX (Cross-Platform Spec)
**Goal:** Consistent editor behavior specified once, implemented per platform.
### 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

View File

@@ -0,0 +1,36 @@
---
epic: 10
story: 10.3
title: "Drag to Resize Columns"
status: draft
---
## Epic 10 — Notepad UX (Cross-Platform Spec)
**Goal:** Consistent editor behavior specified once, implemented per platform.
### 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

View File

@@ -0,0 +1,39 @@
---
epic: 10
story: 10.4
title: "Answer Column Formatting"
status: draft
---
## Epic 10 — Notepad UX (Cross-Platform Spec)
**Goal:** Consistent editor behavior specified once, implemented per platform.
### 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`

View File

@@ -0,0 +1,35 @@
---
epic: 10
story: 10.5
title: "Line Numbers"
status: draft
---
## Epic 10 — Notepad UX (Cross-Platform Spec)
**Goal:** Consistent editor behavior specified once, implemented per platform.
### 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

View File

@@ -0,0 +1,43 @@
---
epic: 10
story: 10.6
title: "Find & Replace"
status: draft
---
## Epic 10 — Notepad UX (Cross-Platform Spec)
**Goal:** Consistent editor behavior specified once, implemented per platform.
### 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

View File

@@ -0,0 +1,32 @@
---
epic: 11
story: 11.1
title: "Single Expression Evaluation"
status: draft
---
## 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

View File

@@ -0,0 +1,32 @@
---
epic: 11
story: 11.2
title: "Pipe / Stdin Mode"
status: draft
---
## Epic 11 — CLI Tool
**Goal:** Command-line tool for scripting and automation, same Rust engine.
### 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

View File

@@ -0,0 +1,36 @@
---
epic: 11
story: 11.3
title: "Output Formats"
status: draft
---
## Epic 11 — CLI Tool
**Goal:** Command-line tool for scripting and automation, same Rust engine.
### 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

View File

@@ -0,0 +1,42 @@
---
epic: 11
story: 11.4
title: "Interactive REPL"
status: draft
---
## Epic 11 — CLI Tool
**Goal:** Command-line tool for scripting and automation, same Rust engine.
### 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)

View File

@@ -0,0 +1,36 @@
---
epic: 11
story: 11.5
title: "Distribution"
status: draft
---
## Epic 11 — CLI Tool
**Goal:** Command-line tool for scripting and automation, same Rust engine.
### 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

View File

@@ -0,0 +1,37 @@
---
epic: 12
story: 12.1
title: "Plugin API (Rust Trait)"
status: draft
---
## 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

View File

@@ -0,0 +1,41 @@
---
epic: 12
story: 12.2
title: "Scripting Layer (Rhai or mlua)"
status: draft
---
## Epic 12 — Plugin & Extension System
**Goal:** Let power users extend CalcPad.
### 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

View File

@@ -0,0 +1,37 @@
---
epic: 12
story: 12.3
title: "Plugin Marketplace"
status: draft
---
## Epic 12 — Plugin & Extension System
**Goal:** Let power users extend CalcPad.
### 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

View File

@@ -0,0 +1,46 @@
---
epic: 12
story: 12.4
title: "Built-in Plugin: Stock Prices"
status: draft
---
## Epic 12 — Plugin & Extension System
**Goal:** Let power users extend CalcPad.
### 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

View File

@@ -0,0 +1,47 @@
---
epic: 12
story: 12.5
title: "Built-in Plugin: Crypto & DeFi"
status: draft
---
## Epic 12 — Plugin & Extension System
**Goal:** Let power users extend CalcPad.
### 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

View File

@@ -0,0 +1,32 @@
---
epic: 13
story: 13.1
title: "Real-Time Evaluation (< 16 ms per Line)"
status: draft
---
## 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

View File

@@ -0,0 +1,38 @@
---
epic: 13
story: 13.2
title: "Dependency Graph & Lazy Evaluation"
status: draft
---
## Epic 13 — Performance & Reliability
**Goal:** Instant evaluation, never lose work.
### 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

View File

@@ -0,0 +1,38 @@
---
epic: 13
story: 13.3
title: "Web Worker Offloading (Web Only)"
status: draft
---
## Epic 13 — Performance & Reliability
**Goal:** Instant evaluation, never lose work.
### 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

View File

@@ -0,0 +1,43 @@
---
epic: 13
story: 13.4
title: "Crash Recovery"
status: draft
---
## Epic 13 — Performance & Reliability
**Goal:** Instant evaluation, never lose work.
### 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

View File

@@ -0,0 +1,44 @@
---
epic: 13
story: 13.5
title: "Test Suite"
status: draft
---
## Epic 13 — Performance & Reliability
**Goal:** Instant evaluation, never lose work.
### 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

View File

@@ -0,0 +1,38 @@
---
epic: 14
story: 14.1
title: "Screen Reader Support"
status: draft
---
## 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")

View File

@@ -0,0 +1,32 @@
---
epic: 14
story: 14.2
title: "High Contrast Mode"
status: draft
---
## Epic 14 — Accessibility & Internationalization
**Goal:** Make CalcPad usable by everyone regardless of ability, language, or locale.
### 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)

View File

@@ -0,0 +1,40 @@
---
epic: 14
story: 14.3
title: "Keyboard-Only Operation"
status: draft
---
## Epic 14 — Accessibility & Internationalization
**Goal:** Make CalcPad usable by everyone regardless of ability, language, or locale.
### 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

View File

@@ -0,0 +1,36 @@
---
epic: 14
story: 14.4
title: "RTL Language Support"
status: draft
---
## Epic 14 — Accessibility & Internationalization
**Goal:** Make CalcPad usable by everyone regardless of ability, language, or locale.
### 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

View File

@@ -0,0 +1,42 @@
---
epic: 14
story: 14.5
title: "Localization Framework"
status: draft
---
## Epic 14 — Accessibility & Internationalization
**Goal:** Make CalcPad usable by everyone regardless of ability, language, or locale.
### 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

View File

@@ -0,0 +1,43 @@
---
epic: 15
story: 15.1
title: "Free Tier"
status: draft
---
## 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

View File

@@ -0,0 +1,51 @@
---
epic: 15
story: 15.2
title: "Pro Tier"
status: draft
---
## Epic 15 — Monetization & Onboarding
**Goal:** Establish sustainable revenue with a generous free tier and frictionless onboarding.
### 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

View File

@@ -0,0 +1,45 @@
---
epic: 15
story: 15.3
title: "Pricing Configuration"
status: draft
---
## Epic 15 — Monetization & Onboarding
**Goal:** Establish sustainable revenue with a generous free tier and frictionless onboarding.
### 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

View File

@@ -0,0 +1,44 @@
---
epic: 15
story: 15.4
title: "Onboarding Tutorial"
status: draft
---
## Epic 15 — Monetization & Onboarding
**Goal:** Establish sustainable revenue with a generous free tier and frictionless onboarding.
### 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

View File

@@ -0,0 +1,46 @@
---
epic: 15
story: 15.5
title: "Template Library"
status: draft
---
## Epic 15 — Monetization & Onboarding
**Goal:** Establish sustainable revenue with a generous free tier and frictionless onboarding.
### 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

View File

@@ -0,0 +1,44 @@
---
epic: 16
story: 16.1
title: "Privacy-Respecting Analytics"
status: draft
---
## 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

View File

@@ -0,0 +1,39 @@
---
epic: 16
story: 16.2
title: "In-App Feedback"
status: draft
---
## Epic 16 — Analytics, Feedback & Iteration
**Goal:** Learn from usage to improve CalcPad while respecting user privacy.
### 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

View File

@@ -0,0 +1,41 @@
---
epic: 16
story: 16.3
title: "Changelog / \"What's New\" Panel"
status: draft
---
## Epic 16 — Analytics, Feedback & Iteration
**Goal:** Learn from usage to improve CalcPad while respecting user privacy.
### 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)

View File

@@ -0,0 +1,50 @@
---
epic: 2
story: 2.1
title: "Unit Registry & Base Conversion"
status: draft
---
## 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

View File

@@ -0,0 +1,46 @@
---
epic: 2
story: 2.2
title: "SI Prefix Support"
status: draft
---
## Epic 2 — Unit Conversion System
**Goal:** Support 200+ units across all major categories with SI prefix support.
### 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

View File

@@ -0,0 +1,42 @@
---
epic: 2
story: 2.3
title: "CSS & Screen Units"
status: draft
---
## Epic 2 — Unit Conversion System
**Goal:** Support 200+ units across all major categories with SI prefix support.
### 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`

View File

@@ -0,0 +1,46 @@
---
epic: 2
story: 2.4
title: "Data Units (Binary vs Decimal)"
status: draft
---
## Epic 2 — Unit Conversion System
**Goal:** Support 200+ units across all major categories with SI prefix support.
### 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`

View File

@@ -0,0 +1,47 @@
---
epic: 2
story: 2.5
title: "Natural Language Unit Expressions"
status: draft
---
## Epic 2 — Unit Conversion System
**Goal:** Support 200+ units across all major categories with SI prefix support.
### 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`

View File

@@ -0,0 +1,45 @@
---
epic: 2
story: 2.6
title: "Custom User-Defined Units"
status: draft
---
## Epic 2 — Unit Conversion System
**Goal:** Support 200+ units across all major categories with SI prefix support.
### 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

View File

@@ -0,0 +1,45 @@
---
epic: 3
story: 3.1
title: "Fiat Currency Provider"
status: draft
---
## 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

View File

@@ -0,0 +1,46 @@
---
epic: 3
story: 3.2
title: "Cryptocurrency Provider"
status: draft
---
## Epic 3 — Currency & Cryptocurrency
**Goal:** Real-time and historical currency conversion with 180+ fiat and 50+ crypto.
### 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`)

View File

@@ -0,0 +1,44 @@
---
epic: 3
story: 3.3
title: "Historical Rates"
status: draft
---
## Epic 3 — Currency & Cryptocurrency
**Goal:** Real-time and historical currency conversion with 180+ fiat and 50+ crypto.
### 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

View File

@@ -0,0 +1,44 @@
---
epic: 3
story: 3.4
title: "Currency Symbol & Code Recognition"
status: draft
---
## Epic 3 — Currency & Cryptocurrency
**Goal:** Real-time and historical currency conversion with 180+ fiat and 50+ crypto.
### 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`

View File

@@ -0,0 +1,47 @@
---
epic: 3
story: 3.5
title: "Multi-Currency Arithmetic"
status: draft
---
## Epic 3 — Currency & Cryptocurrency
**Goal:** Real-time and historical currency conversion with 180+ fiat and 50+ crypto.
### 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

View File

@@ -0,0 +1,48 @@
---
epic: 4
story: 4.1
title: "Date Math"
status: draft
---
## 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

View File

@@ -0,0 +1,47 @@
---
epic: 4
story: 4.2
title: "Time Math"
status: draft
---
## Epic 4 — Date, Time & Time Zones
**Goal:** Full date/time math, business day calculations, and timezone awareness.
### 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`

View File

@@ -0,0 +1,50 @@
---
epic: 4
story: 4.3
title: "Time Zone Conversions"
status: draft
---
## Epic 4 — Date, Time & Time Zones
**Goal:** Full date/time math, business day calculations, and timezone awareness.
### 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

View File

@@ -0,0 +1,46 @@
---
epic: 4
story: 4.4
title: "Business Day Calculations"
status: draft
---
## Epic 4 — Date, Time & Time Zones
**Goal:** Full date/time math, business day calculations, and timezone awareness.
### 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

View File

@@ -0,0 +1,47 @@
---
epic: 4
story: 4.5
title: "Unix Timestamp Conversions"
status: draft
---
## Epic 4 — Date, Time & Time Zones
**Goal:** Full date/time math, business day calculations, and timezone awareness.
### 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

View File

@@ -0,0 +1,50 @@
---
epic: 4
story: 4.6
title: "Relative Time Expressions"
status: draft
---
## Epic 4 — Date, Time & Time Zones
**Goal:** Full date/time math, business day calculations, and timezone awareness.
### 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

View File

@@ -0,0 +1,48 @@
---
epic: 5
story: 5.1
title: "Variable Declaration & Usage"
status: draft
---
## 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)

View File

@@ -0,0 +1,43 @@
---
epic: 5
story: 5.2
title: "Line References"
status: draft
---
## Epic 5 — Variables, Line References & Aggregators
**Goal:** Transform the notepad into a lightweight computational document.
### 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

View File

@@ -0,0 +1,41 @@
---
epic: 5
story: 5.3
title: "Previous Line Reference"
status: draft
---
## Epic 5 — Variables, Line References & Aggregators
**Goal:** Transform the notepad into a lightweight computational document.
### 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

View File

@@ -0,0 +1,49 @@
---
epic: 5
story: 5.4
title: "Section Aggregators"
status: draft
---
## Epic 5 — Variables, Line References & Aggregators
**Goal:** Transform the notepad into a lightweight computational document.
### 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 14 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 14)
**Given** lines 14 contain numeric values and line 5 is a heading `## Monthly Costs`
**When** the user writes `total` immediately after lines 69 (which contain values under that heading)
**Then** `total` sums only lines 69 (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)

View File

@@ -0,0 +1,42 @@
---
epic: 5
story: 5.5
title: "Subtotals & Grand Total"
status: draft
---
## Epic 5 — Variables, Line References & Aggregators
**Goal:** Transform the notepad into a lightweight computational document.
### 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

View File

@@ -0,0 +1,52 @@
---
epic: 5
story: 5.6
title: "Autocomplete"
status: draft
---
## Epic 5 — Variables, Line References & Aggregators
**Goal:** Transform the notepad into a lightweight computational 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

View File

@@ -0,0 +1,70 @@
---
epic: 6
story: 6.1
title: "Trigonometric Functions"
status: draft
---
## 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

View File

@@ -0,0 +1,69 @@
---
epic: 6
story: 6.2
title: "Logarithmic & Exponential Functions"
status: draft
---
## Epic 6 — Advanced Math & Functions
**Goal:** Scientific, financial, and power-user math.
### 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)

View File

@@ -0,0 +1,50 @@
---
epic: 6
story: 6.3
title: "Factorial & Combinatorics"
status: draft
---
## Epic 6 — Advanced Math & Functions
**Goal:** Scientific, financial, and power-user math.
### 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)

View File

@@ -0,0 +1,49 @@
---
epic: 6
story: 6.4
title: "Financial Functions"
status: draft
---
## Epic 6 — Advanced Math & Functions
**Goal:** Scientific, financial, and power-user math.
### 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

View File

@@ -0,0 +1,37 @@
---
epic: 6
story: 6.5
title: "Proportions / Rule of Three"
status: draft
---
## Epic 6 — Advanced Math & Functions
**Goal:** Scientific, financial, and power-user math.
### 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)

View File

@@ -0,0 +1,49 @@
---
epic: 6
story: 6.6
title: "Conditionals"
status: draft
---
## Epic 6 — Advanced Math & Functions
**Goal:** Scientific, financial, and power-user math.
### 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`

View File

@@ -0,0 +1,57 @@
---
epic: 6
story: 6.7
title: "Rounding Functions"
status: draft
---
## Epic 6 — Advanced Math & Functions
**Goal:** Scientific, financial, and power-user math.
### 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)

View File

@@ -0,0 +1,53 @@
---
epic: 6
story: 6.8
title: "List Operations"
status: draft
---
## Epic 6 — Advanced Math & Functions
**Goal:** Scientific, financial, and power-user math.
### 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

View File

@@ -0,0 +1,49 @@
---
epic: 6
story: 6.9
title: "Video Timecodes"
status: draft
---
## Epic 6 — Advanced Math & Functions
**Goal:** Scientific, financial, and power-user math.
### 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)

View File

@@ -0,0 +1,39 @@
---
epic: 7
story: 7.1
title: "SwiftUI Two-Column Editor"
status: draft
---
## 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

View File

@@ -0,0 +1,37 @@
---
epic: 7
story: 7.10
title: "Widgets (Notification Center)"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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

View File

@@ -0,0 +1,45 @@
---
epic: 7
story: 7.11
title: "Keyboard Shortcuts"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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)

View File

@@ -0,0 +1,41 @@
---
epic: 7
story: 7.12
title: "Dark/Light/Auto Theme"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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

View File

@@ -0,0 +1,46 @@
---
epic: 7
story: 7.13
title: "Export"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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

View File

@@ -0,0 +1,38 @@
---
epic: 7
story: 7.14
title: "App Store & Notarization"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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

View File

@@ -0,0 +1,42 @@
---
epic: 7
story: 7.2
title: "Rust FFI Bridge"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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)

View File

@@ -0,0 +1,42 @@
---
epic: 7
story: 7.3
title: "Syntax Highlighting"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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

View File

@@ -0,0 +1,41 @@
---
epic: 7
story: 7.4
title: "Menu Bar Mode"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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

View File

@@ -0,0 +1,45 @@
---
epic: 7
story: 7.5
title: "Global Hotkey (QuickCalc)"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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 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)

View File

@@ -0,0 +1,41 @@
---
epic: 7
story: 7.6
title: "Sheets Sidebar"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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

View File

@@ -0,0 +1,44 @@
---
epic: 7
story: 7.7
title: "iCloud Sync"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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

View File

@@ -0,0 +1,34 @@
---
epic: 7
story: 7.8
title: "Spotlight & Quick Look"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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

View File

@@ -0,0 +1,37 @@
---
epic: 7
story: 7.9
title: "Alfred / Raycast Extension"
status: draft
---
## Epic 7 — macOS App (Swift/SwiftUI)
**Goal:** A beautiful, native macOS app that feels like it belongs on the platform.
### 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)

View File

@@ -0,0 +1,41 @@
---
epic: 8
story: 8.1
title: "iced Two-Column Editor"
status: draft
---
## 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

View File

@@ -0,0 +1,42 @@
---
epic: 8
story: 8.10
title: "Installer & Distribution"
status: draft
---
## Epic 8 — Windows App (Rust + iced)
**Goal:** Fast, lightweight native Windows app -- single .exe, no runtime dependencies.
### 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

View File

@@ -0,0 +1,33 @@
---
epic: 8
story: 8.2
title: "Engine Integration (Direct Linking)"
status: draft
---
## Epic 8 — Windows App (Rust + iced)
**Goal:** Fast, lightweight native Windows app -- single .exe, no runtime dependencies.
### 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

View File

@@ -0,0 +1,37 @@
---
epic: 8
story: 8.3
title: "Syntax Highlighting"
status: draft
---
## Epic 8 — Windows App (Rust + iced)
**Goal:** Fast, lightweight native Windows app -- single .exe, no runtime dependencies.
### 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

View File

@@ -0,0 +1,46 @@
---
epic: 8
story: 8.4
title: "System Tray Mode"
status: draft
---
## Epic 8 — Windows App (Rust + iced)
**Goal:** Fast, lightweight native Windows app -- single .exe, no runtime dependencies.
### 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)

View File

@@ -0,0 +1,45 @@
---
epic: 8
story: 8.5
title: "Global Hotkey (QuickCalc)"
status: draft
---
## Epic 8 — Windows App (Rust + iced)
**Goal:** Fast, lightweight native Windows app -- single .exe, no runtime dependencies.
### 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)

View File

@@ -0,0 +1,46 @@
---
epic: 8
story: 8.6
title: "File Management"
status: draft
---
## Epic 8 — Windows App (Rust + iced)
**Goal:** Fast, lightweight native Windows app -- single .exe, no runtime dependencies.
### 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

View File

@@ -0,0 +1,41 @@
---
epic: 8
story: 8.7
title: "Windows Theming"
status: draft
---
## Epic 8 — Windows App (Rust + iced)
**Goal:** Fast, lightweight native Windows app -- single .exe, no runtime dependencies.
### 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)

View File

@@ -0,0 +1,46 @@
---
epic: 8
story: 8.8
title: "Auto-Update"
status: draft
---
## Epic 8 — Windows App (Rust + iced)
**Goal:** Fast, lightweight native Windows app -- single .exe, no runtime dependencies.
### 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)

View File

@@ -0,0 +1,38 @@
---
epic: 8
story: 8.9
title: "Portable Mode"
status: draft
---
## Epic 8 — Windows App (Rust + iced)
**Goal:** Fast, lightweight native Windows app -- single .exe, no runtime dependencies.
### 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

View File

@@ -0,0 +1,37 @@
---
epic: 9
story: 9.1
title: "WASM Engine Integration"
status: draft
---
## 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

View File

@@ -0,0 +1,37 @@
---
epic: 9
story: 9.2
title: "CodeMirror 6 Editor"
status: draft
---
## Epic 9 — Web App (React + WASM)
**Goal:** Zero-install web experience with real-time collaboration.
### 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

Some files were not shown because too many files have changed in this diff Show More