initial commit
This commit is contained in:
@@ -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
|
||||
65
_bmad-output/implementation-artifacts/1-2-parser-and-ast.md
Normal file
65
_bmad-output/implementation-artifacts/1-2-parser-and-ast.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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`
|
||||
@@ -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
|
||||
50
_bmad-output/implementation-artifacts/1-8-sheet-context.md
Normal file
50
_bmad-output/implementation-artifacts/1-8-sheet-context.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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`
|
||||
35
_bmad-output/implementation-artifacts/10-5-line-numbers.md
Normal file
35
_bmad-output/implementation-artifacts/10-5-line-numbers.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
36
_bmad-output/implementation-artifacts/11-3-output-formats.md
Normal file
36
_bmad-output/implementation-artifacts/11-3-output-formats.md
Normal 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
|
||||
@@ -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)
|
||||
36
_bmad-output/implementation-artifacts/11-5-distribution.md
Normal file
36
_bmad-output/implementation-artifacts/11-5-distribution.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
43
_bmad-output/implementation-artifacts/13-4-crash-recovery.md
Normal file
43
_bmad-output/implementation-artifacts/13-4-crash-recovery.md
Normal 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
|
||||
44
_bmad-output/implementation-artifacts/13-5-test-suite.md
Normal file
44
_bmad-output/implementation-artifacts/13-5-test-suite.md
Normal 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
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
43
_bmad-output/implementation-artifacts/15-1-free-tier.md
Normal file
43
_bmad-output/implementation-artifacts/15-1-free-tier.md
Normal 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
|
||||
51
_bmad-output/implementation-artifacts/15-2-pro-tier.md
Normal file
51
_bmad-output/implementation-artifacts/15-2-pro-tier.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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`)
|
||||
@@ -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
|
||||
@@ -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`
|
||||
@@ -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
|
||||
48
_bmad-output/implementation-artifacts/4-1-date-math.md
Normal file
48
_bmad-output/implementation-artifacts/4-1-date-math.md
Normal 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
|
||||
47
_bmad-output/implementation-artifacts/4-2-time-math.md
Normal file
47
_bmad-output/implementation-artifacts/4-2-time-math.md
Normal 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`
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
43
_bmad-output/implementation-artifacts/5-2-line-references.md
Normal file
43
_bmad-output/implementation-artifacts/5-2-line-references.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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 1–4 contain numeric values `10`, `20`, `30`, `40` and line 5 is empty
|
||||
**When** the user writes `sum` on line 5 (or after the group)
|
||||
**Then** the result is `100` (sum of lines 1–4)
|
||||
|
||||
**Given** lines 1–4 contain numeric values and line 5 is a heading `## Monthly Costs`
|
||||
**When** the user writes `total` immediately after lines 6–9 (which contain values under that heading)
|
||||
**Then** `total` sums only lines 6–9 (the section bounded by the heading above and the aggregator line)
|
||||
|
||||
**Given** a section with values `10`, `20`, `30`
|
||||
**When** the user writes `average` (or `avg`) at the end of the section
|
||||
**Then** the result is `20`
|
||||
|
||||
**Given** a section with values `5`, `12`, `3`, `8`
|
||||
**When** the user writes `min` at the end of the section
|
||||
**Then** the result is `3`
|
||||
|
||||
**Given** a section with values `5`, `12`, `3`, `8`
|
||||
**When** the user writes `max` at the end of the section
|
||||
**Then** the result is `12`
|
||||
|
||||
**Given** a section with values `5`, `12`, `3`, `8`
|
||||
**When** the user writes `count` at the end of the section
|
||||
**Then** the result is `4` (number of lines with numeric results)
|
||||
|
||||
**Given** a section contains a mix of numeric lines and comment lines
|
||||
**When** an aggregator is applied
|
||||
**Then** only lines with numeric results are included in the aggregation (comments and blank lines are skipped)
|
||||
|
||||
**Given** an empty section (heading immediately followed by another heading or end-of-document)
|
||||
**When** the user writes `sum`
|
||||
**Then** the result is `0` (or an appropriate indication that there are no values to aggregate)
|
||||
@@ -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
|
||||
52
_bmad-output/implementation-artifacts/5-6-autocomplete.md
Normal file
52
_bmad-output/implementation-artifacts/5-6-autocomplete.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
49
_bmad-output/implementation-artifacts/6-6-conditionals.md
Normal file
49
_bmad-output/implementation-artifacts/6-6-conditionals.md
Normal 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`
|
||||
@@ -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)
|
||||
53
_bmad-output/implementation-artifacts/6-8-list-operations.md
Normal file
53
_bmad-output/implementation-artifacts/6-8-list-operations.md
Normal 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
|
||||
49
_bmad-output/implementation-artifacts/6-9-video-timecodes.md
Normal file
49
_bmad-output/implementation-artifacts/6-9-video-timecodes.md
Normal 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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
46
_bmad-output/implementation-artifacts/7-13-export.md
Normal file
46
_bmad-output/implementation-artifacts/7-13-export.md
Normal 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
|
||||
@@ -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
|
||||
42
_bmad-output/implementation-artifacts/7-2-rust-ffi-bridge.md
Normal file
42
_bmad-output/implementation-artifacts/7-2-rust-ffi-bridge.md
Normal 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)
|
||||
@@ -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
|
||||
41
_bmad-output/implementation-artifacts/7-4-menu-bar-mode.md
Normal file
41
_bmad-output/implementation-artifacts/7-4-menu-bar-mode.md
Normal 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
|
||||
@@ -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)
|
||||
41
_bmad-output/implementation-artifacts/7-6-sheets-sidebar.md
Normal file
41
_bmad-output/implementation-artifacts/7-6-sheets-sidebar.md
Normal 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
|
||||
44
_bmad-output/implementation-artifacts/7-7-icloud-sync.md
Normal file
44
_bmad-output/implementation-artifacts/7-7-icloud-sync.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
46
_bmad-output/implementation-artifacts/8-6-file-management.md
Normal file
46
_bmad-output/implementation-artifacts/8-6-file-management.md
Normal 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
|
||||
41
_bmad-output/implementation-artifacts/8-7-windows-theming.md
Normal file
41
_bmad-output/implementation-artifacts/8-7-windows-theming.md
Normal 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)
|
||||
46
_bmad-output/implementation-artifacts/8-8-auto-update.md
Normal file
46
_bmad-output/implementation-artifacts/8-8-auto-update.md
Normal 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)
|
||||
38
_bmad-output/implementation-artifacts/8-9-portable-mode.md
Normal file
38
_bmad-output/implementation-artifacts/8-9-portable-mode.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user